URI: 
       tstore invoices in a separate file, with their status - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 036f96cf35e5de2a6029de0af947f5ca34d821a4
   DIR parent 3bac924303b4b4ae2e1009f11544c4afe7b99849
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Tue, 11 Nov 2014 11:08:25 +0100
       
       store invoices in a separate file, with their status
       
       Diffstat:
         M gui/qt/main_window.py               |      90 ++++++++++++-------------------
         M lib/paymentrequest.py               |     151 ++++++++++++++++++++-----------
         M lib/wallet.py                       |       2 +-
         M lib/x509.py                         |       3 +++
       
       4 files changed, 137 insertions(+), 109 deletions(-)
       ---
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -78,13 +78,8 @@ class StatusBarButton(QPushButton):
                    apply(self.func,())
        
        
       -
       -# status of payment requests
       -PR_UNPAID  = 0
       -PR_EXPIRED = 1
       -PR_SENT    = 2     # sent but not propagated
       -PR_PAID    = 3     # send and propagated
       -PR_ERROR   = 4     # could not parse
       +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
       +from electrum.paymentrequest import PaymentRequest, InvoiceStore, get_payment_request
        
        pr_icons = {
            PR_UNPAID:":icons/unpaid.png",
       t@@ -113,12 +108,13 @@ class ElectrumWindow(QMainWindow):
                self.lite = None
                self.app = gui_object.app
        
       +        self.invoices = InvoiceStore(self.config)
       +
                self.create_status_bar()
                self.need_update = threading.Event()
        
                self.decimal_point = config.get('decimal_point', 5)
                self.num_zeros     = int(config.get('num_zeros',0))
       -        self.invoices      = {}
        
                self.completions = QStringListModel()
        
       t@@ -203,7 +199,6 @@ class ElectrumWindow(QMainWindow):
                a = self.wallet.addresses(False)
                self.dummy_address = a[0] if a else None
        
       -        self.invoices = self.wallet.storage.get('invoices', {})
                self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
                self.current_account = self.wallet.storage.get("current_account", None)
                title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + os.path.basename(self.wallet.storage.path)
       t@@ -568,7 +563,8 @@ class ElectrumWindow(QMainWindow):
        
                self.receive_address_e = QLineEdit()
                self.receive_address_e.setReadOnly(True)
       -        grid.addWidget(QLabel(_('Receiving address')), 0, 0)
       +        self.receive_address_label = QLabel(_('Receiving address'))
       +        grid.addWidget(self.receive_address_label, 0, 0)
                grid.addWidget(self.receive_address_e, 0, 1, 1, 3)
                self.receive_address_e.textChanged.connect(self.update_receive_qr)
        
       t@@ -1060,8 +1056,7 @@ class ElectrumWindow(QMainWindow):
                    status, msg =  self.wallet.sendtx(tx)
                    if not status:
                        return False, msg
       -            self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_PAID, tx.hash())
       -            self.wallet.storage.put('invoices', self.invoices)
       +            pr.set_paid(tx.hash())
                    self.payment_request = None
                    refund_address = self.wallet.addresses()[0]
                    ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
       t@@ -1096,15 +1091,9 @@ class ElectrumWindow(QMainWindow):
        
            def payment_request_ok(self):
                pr = self.payment_request
       -        pr_id = pr.get_id()
       -        if pr_id not in self.invoices:
       -            self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_UNPAID, None)
       -            self.wallet.storage.put('invoices', self.invoices)
       -            self.update_invoices_tab()
       -        else:
       -            print_error('invoice already in list')
       -
       -        status = self.invoices[pr_id][4]
       +        status = pr.get_status()
       +        key = self.invoices.add(pr)
       +        self.update_invoices_tab()
                if status == PR_PAID:
                    self.do_clear()
                    self.show_message("invoice already paid")
       t@@ -1113,7 +1102,6 @@ class ElectrumWindow(QMainWindow):
        
                self.payto_help.show()
                self.payto_help.set_alt(lambda: self.show_pr_details(pr))
       -
                if not pr.has_expired():
                    self.payto_e.setGreen()
                else:
       t@@ -1159,16 +1147,14 @@ class ElectrumWindow(QMainWindow):
                        self.amount_e.textEdited.emit("")
                    return
        
       -        from electrum import paymentrequest
       -        def payment_request():
       -            self.payment_request = paymentrequest.PaymentRequest(self.config)
       -            self.payment_request.read(request_url)
       +        def get_payment_request_thread():
       +            self.payment_request = get_payment_request(request_url)
                    if self.payment_request.verify():
                        self.emit(SIGNAL('payment_request_ok'))
                    else:
                        self.emit(SIGNAL('payment_request_error'))
        
       -        self.pr_thread = threading.Thread(target=payment_request).start()
       +        self.pr_thread = threading.Thread(target=get_payment_request_thread).start()
                self.prepare_for_payment_request()
        
        
       t@@ -1227,15 +1213,14 @@ class ElectrumWindow(QMainWindow):
                return self.create_list_tab(l)
        
            def update_invoices_tab(self):
       -        invoices = self.wallet.storage.get('invoices', {})
                l = self.invoices_list
                l.clear()
       -        for key, value in sorted(invoices.items(), key=lambda x: -x[1][3]):
       -            domain, memo, amount, expiration_date, status, tx_hash = value
       -            if status == PR_UNPAID and expiration_date and expiration_date < time.time():
       -                status = PR_EXPIRED
       -            date_str = format_time(expiration_date)
       -            item = QTreeWidgetItem( [ date_str, domain, memo, self.format_amount(amount, whitespaces=True), ''] )
       +        for pr in self.invoices.sorted_list():
       +            key = pr.get_id()
       +            status = pr.get_status()
       +            domain = pr.get_domain()
       +            date_str = format_time(pr.get_expiration_date())
       +            item = QTreeWidgetItem( [ date_str, domain, pr.memo, self.format_amount(pr.get_amount(), whitespaces=True), ''] )
                    icon = QIcon(pr_icons.get(status))
                    item.setIcon(4, icon)
                    item.setToolTip(4, pr_tooltips.get(status,''))
       t@@ -1385,36 +1370,25 @@ class ElectrumWindow(QMainWindow):
                run_hook('create_contact_menu', menu, item)
                menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
        
       -    def delete_invoice(self, key):
       -        self.invoices.pop(key)
       -        self.wallet.storage.put('invoices', self.invoices)
       -        self.update_invoices_tab()
        
            def show_invoice(self, key):
       -        from electrum.paymentrequest import PaymentRequest
       -        domain, memo, value, expiration, status, tx_hash = self.invoices[key]
       -        pr = PaymentRequest(self.config)
       -        pr.read_file(key)
       -        pr.domain = domain
       +        pr = self.invoices.get(key)
                pr.verify()
       -        self.show_pr_details(pr, tx_hash)
       +        self.show_pr_details(pr)
        
       -    def show_pr_details(self, pr, tx_hash=None):
       -        msg = 'Domain: ' + pr.domain
       -        msg += '\nStatus: ' + pr.get_status()
       +    def show_pr_details(self, pr):
       +        msg = 'Requestor: ' + pr.get_domain()
       +        msg += '\nExpires: ' + format_time(pr.get_expiration_date())
       +        msg += '\nStatus: ' + pr.get_verify_status()
                msg += '\nMemo: ' + pr.get_memo()
                msg += '\nPayment URL: ' + pr.payment_url
                msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[1] + ' ' + self.format_amount(x[2])+ self.base_unit(), pr.get_outputs()))
       -        if tx_hash:
       -            msg += '\n\nTransaction ID: ' + tx_hash
       +        if pr.tx:
       +            msg += '\n\nTransaction ID: ' + pr.tx
                QMessageBox.information(self, 'Invoice', msg , 'OK')
        
            def do_pay_invoice(self, key):
       -        from electrum.paymentrequest import PaymentRequest
       -        domain, memo, value, expiration, status, tx_hash = self.invoices[key]
       -        pr = PaymentRequest(self.config)
       -        pr.read_file(key)
       -        pr.domain = domain
       +        pr = self.invoices.get(key)
                self.payment_request = pr
                self.prepare_for_payment_request()
                if pr.verify():
       t@@ -1428,12 +1402,16 @@ class ElectrumWindow(QMainWindow):
                if not item:
                    return
                key = str(item.data(0, 32).toString())
       -        domain, memo, value, expiration, status, tx_hash = self.invoices[key]
       +        pr = self.invoices.get(key)
       +        status = pr.get_status()
                menu = QMenu()
                menu.addAction(_("Details"), lambda: self.show_invoice(key))
                if status == PR_UNPAID:
                    menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
       -        menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
       +        def delete_invoice(key):
       +            self.wallet.delete_invoice(key)
       +            self.update_invoices_tab()
       +        menu.addAction(_("Delete"), lambda: delete_invoice(key))
                menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
        
        
   DIR diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py
       t@@ -38,7 +38,7 @@ import bitcoin
        import util
        import transaction
        import x509
       -
       +from util import print_error
        
        REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
        ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
       t@@ -47,59 +47,47 @@ ca_path = requests.certs.where()
        ca_list = x509.load_certificates(ca_path)
        
        
       -class PaymentRequest:
       -    def __init__(self, config):
       -        self.config = config
       -        self.outputs = []
       -        self.error = ""
       -        self.dir_path = os.path.join( self.config.path, 'requests')
       -        if not os.path.exists(self.dir_path):
       -            os.mkdir(self.dir_path)
       -
       -    def read(self, url):
       -        self.url = url
       -        u = urlparse.urlparse(url)
       -        self.domain = u.netloc
       -        try:
       -            connection = httplib.HTTPConnection(u.netloc) if u.scheme == 'http' else httplib.HTTPSConnection(u.netloc)
       -            connection.request("GET",u.geturl(), headers=REQUEST_HEADERS)
       -            response = connection.getresponse()
       -        except:
       -            self.error = "cannot read url"
       -            return
       +# status of payment requests
       +PR_UNPAID  = 0
       +PR_EXPIRED = 1
       +PR_SENT    = 2     # sent but not propagated
       +PR_PAID    = 3     # send and propagated
       +PR_ERROR   = 4     # could not parse
        
       -        try:
       -            r = response.read()
       -        except:
       -            self.error = "cannot read"
       -            return
       -
       -        self.id = bitcoin.sha256(r)[0:16].encode('hex')
       -        filename = os.path.join(self.dir_path, self.id)
       -        with open(filename,'wb') as f:
       -            f.write(r)
       +import json
        
       -        return self.parse(r)
        
       +def get_payment_request(url):
       +    u = urlparse.urlparse(url)
       +    domain = u.netloc
       +    connection = httplib.HTTPConnection(u.netloc) if u.scheme == 'http' else httplib.HTTPSConnection(u.netloc)
       +    connection.request("GET", u.geturl(), headers=REQUEST_HEADERS)
       +    response = connection.getresponse()
       +    data = response.read()
       +    pr = PaymentRequest(data)
       +    return pr
        
       -    def get_status(self):
       -        if self.error:
       -            return self.error
       -        else:
       -            return self.status
        
       +class PaymentRequest:
        
       -    def read_file(self, key):
       -        filename = os.path.join(self.dir_path, key)
       -        with open(filename,'rb') as f:
       -            r = f.read()
       +    def __init__(self, data):
       +        self.raw = data
       +        self.parse(data)
       +        self.domain = None # known after verify
       +        self.tx = None
        
       -        assert key == bitcoin.sha256(r)[0:16].encode('hex')
       -        self.id = key
       -        self.parse(r)
       +    def __str__(self):
       +        return self.raw
        
       +    def get_status(self):
       +        if self.tx is not None:
       +            return PR_PAID
       +        if self.has_expired():
       +            return PR_EXPIRED
       +        return PR_UNPAID
        
            def parse(self, r):
       +        self.id = bitcoin.sha256(r)[0:16].encode('hex')
                try:
                    self.data = pb2.PaymentRequest()
                    self.data.ParseFromString(r)
       t@@ -108,13 +96,13 @@ class PaymentRequest:
                    return
                self.details = pb2.PaymentDetails()
                self.details.ParseFromString(self.data.serialized_payment_details)
       +        self.outputs = []
                for o in self.details.outputs:
                    addr = transaction.get_address_from_output_script(o.script)[1]
                    self.outputs.append(('address', addr, o.amount))
                self.memo = self.details.memo
                self.payment_url = self.details.payment_url
        
       -
            def verify(self):
                if not ca_list:
                    self.error = "Trusted certificate authorities list not found"
       t@@ -135,10 +123,10 @@ class PaymentRequest:
                    if i == 0:
                        try:
                            x.check_date()
       -                    x.check_name(self.domain)
                        except Exception as e:
                            self.error = str(e)
                            return
       +                self.domain = x.get_common_name()
                    else:
                        if not x.check_ca():
                            self.error = "ERROR: Supplied CA Certificate Error"
       t@@ -149,13 +137,10 @@ class PaymentRequest:
                # if the root CA is not supplied, add it to the chain
                ca = x509_chain[cert_num-1]
                supplied_CA_fingerprint = ca.getFingerprint()
       -        supplied_CA_names = ca.extract_names()
       -        CA_OU = supplied_CA_names['OU']
                x = ca_list.get(supplied_CA_fingerprint)
                if x:
                    x.slow_parse()
       -            names = x.extract_names()
       -            assert names['CN'] == supplied_CA_names['CN']
       +            assert x.get_common_name() == ca.get_common_name()
                else:
                    issuer = ca.get_issuer()
                    for x in ca_list.values():
       t@@ -216,7 +201,7 @@ class PaymentRequest:
                    self.error = "ERROR: Invalid Signature for Payment Request Data"
                    return False
                ### SIG Verified
       -        self.status = 'Signed by Trusted CA:\n' + CA_OU
       +        self.error = 'Signed by Trusted CA: ' + ca.extract_names()['OU']
                return True
        
            def has_expired(self):
       t@@ -229,7 +214,10 @@ class PaymentRequest:
                return sum(map(lambda x:x[2], self.outputs))
        
            def get_domain(self):
       -        return self.domain
       +        return self.domain if self.domain else 'unknown'
       +
       +    def get_verify_status(self):
       +        return self.error
        
            def get_memo(self):
                return self.memo
       t@@ -303,6 +291,65 @@ def make_payment_request(amount, script, memo, rsakey=None):
        
        
        
       +
       +class InvoiceStore(object):
       +
       +    def __init__(self, config):
       +        self.config = config
       +        self.invoices = {}
       +        self.load_invoices()
       +
       +    def load_invoices(self):
       +        path = os.path.join(self.config.path, 'invoices')
       +        try:
       +            with open(path, 'r') as f:
       +                d = json.loads(f.read())
       +        except:
       +            return
       +        for k, v in d.items():
       +            ser, domain, tx = v
       +            try:
       +                pr = PaymentRequest(ser.decode('hex'))
       +                pr.tx = tx
       +                pr.domain = domain
       +                self.invoices[k] = pr
       +            except:
       +                continue
       +
       +    def save(self):
       +        l = {}
       +        for k, pr in self.invoices.items():
       +            l[k] = str(pr).encode('hex'), pr.domain, pr.tx
       +        path = os.path.join(self.config.path, 'invoices')
       +        with open(path, 'w') as f:
       +            r = f.write(json.dumps(l))
       +
       +    def add(self, pr):
       +        key = pr.get_id()
       +        if key in self.invoices:
       +            print_error('invoice already in list')
       +            return False
       +        self.invoices[key] = pr
       +        self.save()
       +        return key
       +
       +    def remove(self, key):
       +        self.invoices.pop(key)
       +        self.save()
       +
       +    def get(self, k):
       +        return self.invoices.get(k)
       +
       +    def set_paid(self, key, tx_hash):
       +        self.invoices[key].tx = tx_hash
       +        self.save()
       +
       +    def sorted_list(self):
       +        # sort
       +        return self.invoices.values()
       +
       +
       +
        if __name__ == "__main__":
        
            util.set_verbosity(True)
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -171,7 +171,6 @@ class Abstract_Wallet(object):
                self.imported_keys = self.storage.get('imported_keys',{})
        
                self.load_accounts()
       -
                self.load_transactions()
        
                # spv
       t@@ -221,6 +220,7 @@ class Abstract_Wallet(object):
                    self.history = {}
                self.save_transactions()
        
       +    # wizard action
            def get_action(self):
                pass
        
   DIR diff --git a/lib/x509.py b/lib/x509.py
       t@@ -75,6 +75,9 @@ class X509(tlslite.X509):
                self.subject = self.tbs.getComponentByName('subject')
                self.extensions = self.tbs.getComponentByName('extensions') or []
        
       +    def get_common_name(self):
       +        return self.extract_names()['CN']
       +
            def get_issuer(self):
                results = {'CN': None, 'OU': None,}
                issuer = self.tbs.getComponentByName('issuer')