Browse Source

invoices: follow-up fixes re clean-up

follow-up 6058829870 and related
master
SomberNight 4 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)
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()

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):
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

33
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(

8
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

58
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:

Loading…
Cancel
Save