Merge pull request #8231 from spesmilo/fix_8213
Refresh bolt11 routing hints when channel liquidity changes:
This commit is contained in:
@@ -124,8 +124,7 @@ class QEAbstractInvoiceListModel(QAbstractListModel):
|
||||
item['address'] = ''
|
||||
item['date'] = format_time(item['timestamp'])
|
||||
item['amount'] = QEAmount(from_invoice=invoice)
|
||||
item['onchain_fallback'] = invoice.is_lightning() and invoice._lnaddr.get_fallback_address()
|
||||
item['type'] = 'invoice'
|
||||
item['onchain_fallback'] = invoice.is_lightning() and invoice.get_address()
|
||||
|
||||
return item
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class QERequestDetails(QObject, QtEventListener):
|
||||
def bolt11(self):
|
||||
can_receive = self._wallet.wallet.lnworker.num_sats_can_receive() if self._wallet.wallet.lnworker else 0
|
||||
if self._req and can_receive > 0 and self._req.amount_msat/1000 <= can_receive:
|
||||
return self._req.lightning_invoice
|
||||
return self._wallet.wallet.get_bolt11_invoice(self._req)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
help_texts = self.wallet.get_help_texts_for_receive_request(req)
|
||||
addr = (req.get_address() or '') if not help_texts.address_is_error else ''
|
||||
URI = (self.wallet.get_request_URI(req) or '') if not help_texts.URI_is_error else ''
|
||||
lnaddr = (req.lightning_invoice or '') if not help_texts.ln_is_error else ''
|
||||
lnaddr = self.wallet.get_bolt11_invoice(req) if not help_texts.ln_is_error else ''
|
||||
address_help = help_texts.address_help
|
||||
URI_help = help_texts.URI_help
|
||||
ln_help = help_texts.ln_help
|
||||
|
||||
@@ -197,7 +197,7 @@ class RequestList(MyTreeView):
|
||||
if URI := self.wallet.get_request_URI(req):
|
||||
copy_menu.addAction(_("Bitcoin URI"), lambda: self.parent.do_copy(URI, title='Bitcoin URI'))
|
||||
if req.is_lightning():
|
||||
copy_menu.addAction(_("Lightning Request"), lambda: self.parent.do_copy(req.lightning_invoice, title='Lightning Request'))
|
||||
copy_menu.addAction(_("Lightning Request"), lambda: self.parent.do_copy(self.wallet.get_bolt11_invoice(req), title='Lightning Request'))
|
||||
#if 'view_url' in req:
|
||||
# menu.addAction(_("View in web browser"), lambda: webopen(req['view_url']))
|
||||
menu.addAction(_("Delete"), lambda: self.delete_requests([key]))
|
||||
|
||||
@@ -7,6 +7,7 @@ import attr
|
||||
from .json_db import StoredObject
|
||||
from .i18n import _
|
||||
from .util import age, InvoiceError
|
||||
from .lnutil import hex_to_bytes
|
||||
from .lnaddr import lndecode, LnAddr
|
||||
from . import constants
|
||||
from .bitcoin import COIN, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC
|
||||
@@ -83,7 +84,11 @@ LN_EXPIRY_NEVER = 100 * 365 * 24 * 60 * 60 # 100 years
|
||||
|
||||
|
||||
@attr.s
|
||||
class Invoice(StoredObject):
|
||||
class BaseInvoice(StoredObject):
|
||||
"""
|
||||
Base class for Invoice and Request
|
||||
In the code, we use 'invoice' for outgoing payments, and 'request' for incoming payments.
|
||||
"""
|
||||
|
||||
# mandatory fields
|
||||
amount_msat = attr.ib(kw_only=True) # type: Optional[Union[int, str]] # can be '!' or None
|
||||
@@ -101,10 +106,6 @@ class Invoice(StoredObject):
|
||||
bip70 = attr.ib(type=str, kw_only=True) # type: Optional[str]
|
||||
#bip70_requestor = attr.ib(type=str, kw_only=True) # type: Optional[str]
|
||||
|
||||
# lightning only
|
||||
lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str]
|
||||
|
||||
__lnaddr = None
|
||||
|
||||
def is_lightning(self):
|
||||
return self.lightning_invoice is not None
|
||||
@@ -117,15 +118,6 @@ class Invoice(StoredObject):
|
||||
status_str = _('Expires') + ' ' + age(expiration, include_seconds=True)
|
||||
return status_str
|
||||
|
||||
def get_address(self) -> Optional[str]:
|
||||
"""returns the first address, to be displayed in GUI"""
|
||||
address = None
|
||||
if self.outputs:
|
||||
address = self.outputs[0].address if len(self.outputs) > 0 else None
|
||||
if not address and self.is_lightning():
|
||||
address = self._lnaddr.get_fallback_address() or None
|
||||
return address
|
||||
|
||||
def get_outputs(self) -> Sequence[PartialTxOutput]:
|
||||
outputs = self.outputs or []
|
||||
if not outputs:
|
||||
@@ -135,12 +127,6 @@ class Invoice(StoredObject):
|
||||
outputs = [PartialTxOutput.from_address_and_value(address, int(amount))]
|
||||
return outputs
|
||||
|
||||
def can_be_paid_onchain(self) -> bool:
|
||||
if self.is_lightning():
|
||||
return bool(self._lnaddr.get_fallback_address())
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_expiration_date(self):
|
||||
# 0 means never
|
||||
return self.exp + self.time if self.exp else 0
|
||||
@@ -193,12 +179,6 @@ class Invoice(StoredObject):
|
||||
uri = create_bip21_uri(addr, amount, message, extra_query_params=extra)
|
||||
return str(uri)
|
||||
|
||||
@lightning_invoice.validator
|
||||
def _validate_invoice_str(self, attribute, value):
|
||||
if value is not None:
|
||||
lnaddr = lndecode(value) # this checks the str can be decoded
|
||||
self.__lnaddr = lnaddr # save it, just to avoid having to recompute later
|
||||
|
||||
@amount_msat.validator
|
||||
def _validate_amount(self, attribute, value):
|
||||
if value is None:
|
||||
@@ -212,16 +192,6 @@ class Invoice(StoredObject):
|
||||
else:
|
||||
raise InvoiceError(f"unexpected amount: {value!r}")
|
||||
|
||||
@property
|
||||
def _lnaddr(self) -> LnAddr:
|
||||
if self.__lnaddr is None:
|
||||
self.__lnaddr = lndecode(self.lightning_invoice)
|
||||
return self.__lnaddr
|
||||
|
||||
@property
|
||||
def rhash(self) -> str:
|
||||
return self._lnaddr.paymenthash.hex()
|
||||
|
||||
@classmethod
|
||||
def from_bech32(cls, invoice: str) -> 'Invoice':
|
||||
"""Constructs Invoice object from BOLT-11 string.
|
||||
@@ -259,6 +229,48 @@ class Invoice(StoredObject):
|
||||
lightning_invoice=None,
|
||||
)
|
||||
|
||||
def get_id(self) -> str:
|
||||
if self.is_lightning():
|
||||
return self.rhash
|
||||
else: # on-chain
|
||||
return get_id_from_onchain_outputs(outputs=self.get_outputs(), timestamp=self.time)
|
||||
|
||||
@attr.s
|
||||
class Invoice(BaseInvoice):
|
||||
lightning_invoice = attr.ib(type=str, kw_only=True) # type: Optional[str]
|
||||
__lnaddr = None
|
||||
|
||||
def get_address(self) -> Optional[str]:
|
||||
"""returns the first address, to be displayed in GUI"""
|
||||
address = None
|
||||
if self.outputs:
|
||||
address = self.outputs[0].address if len(self.outputs) > 0 else None
|
||||
if not address and self.is_lightning():
|
||||
address = self._lnaddr.get_fallback_address() or None
|
||||
return address
|
||||
|
||||
@property
|
||||
def _lnaddr(self) -> LnAddr:
|
||||
if self.__lnaddr is None:
|
||||
self.__lnaddr = lndecode(self.lightning_invoice)
|
||||
return self.__lnaddr
|
||||
|
||||
@property
|
||||
def rhash(self) -> str:
|
||||
return self._lnaddr.paymenthash.hex()
|
||||
|
||||
@lightning_invoice.validator
|
||||
def _validate_invoice_str(self, attribute, value):
|
||||
if value is not None:
|
||||
lnaddr = lndecode(value) # this checks the str can be decoded
|
||||
self.__lnaddr = lnaddr # save it, just to avoid having to recompute later
|
||||
|
||||
def can_be_paid_onchain(self) -> bool:
|
||||
if self.is_lightning():
|
||||
return bool(self._lnaddr.get_fallback_address())
|
||||
else:
|
||||
return True
|
||||
|
||||
def to_debug_json(self) -> Dict[str, Any]:
|
||||
d = self.to_json()
|
||||
d.update({
|
||||
@@ -274,11 +286,24 @@ class Invoice(StoredObject):
|
||||
d['r_tags'] = [str((a.hex(),b.hex(),c,d,e)) for a,b,c,d,e in ln_routing_info[-1]]
|
||||
return d
|
||||
|
||||
def get_id(self) -> str:
|
||||
if self.is_lightning():
|
||||
return self.rhash
|
||||
else: # on-chain
|
||||
return get_id_from_onchain_outputs(outputs=self.get_outputs(), timestamp=self.time)
|
||||
|
||||
@attr.s
|
||||
class Request(BaseInvoice):
|
||||
payment_hash = attr.ib(type=bytes, kw_only=True, converter=hex_to_bytes) # type: Optional[bytes]
|
||||
|
||||
def is_lightning(self):
|
||||
return self.payment_hash is not None
|
||||
|
||||
def get_address(self) -> Optional[str]:
|
||||
"""returns the first address, to be displayed in GUI"""
|
||||
address = None
|
||||
if self.outputs:
|
||||
address = self.outputs[0].address if len(self.outputs) > 0 else None
|
||||
return address
|
||||
|
||||
@property
|
||||
def rhash(self) -> str:
|
||||
return self.payment_hash.hex()
|
||||
|
||||
|
||||
def get_id_from_onchain_outputs(outputs: List[PartialTxOutput], *, timestamp: int) -> str:
|
||||
|
||||
@@ -633,6 +633,7 @@ class LNWallet(LNWorker):
|
||||
self.lnrater: LNRater = None
|
||||
self.payment_info = self.db.get_dict('lightning_payments') # RHASH -> amount, direction, is_paid
|
||||
self.preimages = self.db.get_dict('lightning_preimages') # RHASH -> preimage
|
||||
self._bolt11_cache = {}
|
||||
# note: this sweep_address is only used as fallback; as it might result in address-reuse
|
||||
self.logs = defaultdict(list) # type: Dict[str, List[HtlcLog]] # key is RHASH # (not persisted)
|
||||
# used in tests
|
||||
@@ -980,6 +981,7 @@ class LNWallet(LNWorker):
|
||||
def channel_state_changed(self, chan: Channel):
|
||||
if type(chan) is Channel:
|
||||
self.save_channel(chan)
|
||||
self.clear_invoices_cache()
|
||||
util.trigger_callback('channel', self.wallet, chan)
|
||||
|
||||
def save_channel(self, chan: Channel):
|
||||
@@ -1781,27 +1783,31 @@ class LNWallet(LNWorker):
|
||||
route[-1].node_features |= invoice_features
|
||||
return route
|
||||
|
||||
def create_invoice(
|
||||
def clear_invoices_cache(self):
|
||||
self._bolt11_cache.clear()
|
||||
|
||||
def get_bolt11_invoice(
|
||||
self, *,
|
||||
payment_hash: bytes,
|
||||
amount_msat: Optional[int],
|
||||
message: str,
|
||||
expiry: int,
|
||||
fallback_address: Optional[str],
|
||||
write_to_disk: bool = True,
|
||||
channels: Optional[Sequence[Channel]] = None,
|
||||
) -> Tuple[LnAddr, str]:
|
||||
|
||||
pair = self._bolt11_cache.get(payment_hash)
|
||||
if pair:
|
||||
lnaddr, invoice = pair
|
||||
assert lnaddr.get_amount_msat() == amount_msat
|
||||
return pair
|
||||
|
||||
assert amount_msat is None or amount_msat > 0
|
||||
timestamp = int(time.time())
|
||||
routing_hints, trampoline_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels)
|
||||
if not routing_hints:
|
||||
self.logger.info(
|
||||
"Warning. No routing hints added to invoice. "
|
||||
"Other clients will likely not be able to send to us.")
|
||||
self.logger.info(f"creating bolt11 invoice with routing_hints: {routing_hints}")
|
||||
invoice_features = self.features.for_invoice()
|
||||
payment_preimage = os.urandom(32)
|
||||
payment_hash = sha256(payment_preimage)
|
||||
info = PaymentInfo(payment_hash, amount_msat, RECEIVED, PR_UNPAID)
|
||||
payment_preimage = self.get_preimage(payment_hash)
|
||||
amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None
|
||||
if expiry == 0:
|
||||
expiry = LN_EXPIRY_NEVER
|
||||
@@ -1820,30 +1826,20 @@ class LNWallet(LNWorker):
|
||||
date=timestamp,
|
||||
payment_secret=derive_payment_secret_from_payment_preimage(payment_preimage))
|
||||
invoice = lnencode(lnaddr, self.node_keypair.privkey)
|
||||
pair = lnaddr, invoice
|
||||
self._bolt11_cache[payment_hash] = pair
|
||||
return pair
|
||||
|
||||
def create_payment_info(self, amount_sat: Optional[int], write_to_disk=True) -> bytes:
|
||||
amount_msat = amount_sat * 1000 if amount_sat else None
|
||||
payment_preimage = os.urandom(32)
|
||||
payment_hash = sha256(payment_preimage)
|
||||
info = PaymentInfo(payment_hash, amount_msat, RECEIVED, PR_UNPAID)
|
||||
self.save_preimage(payment_hash, payment_preimage, write_to_disk=False)
|
||||
self.save_payment_info(info, write_to_disk=False)
|
||||
if write_to_disk:
|
||||
self.wallet.save_db()
|
||||
return lnaddr, invoice
|
||||
|
||||
def add_request(
|
||||
self,
|
||||
*,
|
||||
amount_sat: Optional[int],
|
||||
message: str,
|
||||
expiry: int,
|
||||
fallback_address: Optional[str],
|
||||
) -> str:
|
||||
# passed expiry is relative, it is absolute in the lightning invoice
|
||||
amount_msat = amount_sat * 1000 if amount_sat else None
|
||||
lnaddr, invoice = self.create_invoice(
|
||||
amount_msat=amount_msat,
|
||||
message=message,
|
||||
expiry=expiry,
|
||||
fallback_address=fallback_address,
|
||||
write_to_disk=False,
|
||||
)
|
||||
return invoice
|
||||
return payment_hash
|
||||
|
||||
def save_preimage(self, payment_hash: bytes, preimage: bytes, *, write_to_disk: bool = True):
|
||||
assert sha256(preimage) == payment_hash
|
||||
@@ -1853,7 +1849,7 @@ class LNWallet(LNWorker):
|
||||
|
||||
def get_preimage(self, payment_hash: bytes) -> Optional[bytes]:
|
||||
r = self.preimages.get(payment_hash.hex())
|
||||
return bfh(r) if r else None
|
||||
return bytes.fromhex(r) if r else None
|
||||
|
||||
def get_payment_info(self, payment_hash: bytes) -> Optional[PaymentInfo]:
|
||||
"""returns None if payment_hash is a payment we are forwarding"""
|
||||
@@ -1922,6 +1918,8 @@ class LNWallet(LNWorker):
|
||||
self.set_payment_status(bfh(key), status)
|
||||
util.trigger_callback('invoice_status', self.wallet, key, status)
|
||||
self.logger.info(f"invoice status triggered (2) for key {key} and status {status}")
|
||||
# liquidity changed
|
||||
self.clear_invoices_cache()
|
||||
|
||||
def set_request_status(self, payment_hash: bytes, status: int) -> None:
|
||||
if self.get_payment_status(payment_hash) == status:
|
||||
@@ -2138,7 +2136,7 @@ class LNWallet(LNWorker):
|
||||
channels = list(self.channels.values())
|
||||
# we exclude channels that cannot *right now* receive (e.g. peer offline)
|
||||
channels = [chan for chan in channels
|
||||
if (chan.is_active() and not chan.is_frozen_for_receiving())]
|
||||
if (chan.is_open() and not chan.is_frozen_for_receiving())]
|
||||
# Filter out nodes that have low receive capacity compared to invoice amt.
|
||||
# Even with MPP, below a certain threshold, including these channels probably
|
||||
# hurts more than help, as they lead to many failed attempts for the sender.
|
||||
@@ -2283,7 +2281,7 @@ class LNWallet(LNWorker):
|
||||
raise Exception('Rebalance requires two different channels')
|
||||
if self.uses_trampoline() and chan1.node_id == chan2.node_id:
|
||||
raise Exception('Rebalance requires channels from different trampolines')
|
||||
lnaddr, invoice = self.create_invoice(
|
||||
lnaddr, invoice = self.add_reqest(
|
||||
amount_msat=amount_msat,
|
||||
message='rebalance',
|
||||
expiry=3600,
|
||||
|
||||
@@ -261,14 +261,16 @@ class SwapManager(Logger):
|
||||
assert self.lnwatcher
|
||||
privkey = os.urandom(32)
|
||||
pubkey = ECPrivkey(privkey).get_public_key_bytes(compressed=True)
|
||||
lnaddr, invoice = self.lnworker.create_invoice(
|
||||
amount_msat=lightning_amount_sat * 1000,
|
||||
amount_msat = lightning_amount_sat * 1000
|
||||
payment_hash = self.lnworker.create_payment_info(lightning_amount_sat)
|
||||
lnaddr, invoice = self.lnworker.get_bolt11_invoice(
|
||||
payment_hash=payment_hash,
|
||||
amount_msat=amount_msat,
|
||||
message='swap',
|
||||
expiry=3600 * 24,
|
||||
fallback_address=None,
|
||||
channels=channels,
|
||||
)
|
||||
payment_hash = lnaddr.paymenthash
|
||||
preimage = self.lnworker.get_preimage(payment_hash)
|
||||
request_data = {
|
||||
"type": "submarine",
|
||||
|
||||
@@ -5,7 +5,7 @@ from . import ElectrumTestCase
|
||||
|
||||
from electrum.simple_config import SimpleConfig
|
||||
from electrum.wallet import restore_wallet_from_text, Standard_Wallet, Abstract_Wallet
|
||||
from electrum.invoices import PR_UNPAID, PR_PAID, PR_UNCONFIRMED, Invoice
|
||||
from electrum.invoices import PR_UNPAID, PR_PAID, PR_UNCONFIRMED, BaseInvoice
|
||||
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED
|
||||
from electrum.transaction import Transaction, PartialTxOutput
|
||||
from electrum.util import TxMinedInfo
|
||||
@@ -20,11 +20,11 @@ class TestWalletPaymentRequests(ElectrumTestCase):
|
||||
self.config = SimpleConfig({'electrum_path': self.electrum_path})
|
||||
self.wallet1_path = os.path.join(self.electrum_path, "somewallet1")
|
||||
self.wallet2_path = os.path.join(self.electrum_path, "somewallet2")
|
||||
self._orig_get_cur_time = Invoice._get_cur_time
|
||||
self._orig_get_cur_time = BaseInvoice._get_cur_time
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
Invoice._get_cur_time = staticmethod(self._orig_get_cur_time)
|
||||
BaseInvoice._get_cur_time = staticmethod(self._orig_get_cur_time)
|
||||
|
||||
def create_wallet2(self) -> Standard_Wallet:
|
||||
text = 'cross end slow expose giraffe fuel track awake turtle capital ranch pulp'
|
||||
@@ -156,8 +156,6 @@ class TestWalletPaymentRequests(ElectrumTestCase):
|
||||
self.assertTrue(pr1.is_lightning())
|
||||
self.assertEqual(PR_UNPAID, wallet1.get_invoice_status(pr1))
|
||||
self.assertEqual(addr1, pr1.get_address())
|
||||
self.assertEqual(addr1, pr1._lnaddr.get_fallback_address())
|
||||
self.assertTrue(pr1.can_be_paid_onchain())
|
||||
self.assertFalse(pr1.has_expired())
|
||||
|
||||
# create payreq2
|
||||
@@ -213,7 +211,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
|
||||
self.assertEqual(addr1, pr1.get_address())
|
||||
self.assertFalse(pr1.has_expired())
|
||||
|
||||
Invoice._get_cur_time = lambda *args: time.time() + 100_000
|
||||
BaseInvoice._get_cur_time = lambda *args: time.time() + 100_000
|
||||
self.assertTrue(pr1.has_expired())
|
||||
|
||||
# create payreq2
|
||||
@@ -240,7 +238,7 @@ class TestWalletPaymentRequests(ElectrumTestCase):
|
||||
self.assertFalse(pr1.has_expired())
|
||||
self.assertEqual(pr1, wallet1.get_request_by_addr(addr1))
|
||||
|
||||
Invoice._get_cur_time = lambda *args: time.time() + 100_000
|
||||
BaseInvoice._get_cur_time = lambda *args: time.time() + 100_000
|
||||
self.assertTrue(pr1.has_expired())
|
||||
self.assertEqual(None, wallet1.get_request_by_addr(addr1))
|
||||
|
||||
@@ -265,6 +263,6 @@ class TestWalletPaymentRequests(ElectrumTestCase):
|
||||
self.assertEqual(PR_UNCONFIRMED, wallet1.get_invoice_status(pr1))
|
||||
|
||||
# now make both invoices be past their expiration date. pr2 should be unaffected.
|
||||
Invoice._get_cur_time = lambda *args: time.time() + 200_000
|
||||
BaseInvoice._get_cur_time = lambda *args: time.time() + 200_000
|
||||
self.assertEqual(PR_UNCONFIRMED, wallet1.get_invoice_status(pr2))
|
||||
self.assertEqual(pr2, wallet1.get_request_by_addr(addr1))
|
||||
|
||||
@@ -175,6 +175,9 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
|
||||
self.logger.info(f"created LNWallet[{name}] with nodeID={local_keypair.pubkey.hex()}")
|
||||
|
||||
def clear_invoices_cache(self):
|
||||
pass
|
||||
|
||||
def pay_scheduled_invoices(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -74,7 +74,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
|
||||
from .invoices import Invoice, Request
|
||||
from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED
|
||||
from .contacts import Contacts
|
||||
from .interface import NetworkException
|
||||
@@ -2443,7 +2443,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
}
|
||||
if is_lightning:
|
||||
d['rhash'] = x.rhash
|
||||
d['lightning_invoice'] = x.lightning_invoice
|
||||
d['lightning_invoice'] = self.get_bolt11_invoice(x)
|
||||
d['amount_msat'] = x.get_amount_msat()
|
||||
if self.lnworker and status == PR_UNPAID:
|
||||
d['can_receive'] = self.lnworker.can_receive_invoice(x)
|
||||
@@ -2477,7 +2477,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
'invoice_id': key,
|
||||
}
|
||||
if is_lightning:
|
||||
d['lightning_invoice'] = x.lightning_invoice
|
||||
d['lightning_invoice'] = self.get_bolt11_invoice(x)
|
||||
d['amount_msat'] = x.get_amount_msat()
|
||||
if self.lnworker and status == PR_UNPAID:
|
||||
d['can_pay'] = self.lnworker.can_pay_invoice(x)
|
||||
@@ -2508,6 +2508,18 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
relevant_invoice_keys.add(invoice_key)
|
||||
self._update_onchain_invoice_paid_detection(relevant_invoice_keys)
|
||||
|
||||
def get_bolt11_invoice(self, req: Request) -> str:
|
||||
if not self.lnworker:
|
||||
return ''
|
||||
amount_msat = req.amount_msat if req.amount_msat > 0 else None
|
||||
lnaddr, invoice = self.lnworker.get_bolt11_invoice(
|
||||
payment_hash=req.payment_hash,
|
||||
amount_msat=amount_msat,
|
||||
message=req.message,
|
||||
expiry=req.exp,
|
||||
fallback_address=req.get_address() if self.config.get('bolt11_fallback', True) else None)
|
||||
return invoice
|
||||
|
||||
def create_request(self, amount_sat: int, message: str, exp_delay: int, address: Optional[str]):
|
||||
# for receiving
|
||||
amount_sat = amount_sat or 0
|
||||
@@ -2515,21 +2527,11 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
message = message or ''
|
||||
address = address or None # converts "" to None
|
||||
exp_delay = exp_delay or 0
|
||||
timestamp = int(Invoice._get_cur_time())
|
||||
fallback_address = address if self.config.get('bolt11_fallback', True) else None
|
||||
lightning = self.has_lightning()
|
||||
if lightning:
|
||||
lightning_invoice = self.lnworker.add_request(
|
||||
amount_sat=amount_sat,
|
||||
message=message,
|
||||
expiry=exp_delay,
|
||||
fallback_address=fallback_address,
|
||||
)
|
||||
else:
|
||||
lightning_invoice = None
|
||||
timestamp = int(Request._get_cur_time())
|
||||
payment_hash = self.lnworker.create_payment_info(amount_sat, write_to_disk=False) if self.has_lightning() else None
|
||||
outputs = [ PartialTxOutput.from_address_and_value(address, amount_sat)] if address else []
|
||||
height = self.adb.get_local_height()
|
||||
req = Invoice(
|
||||
req = Request(
|
||||
outputs=outputs,
|
||||
message=message,
|
||||
time=timestamp,
|
||||
@@ -2537,7 +2539,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
exp=exp_delay,
|
||||
height=height,
|
||||
bip70=None,
|
||||
lightning_invoice=lightning_invoice,
|
||||
payment_hash=payment_hash,
|
||||
)
|
||||
key = self.add_payment_request(req)
|
||||
return key
|
||||
@@ -2858,7 +2860,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
ln_is_error = False
|
||||
ln_swap_suggestion = None
|
||||
ln_rebalance_suggestion = None
|
||||
lnaddr = req.lightning_invoice or ''
|
||||
URI = self.get_request_URI(req) or ''
|
||||
lightning_online = self.lnworker and self.lnworker.num_peers() > 0
|
||||
can_receive_lightning = self.lnworker and amount_sat <= self.lnworker.num_sats_can_receive()
|
||||
@@ -2878,7 +2879,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
URI_help = _('This request cannot be paid on-chain')
|
||||
if is_amt_too_small_for_onchain:
|
||||
URI_help = _('Amount too small to be received onchain')
|
||||
if not lnaddr:
|
||||
if not req.is_lightning():
|
||||
ln_is_error = True
|
||||
ln_help = _('This request does not have a Lightning invoice.')
|
||||
|
||||
@@ -2886,7 +2887,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
if self.adb.is_used(addr):
|
||||
address_help = URI_help = (_("This address has already been used. "
|
||||
"For better privacy, do not reuse it for new payments."))
|
||||
if lnaddr:
|
||||
if req.is_lightning():
|
||||
if not lightning_online:
|
||||
ln_is_error = True
|
||||
ln_help = _('You must be online to receive Lightning payments.')
|
||||
|
||||
@@ -33,7 +33,7 @@ import binascii
|
||||
|
||||
from . import util, bitcoin
|
||||
from .util import profiler, WalletFileException, multisig_type, TxMinedInfo, bfh
|
||||
from .invoices import Invoice
|
||||
from .invoices import Invoice, Request
|
||||
from .keystore import bip44_derivation
|
||||
from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput
|
||||
from .logging import Logger
|
||||
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
|
||||
|
||||
OLD_SEED_VERSION = 4 # electrum versions < 2.0
|
||||
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
|
||||
FINAL_SEED_VERSION = 50 # electrum >= 2.7 will set this to prevent
|
||||
FINAL_SEED_VERSION = 51 # electrum >= 2.7 will set this to prevent
|
||||
# old versions from overwriting new format
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ class WalletDB(JsonDB):
|
||||
self._convert_version_48()
|
||||
self._convert_version_49()
|
||||
self._convert_version_50()
|
||||
self._convert_version_51()
|
||||
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
|
||||
|
||||
self._after_upgrade_tasks()
|
||||
@@ -980,6 +981,21 @@ class WalletDB(JsonDB):
|
||||
self._convert_invoices_keys(requests)
|
||||
self.data['seed_version'] = 50
|
||||
|
||||
def _convert_version_51(self):
|
||||
from .lnaddr import lndecode
|
||||
if not self._is_upgrade_method_needed(50, 50):
|
||||
return
|
||||
requests = self.data.get('payment_requests', {})
|
||||
for key, item in list(requests.items()):
|
||||
lightning_invoice = item.pop('lightning_invoice')
|
||||
if lightning_invoice is None:
|
||||
payment_hash = None
|
||||
else:
|
||||
lnaddr = lndecode(lightning_invoice)
|
||||
payment_hash = lnaddr.paymenthash.hex()
|
||||
item['payment_hash'] = payment_hash
|
||||
self.data['seed_version'] = 51
|
||||
|
||||
def _convert_imported(self):
|
||||
if not self._is_upgrade_method_needed(0, 13):
|
||||
return
|
||||
@@ -1460,7 +1476,7 @@ class WalletDB(JsonDB):
|
||||
if key == 'invoices':
|
||||
v = dict((k, Invoice(**x)) for k, x in v.items())
|
||||
if key == 'payment_requests':
|
||||
v = dict((k, Invoice(**x)) for k, x in v.items())
|
||||
v = dict((k, Request(**x)) for k, x in v.items())
|
||||
elif key == 'adds':
|
||||
v = dict((k, UpdateAddHtlc.from_tuple(*x)) for k, x in v.items())
|
||||
elif key == 'fee_updates':
|
||||
|
||||
Reference in New Issue
Block a user