commit 036f96cf35e5de2a6029de0af947f5ca34d821a4
parent 3bac924303b4b4ae2e1009f11544c4afe7b99849
Author: ThomasV <thomasv@gitorious>
Date: Tue, 11 Nov 2014 11:08:25 +0100
store invoices in a separate file, with their status
Diffstat:
4 files changed, 137 insertions(+), 109 deletions(-)
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -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",
@@ -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()
@@ -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)
@@ -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)
@@ -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)
@@ -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")
@@ -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:
@@ -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()
@@ -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,''))
@@ -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():
@@ -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))
diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py
@@ -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'}
@@ -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)
@@ -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"
@@ -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"
@@ -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():
@@ -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):
@@ -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
@@ -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)
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -171,7 +171,6 @@ class Abstract_Wallet(object):
self.imported_keys = self.storage.get('imported_keys',{})
self.load_accounts()
-
self.load_transactions()
# spv
@@ -221,6 +220,7 @@ class Abstract_Wallet(object):
self.history = {}
self.save_transactions()
+ # wizard action
def get_action(self):
pass
diff --git a/lib/x509.py b/lib/x509.py
@@ -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')