commit 309ba15745cc60c01f660c10e30bbe3ac4fb9604
parent e058ee29575ddd69afeefe4a0319360163750c53
Author: SomberNight <somber.night@protonmail.com>
Date: Wed, 3 Jun 2020 21:00:03 +0200
invoices: follow-up fixes re clean-up
follow-up 6058829870fde0ef17b2e08a567110ecc381ab94 and related
Diffstat:
5 files changed, 71 insertions(+), 42 deletions(-)
diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
@@ -310,7 +310,11 @@ class SendScreen(CScreen):
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
return
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):
invoice = self.read_invoice()
diff --git a/electrum/gui/qt/main_window.py b/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):
return
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):
invoice = self.read_invoice()
@@ -1772,7 +1776,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
return
key = pr.get_id()
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.do_clear()
self.payment_request = None
diff --git a/electrum/invoices.py b/electrum/invoices.py
@@ -1,5 +1,6 @@
import attr
import time
+from typing import TYPE_CHECKING, List
from .json_db import StoredObject
from .i18n import _
@@ -9,6 +10,9 @@ from . import constants
from .bitcoin import COIN
from .transaction import PartialTxOutput
+if TYPE_CHECKING:
+ from .paymentrequest import PaymentRequest
+
# convention: 'invoices' = outgoing , 'request' = incoming
# types of payment requests
@@ -54,7 +58,14 @@ 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.
# It probably interprets it as 0 seconds, so already expired...
@@ -86,21 +97,35 @@ class Invoice(StoredObject):
@attr.s
class OnchainInvoice(Invoice):
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
requestor = attr.ib(type=str) # may be None
- def get_address(self):
+ def get_address(self) -> str:
assert len(self.outputs) == 1
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
class LNInvoice(Invoice):
rhash = attr.ib(type=str)
invoice = attr.ib(type=str)
@classmethod
- def from_bech32(klass, invoice: str):
+ def from_bech32(klass, invoice: str) -> 'LNInvoice':
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None
return LNInvoice(
diff --git a/electrum/plugins/email_requests/qt.py b/electrum/plugins/email_requests/qt.py
@@ -29,6 +29,7 @@ import base64
from functools import partial
import traceback
import sys
+from typing import Set
import smtplib
import imaplib
@@ -48,6 +49,8 @@ from electrum.plugin import BasePlugin, hook
from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _
from electrum.logging import Logger
+from electrum.wallet import Abstract_Wallet
+from electrum.invoices import OnchainInvoice
class Processor(threading.Thread, Logger):
@@ -150,7 +153,7 @@ class Plugin(BasePlugin):
self.processor.start()
self.obj = QEmailSignalObject()
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):
self.logger.info('received payment request')
@@ -166,8 +169,9 @@ class Plugin(BasePlugin):
self.wallets -= {wallet}
def new_invoice(self):
+ invoice = OnchainInvoice.from_bip70_payreq(self.pr)
for wallet in self.wallets:
- wallet.invoices.add(self.pr)
+ wallet.save_invoice(invoice)
#main_window.invoice_list.update()
@hook
diff --git a/electrum/wallet.py b/electrum/wallet.py
@@ -68,7 +68,7 @@ from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
from .plugin import run_hook
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
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 .contacts import Contacts
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.fiat_value = db.get_dict('fiat_value')
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._prepare_onchain_invoice_paid_detection()
@@ -656,43 +656,33 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
'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):
amount = '!'
else:
amount = sum(x.value for x in outputs)
- outputs = [x.to_legacy_tuple() for x in outputs]
- if pr:
- invoice = OnchainInvoice(
- type = PR_TYPE_ONCHAIN,
- amount = amount,
- outputs = 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(),
- )
- 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,
- )
+ 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
- def save_invoice(self, invoice: Invoice):
+ def save_invoice(self, invoice: Invoice) -> None:
invoice_type = invoice.type
if invoice_type == PR_TYPE_LN:
+ assert isinstance(invoice, LNInvoice)
key = invoice.rhash
elif invoice_type == PR_TYPE_ONCHAIN:
+ assert isinstance(invoice, OnchainInvoice)
key = invoice.id
if self.is_onchain_invoice_paid(invoice):
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]]
for invoice_key, invoice in self.invoices.items():
if invoice.type == PR_TYPE_ONCHAIN:
+ assert isinstance(invoice, OnchainInvoice)
for txout in invoice.outputs:
self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(invoice_key)
def _is_onchain_invoice_paid(self, invoice: Invoice) -> Tuple[bool, Sequence[str]]:
"""Returns whether on-chain invoice is satisfied, and list of relevant TXIDs."""
assert invoice.type == PR_TYPE_ONCHAIN
+ assert isinstance(invoice, OnchainInvoice)
invoice_amounts = defaultdict(int) # type: Dict[bytes, int] # scriptpubkey -> value_sats
for txo in invoice.outputs: # type: PartialTxOutput
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):
invoice = self.invoices.get(invoice_key)
if invoice is None: continue
- assert invoice.get('type') == PR_TYPE_ONCHAIN
- if invoice['message']:
- labels.append(invoice['message'])
+ assert isinstance(invoice, OnchainInvoice)
+ if invoice.message:
+ labels.append(invoice.message)
if labels:
self.set_label(tx_hash, "; ".join(labels))
return bool(labels)
@@ -1610,7 +1602,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
status = PR_EXPIRED
return status
- def get_invoice_status(self, invoice):
+ def get_invoice_status(self, invoice: Invoice):
if invoice.is_lightning():
status = self.lnworker.get_invoice_status(invoice)
else: