Browse Source

invoices: follow-up fixes re clean-up

follow-up 6058829870 and related
master
SomberNight 6 years ago
parent
commit
309ba15745
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 6
      electrum/gui/kivy/uix/screens.py
  2. 8
      electrum/gui/qt/main_window.py
  3. 33
      electrum/invoices.py
  4. 8
      electrum/plugins/email_requests/qt.py
  5. 58
      electrum/wallet.py

6
electrum/gui/kivy/uix/screens.py

@ -310,7 +310,11 @@ class SendScreen(CScreen):
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address) self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
return return
outputs = [PartialTxOutput.from_address_and_value(address, amount)] outputs = [PartialTxOutput.from_address_and_value(address, amount)]
return self.app.wallet.create_invoice(outputs, message, self.payment_request, self.parsed_URI) return self.app.wallet.create_invoice(
outputs=outputs,
message=message,
pr=self.payment_request,
URI=self.parsed_URI)
def do_save(self): def do_save(self):
invoice = self.read_invoice() invoice = self.read_invoice()

8
electrum/gui/qt/main_window.py

@ -1523,7 +1523,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if self.check_send_tab_onchain_outputs_and_show_errors(outputs): if self.check_send_tab_onchain_outputs_and_show_errors(outputs):
return return
message = self.message_e.text() message = self.message_e.text()
return self.wallet.create_invoice(outputs, message, self.payment_request, self.payto_URI) return self.wallet.create_invoice(
outputs=outputs,
message=message,
pr=self.payment_request,
URI=self.payto_URI)
def do_save_invoice(self): def do_save_invoice(self):
invoice = self.read_invoice() invoice = self.read_invoice()
@ -1772,7 +1776,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
return return
key = pr.get_id() key = pr.get_id()
invoice = self.wallet.get_invoice(key) invoice = self.wallet.get_invoice(key)
if invoice and self.wallet.get_invoice_status() == PR_PAID: if invoice and self.wallet.get_invoice_status(invoice) == PR_PAID:
self.show_message("invoice already paid") self.show_message("invoice already paid")
self.do_clear() self.do_clear()
self.payment_request = None self.payment_request = None

33
electrum/invoices.py

@ -1,5 +1,6 @@
import attr import attr
import time import time
from typing import TYPE_CHECKING, List
from .json_db import StoredObject from .json_db import StoredObject
from .i18n import _ from .i18n import _
@ -9,6 +10,9 @@ from . import constants
from .bitcoin import COIN from .bitcoin import COIN
from .transaction import PartialTxOutput from .transaction import PartialTxOutput
if TYPE_CHECKING:
from .paymentrequest import PaymentRequest
# convention: 'invoices' = outgoing , 'request' = incoming # convention: 'invoices' = outgoing , 'request' = incoming
# types of payment requests # types of payment requests
@ -54,7 +58,14 @@ pr_expiration_values = {
} }
assert PR_DEFAULT_EXPIRATION_WHEN_CREATING in pr_expiration_values assert PR_DEFAULT_EXPIRATION_WHEN_CREATING in pr_expiration_values
outputs_decoder = lambda _list: [PartialTxOutput.from_legacy_tuple(*x) for x in _list]
def _decode_outputs(outputs) -> List[PartialTxOutput]:
ret = []
for output in outputs:
if not isinstance(output, PartialTxOutput):
output = PartialTxOutput.from_legacy_tuple(*output)
ret.append(output)
return ret
# hack: BOLT-11 is not really clear on what an expiry of 0 means. # hack: BOLT-11 is not really clear on what an expiry of 0 means.
# It probably interprets it as 0 seconds, so already expired... # It probably interprets it as 0 seconds, so already expired...
@ -86,21 +97,35 @@ class Invoice(StoredObject):
@attr.s @attr.s
class OnchainInvoice(Invoice): class OnchainInvoice(Invoice):
id = attr.ib(type=str) id = attr.ib(type=str)
outputs = attr.ib(type=list, converter=outputs_decoder) outputs = attr.ib(type=list, converter=_decode_outputs)
bip70 = attr.ib(type=str) # may be None bip70 = attr.ib(type=str) # may be None
requestor = attr.ib(type=str) # may be None requestor = attr.ib(type=str) # may be None
def get_address(self): def get_address(self) -> str:
assert len(self.outputs) == 1 assert len(self.outputs) == 1
return self.outputs[0].address return self.outputs[0].address
@classmethod
def from_bip70_payreq(cls, pr: 'PaymentRequest') -> 'OnchainInvoice':
return OnchainInvoice(
type=PR_TYPE_ONCHAIN,
amount=pr.get_amount(),
outputs=pr.get_outputs(),
message=pr.get_memo(),
id=pr.get_id(),
time=pr.get_time(),
exp=pr.get_expiration_date() - pr.get_time(),
bip70=pr.raw.hex() if pr else None,
requestor=pr.get_requestor(),
)
@attr.s @attr.s
class LNInvoice(Invoice): class LNInvoice(Invoice):
rhash = attr.ib(type=str) rhash = attr.ib(type=str)
invoice = attr.ib(type=str) invoice = attr.ib(type=str)
@classmethod @classmethod
def from_bech32(klass, invoice: str): def from_bech32(klass, invoice: str) -> 'LNInvoice':
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None amount = int(lnaddr.amount * COIN) if lnaddr.amount else None
return LNInvoice( return LNInvoice(

8
electrum/plugins/email_requests/qt.py

@ -29,6 +29,7 @@ import base64
from functools import partial from functools import partial
import traceback import traceback
import sys import sys
from typing import Set
import smtplib import smtplib
import imaplib import imaplib
@ -48,6 +49,8 @@ from electrum.plugin import BasePlugin, hook
from electrum.paymentrequest import PaymentRequest from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import Logger from electrum.logging import Logger
from electrum.wallet import Abstract_Wallet
from electrum.invoices import OnchainInvoice
class Processor(threading.Thread, Logger): class Processor(threading.Thread, Logger):
@ -150,7 +153,7 @@ class Plugin(BasePlugin):
self.processor.start() self.processor.start()
self.obj = QEmailSignalObject() self.obj = QEmailSignalObject()
self.obj.email_new_invoice_signal.connect(self.new_invoice) self.obj.email_new_invoice_signal.connect(self.new_invoice)
self.wallets = set() self.wallets = set() # type: Set[Abstract_Wallet]
def on_receive(self, pr_str): def on_receive(self, pr_str):
self.logger.info('received payment request') self.logger.info('received payment request')
@ -166,8 +169,9 @@ class Plugin(BasePlugin):
self.wallets -= {wallet} self.wallets -= {wallet}
def new_invoice(self): def new_invoice(self):
invoice = OnchainInvoice.from_bip70_payreq(self.pr)
for wallet in self.wallets: for wallet in self.wallets:
wallet.invoices.add(self.pr) wallet.save_invoice(invoice)
#main_window.invoice_list.update() #main_window.invoice_list.update()
@hook @hook

58
electrum/wallet.py

@ -68,7 +68,7 @@ from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
from .plugin import run_hook from .plugin import run_hook
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL, from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE) TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE)
from .invoices import Invoice, OnchainInvoice, invoice_from_json from .invoices import Invoice, OnchainInvoice, invoice_from_json, LNInvoice
from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT, PR_TYPE_ONCHAIN, PR_TYPE_LN from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT, PR_TYPE_ONCHAIN, PR_TYPE_LN
from .contacts import Contacts from .contacts import Contacts
from .interface import NetworkException from .interface import NetworkException
@ -248,7 +248,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
self.frozen_coins = set(db.get('frozen_coins', [])) # set of txid:vout strings self.frozen_coins = set(db.get('frozen_coins', [])) # set of txid:vout strings
self.fiat_value = db.get_dict('fiat_value') self.fiat_value = db.get_dict('fiat_value')
self.receive_requests = db.get_dict('payment_requests') self.receive_requests = db.get_dict('payment_requests')
self.invoices = db.get_dict('invoices') self.invoices = db.get_dict('invoices') # type: Dict[str, Invoice]
self._reserved_addresses = set(db.get('reserved_addresses', [])) self._reserved_addresses = set(db.get('reserved_addresses', []))
self._prepare_onchain_invoice_paid_detection() self._prepare_onchain_invoice_paid_detection()
@ -656,43 +656,33 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
'txpos_in_block': hist_item.tx_mined_status.txpos, 'txpos_in_block': hist_item.tx_mined_status.txpos,
} }
def create_invoice(self, outputs: List[PartialTxOutput], message, pr, URI): def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
if pr:
return OnchainInvoice.from_bip70_payreq(pr)
if '!' in (x.value for x in outputs): if '!' in (x.value for x in outputs):
amount = '!' amount = '!'
else: else:
amount = sum(x.value for x in outputs) amount = sum(x.value for x in outputs)
outputs = [x.to_legacy_tuple() for x in outputs] invoice = OnchainInvoice(
if pr: type=PR_TYPE_ONCHAIN,
invoice = OnchainInvoice( amount=amount,
type = PR_TYPE_ONCHAIN, outputs=outputs,
amount = amount, message=message,
outputs = outputs, id=bh2u(sha256(repr(outputs))[0:16]),
message = pr.get_memo(), time=URI.get('time') if URI else int(time.time()),
id = pr.get_id(), exp=URI.get('exp') if URI else 0,
time = pr.get_time(), bip70=None,
exp = pr.get_expiration_date() - pr.get_time(), requestor=None,
bip70 = pr.raw.hex() if pr else None, )
requestor = pr.get_requestor(),
)
else:
invoice = OnchainInvoice(
type = PR_TYPE_ONCHAIN,
amount = amount,
outputs = outputs,
message = message,
id = bh2u(sha256(repr(outputs))[0:16]),
time = URI.get('time') if URI else int(time.time()),
exp = URI.get('exp') if URI else 0,
bip70 = None,
requestor = None,
)
return invoice return invoice
def save_invoice(self, invoice: Invoice): def save_invoice(self, invoice: Invoice) -> None:
invoice_type = invoice.type invoice_type = invoice.type
if invoice_type == PR_TYPE_LN: if invoice_type == PR_TYPE_LN:
assert isinstance(invoice, LNInvoice)
key = invoice.rhash key = invoice.rhash
elif invoice_type == PR_TYPE_ONCHAIN: elif invoice_type == PR_TYPE_ONCHAIN:
assert isinstance(invoice, OnchainInvoice)
key = invoice.id key = invoice.id
if self.is_onchain_invoice_paid(invoice): if self.is_onchain_invoice_paid(invoice):
self.logger.info("saving invoice... but it is already paid!") self.logger.info("saving invoice... but it is already paid!")
@ -729,12 +719,14 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
self._invoices_from_scriptpubkey_map = defaultdict(set) # type: Dict[bytes, Set[str]] self._invoices_from_scriptpubkey_map = defaultdict(set) # type: Dict[bytes, Set[str]]
for invoice_key, invoice in self.invoices.items(): for invoice_key, invoice in self.invoices.items():
if invoice.type == PR_TYPE_ONCHAIN: if invoice.type == PR_TYPE_ONCHAIN:
assert isinstance(invoice, OnchainInvoice)
for txout in invoice.outputs: for txout in invoice.outputs:
self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key) self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key)
def _is_onchain_invoice_paid(self, invoice: Invoice) -> Tuple[bool, Sequence[str]]: def _is_onchain_invoice_paid(self, invoice: Invoice) -> Tuple[bool, Sequence[str]]:
"""Returns whether on-chain invoice is satisfied, and list of relevant TXIDs.""" """Returns whether on-chain invoice is satisfied, and list of relevant TXIDs."""
assert invoice.type == PR_TYPE_ONCHAIN assert invoice.type == PR_TYPE_ONCHAIN
assert isinstance(invoice, OnchainInvoice)
invoice_amounts = defaultdict(int) # type: Dict[bytes, int] # scriptpubkey -> value_sats invoice_amounts = defaultdict(int) # type: Dict[bytes, int] # scriptpubkey -> value_sats
for txo in invoice.outputs: # type: PartialTxOutput for txo in invoice.outputs: # type: PartialTxOutput
invoice_amounts[txo.scriptpubkey] += 1 if txo.value == '!' else txo.value invoice_amounts[txo.scriptpubkey] += 1 if txo.value == '!' else txo.value
@ -763,9 +755,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
for invoice_key in self._get_relevant_invoice_keys_for_tx(tx): for invoice_key in self._get_relevant_invoice_keys_for_tx(tx):
invoice = self.invoices.get(invoice_key) invoice = self.invoices.get(invoice_key)
if invoice is None: continue if invoice is None: continue
assert invoice.get('type') == PR_TYPE_ONCHAIN assert isinstance(invoice, OnchainInvoice)
if invoice['message']: if invoice.message:
labels.append(invoice['message']) labels.append(invoice.message)
if labels: if labels:
self.set_label(tx_hash, "; ".join(labels)) self.set_label(tx_hash, "; ".join(labels))
return bool(labels) return bool(labels)
@ -1610,7 +1602,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
status = PR_EXPIRED status = PR_EXPIRED
return status return status
def get_invoice_status(self, invoice): def get_invoice_status(self, invoice: Invoice):
if invoice.is_lightning(): if invoice.is_lightning():
status = self.lnworker.get_invoice_status(invoice) status = self.lnworker.get_invoice_status(invoice)
else: else:

Loading…
Cancel
Save