mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-28 07:51:27 +00:00
Refactor invoices in lnworker.
- use InvoiceInfo (NamedTuple) for normal operations,
because lndecode operations can be very slow.
- all invoices/requests are stored in wallet
- invoice expiration detection is performed in wallet
- CLI commands: list_invoices, add_request, add_lightning_request
- revert 0062c6d695
because it forbids self-payments
This commit is contained in:
parent
0a395fefbc
commit
f08e5541ae
9 changed files with 214 additions and 244 deletions
|
@ -716,7 +716,7 @@ class Commands:
|
||||||
def _format_request(self, out):
|
def _format_request(self, out):
|
||||||
from .util import get_request_status
|
from .util import get_request_status
|
||||||
out['amount_BTC'] = format_satoshis(out.get('amount'))
|
out['amount_BTC'] = format_satoshis(out.get('amount'))
|
||||||
out['status'] = get_request_status(out)
|
out['status_str'] = get_request_status(out)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
|
@ -733,7 +733,7 @@ class Commands:
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def listrequests(self, pending=False, expired=False, paid=False, wallet: Abstract_Wallet = None):
|
async def list_requests(self, pending=False, expired=False, paid=False, wallet: Abstract_Wallet = None):
|
||||||
"""List the payment requests you made."""
|
"""List the payment requests you made."""
|
||||||
out = wallet.get_sorted_requests()
|
out = wallet.get_sorted_requests()
|
||||||
if pending:
|
if pending:
|
||||||
|
@ -760,7 +760,7 @@ class Commands:
|
||||||
return wallet.get_unused_address()
|
return wallet.get_unused_address()
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def addrequest(self, amount, memo='', expiration=None, force=False, wallet: Abstract_Wallet = None):
|
async def add_request(self, amount, memo='', expiration=3600, force=False, wallet: Abstract_Wallet = None):
|
||||||
"""Create a payment request, using the first unused address of the wallet.
|
"""Create a payment request, using the first unused address of the wallet.
|
||||||
The address will be considered as used after this operation.
|
The address will be considered as used after this operation.
|
||||||
If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
|
If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
|
||||||
|
@ -777,6 +777,12 @@ class Commands:
|
||||||
out = wallet.get_request(addr)
|
out = wallet.get_request(addr)
|
||||||
return self._format_request(out)
|
return self._format_request(out)
|
||||||
|
|
||||||
|
@command('wn')
|
||||||
|
async def add_lightning_request(self, amount, memo='', expiration=3600, wallet: Abstract_Wallet = None):
|
||||||
|
amount_sat = int(satoshis(amount))
|
||||||
|
key = await wallet.lnworker._add_request_coro(amount_sat, memo, expiration)
|
||||||
|
return wallet.get_request(key)['invoice']
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def addtransaction(self, tx, wallet: Abstract_Wallet = None):
|
async def addtransaction(self, tx, wallet: Abstract_Wallet = None):
|
||||||
""" Add a transaction to the wallet history """
|
""" Add a transaction to the wallet history """
|
||||||
|
@ -894,13 +900,6 @@ class Commands:
|
||||||
async def lnpay(self, invoice, attempts=1, timeout=10, wallet: Abstract_Wallet = None):
|
async def lnpay(self, invoice, attempts=1, timeout=10, wallet: Abstract_Wallet = None):
|
||||||
return await wallet.lnworker._pay(invoice, attempts=attempts)
|
return await wallet.lnworker._pay(invoice, attempts=attempts)
|
||||||
|
|
||||||
@command('wn')
|
|
||||||
async def addinvoice(self, requested_amount, message, expiration=3600, wallet: Abstract_Wallet = None):
|
|
||||||
# using requested_amount because it is documented in param_descriptions
|
|
||||||
payment_hash = await wallet.lnworker._add_invoice_coro(satoshis(requested_amount), message, expiration)
|
|
||||||
invoice, direction, is_paid = wallet.lnworker.invoices[bh2u(payment_hash)]
|
|
||||||
return invoice
|
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def nodeid(self, wallet: Abstract_Wallet = None):
|
async def nodeid(self, wallet: Abstract_Wallet = None):
|
||||||
listen_addr = self.config.get('lightning_listen')
|
listen_addr = self.config.get('lightning_listen')
|
||||||
|
@ -925,21 +924,8 @@ class Commands:
|
||||||
self.network.path_finder.blacklist.clear()
|
self.network.path_finder.blacklist.clear()
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def lightning_invoices(self, wallet: Abstract_Wallet = None):
|
async def list_invoices(self, wallet: Abstract_Wallet = None):
|
||||||
from .util import pr_tooltips
|
return wallet.get_invoices()
|
||||||
out = []
|
|
||||||
for payment_hash, (preimage, invoice, is_received, timestamp) in wallet.lnworker.invoices.items():
|
|
||||||
status = wallet.lnworker.get_invoice_status(payment_hash)
|
|
||||||
item = {
|
|
||||||
'date':timestamp_to_datetime(timestamp),
|
|
||||||
'direction': 'received' if is_received else 'sent',
|
|
||||||
'payment_hash':payment_hash,
|
|
||||||
'invoice':invoice,
|
|
||||||
'preimage':preimage,
|
|
||||||
'status':pr_tooltips[status]
|
|
||||||
}
|
|
||||||
out.append(item)
|
|
||||||
return out
|
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
async def lightning_history(self, wallet: Abstract_Wallet = None):
|
async def lightning_history(self, wallet: Abstract_Wallet = None):
|
||||||
|
|
|
@ -428,14 +428,11 @@ class ElectrumWindow(App):
|
||||||
|
|
||||||
def show_request(self, is_lightning, key):
|
def show_request(self, is_lightning, key):
|
||||||
from .uix.dialogs.request_dialog import RequestDialog
|
from .uix.dialogs.request_dialog import RequestDialog
|
||||||
if is_lightning:
|
request = self.wallet.get_request(key)
|
||||||
request, direction, is_paid = self.wallet.lnworker.invoices.get(key) or (None, None, None)
|
status = request['status']
|
||||||
status = self.wallet.lnworker.get_invoice_status(key)
|
data = request['invoice'] if is_lightning else request['URI']
|
||||||
else:
|
self.request_popup = RequestDialog('Request', data, key)
|
||||||
request = self.wallet.get_request_URI(key)
|
self.request_popup.set_status(request['status'])
|
||||||
status, conf = self.wallet.get_request_status(key)
|
|
||||||
self.request_popup = RequestDialog('Request', request, key)
|
|
||||||
self.request_popup.set_status(status)
|
|
||||||
self.request_popup.open()
|
self.request_popup.open()
|
||||||
|
|
||||||
def show_invoice(self, is_lightning, key):
|
def show_invoice(self, is_lightning, key):
|
||||||
|
@ -444,10 +441,7 @@ class ElectrumWindow(App):
|
||||||
if not invoice:
|
if not invoice:
|
||||||
return
|
return
|
||||||
status = invoice['status']
|
status = invoice['status']
|
||||||
if is_lightning:
|
data = invoice['invoice'] if is_lightning else key
|
||||||
data = invoice['invoice']
|
|
||||||
else:
|
|
||||||
data = key
|
|
||||||
self.invoice_popup = InvoiceDialog('Invoice', data, key)
|
self.invoice_popup = InvoiceDialog('Invoice', data, key)
|
||||||
self.invoice_popup.open()
|
self.invoice_popup.open()
|
||||||
|
|
||||||
|
|
|
@ -304,12 +304,7 @@ class SendScreen(CScreen):
|
||||||
return
|
return
|
||||||
message = self.screen.message
|
message = self.screen.message
|
||||||
if self.screen.is_lightning:
|
if self.screen.is_lightning:
|
||||||
return {
|
return self.app.wallet.lnworker.parse_bech32_invoice(address)
|
||||||
'type': PR_TYPE_LN,
|
|
||||||
'invoice': address,
|
|
||||||
'amount': amount,
|
|
||||||
'message': message,
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
if not bitcoin.is_address(address):
|
if not bitcoin.is_address(address):
|
||||||
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
|
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
|
||||||
|
|
|
@ -1073,8 +1073,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||||
message = self.receive_message_e.text()
|
message = self.receive_message_e.text()
|
||||||
expiry = self.config.get('request_expiry', 3600)
|
expiry = self.config.get('request_expiry', 3600)
|
||||||
if is_lightning:
|
if is_lightning:
|
||||||
payment_hash = self.wallet.lnworker.add_invoice(amount, message, expiry)
|
key = self.wallet.lnworker.add_request(amount, message, expiry)
|
||||||
key = bh2u(payment_hash)
|
|
||||||
else:
|
else:
|
||||||
key = self.create_bitcoin_request(amount, message, expiry)
|
key = self.create_bitcoin_request(amount, message, expiry)
|
||||||
self.address_list.update()
|
self.address_list.update()
|
||||||
|
@ -1698,12 +1697,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||||
message = self.message_e.text()
|
message = self.message_e.text()
|
||||||
amount = self.amount_e.get_amount()
|
amount = self.amount_e.get_amount()
|
||||||
if not self.is_onchain:
|
if not self.is_onchain:
|
||||||
return {
|
return self.wallet.lnworker.parse_bech32_invoice(self.payto_e.lightning_invoice)
|
||||||
'type': PR_TYPE_LN,
|
|
||||||
'invoice': self.payto_e.lightning_invoice,
|
|
||||||
'amount': amount,
|
|
||||||
'message': message,
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
outputs = self.read_outputs()
|
outputs = self.read_outputs()
|
||||||
if self.check_send_tab_outputs_and_show_errors(outputs):
|
if self.check_send_tab_outputs_and_show_errors(outputs):
|
||||||
|
@ -1733,7 +1727,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||||
|
|
||||||
def do_pay_invoice(self, invoice, preview=False):
|
def do_pay_invoice(self, invoice, preview=False):
|
||||||
if invoice['type'] == PR_TYPE_LN:
|
if invoice['type'] == PR_TYPE_LN:
|
||||||
self.pay_lightning_invoice(self.payto_e.lightning_invoice)
|
self.pay_lightning_invoice(invoice['invoice'])
|
||||||
return
|
return
|
||||||
elif invoice['type'] == PR_TYPE_ONCHAIN:
|
elif invoice['type'] == PR_TYPE_ONCHAIN:
|
||||||
message = invoice['message']
|
message = invoice['message']
|
||||||
|
|
|
@ -1402,13 +1402,13 @@ class Peer(Logger):
|
||||||
await self.await_local(chan, local_ctn)
|
await self.await_local(chan, local_ctn)
|
||||||
await self.await_remote(chan, remote_ctn)
|
await self.await_remote(chan, remote_ctn)
|
||||||
try:
|
try:
|
||||||
invoice = self.lnworker.get_invoice(htlc.payment_hash)
|
info = self.lnworker.get_invoice_info(htlc.payment_hash)
|
||||||
preimage = self.lnworker.get_preimage(htlc.payment_hash)
|
preimage = self.lnworker.get_preimage(htlc.payment_hash)
|
||||||
except UnknownPaymentHash:
|
except UnknownPaymentHash:
|
||||||
reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
||||||
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
|
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
|
||||||
return
|
return
|
||||||
expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000) if invoice.amount is not None else None
|
expected_received_msat = int(info.amount * 1000) if info.amount is not None else None
|
||||||
if expected_received_msat is not None and \
|
if expected_received_msat is not None and \
|
||||||
not (expected_received_msat <= htlc.amount_msat <= 2 * expected_received_msat):
|
not (expected_received_msat <= htlc.amount_msat <= 2 * expected_received_msat):
|
||||||
reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'')
|
||||||
|
@ -1431,7 +1431,7 @@ class Peer(Logger):
|
||||||
data=htlc.amount_msat.to_bytes(8, byteorder="big"))
|
data=htlc.amount_msat.to_bytes(8, byteorder="big"))
|
||||||
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
|
await self.fail_htlc(chan, htlc.htlc_id, onion_packet, reason)
|
||||||
return
|
return
|
||||||
self.network.trigger_callback('htlc_added', htlc, invoice, RECEIVED)
|
#self.network.trigger_callback('htlc_added', htlc, invoice, RECEIVED)
|
||||||
await asyncio.sleep(self.network.config.lightning_settle_delay)
|
await asyncio.sleep(self.network.config.lightning_settle_delay)
|
||||||
await self._fulfill_htlc(chan, htlc.htlc_id, preimage)
|
await self._fulfill_htlc(chan, htlc.htlc_id, preimage)
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,16 @@ FALLBACK_NODE_LIST_MAINNET = [
|
||||||
|
|
||||||
encoder = ChannelJsonEncoder()
|
encoder = ChannelJsonEncoder()
|
||||||
|
|
||||||
|
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
class InvoiceInfo(NamedTuple):
|
||||||
|
payment_hash: bytes
|
||||||
|
amount: int
|
||||||
|
direction: int
|
||||||
|
status: int
|
||||||
|
|
||||||
|
|
||||||
class LNWorker(Logger):
|
class LNWorker(Logger):
|
||||||
|
|
||||||
def __init__(self, xprv):
|
def __init__(self, xprv):
|
||||||
|
@ -313,7 +323,7 @@ class LNWallet(LNWorker):
|
||||||
LNWorker.__init__(self, xprv)
|
LNWorker.__init__(self, xprv)
|
||||||
self.ln_keystore = keystore.from_xprv(xprv)
|
self.ln_keystore = keystore.from_xprv(xprv)
|
||||||
self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
|
self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
|
||||||
self.invoices = self.storage.get('lightning_invoices', {}) # RHASH -> (invoice, direction, is_paid)
|
self.invoices = self.storage.get('lightning_invoices2', {}) # RHASH -> amount, direction, is_paid
|
||||||
self.preimages = self.storage.get('lightning_preimages', {}) # RHASH -> preimage
|
self.preimages = self.storage.get('lightning_preimages', {}) # RHASH -> preimage
|
||||||
self.sweep_address = wallet.get_receiving_address()
|
self.sweep_address = wallet.get_receiving_address()
|
||||||
self.lock = threading.RLock()
|
self.lock = threading.RLock()
|
||||||
|
@ -409,16 +419,6 @@ class LNWallet(LNWorker):
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
self.network.trigger_callback('ln_payment_completed', timestamp, direction, htlc, preimage, chan_id)
|
self.network.trigger_callback('ln_payment_completed', timestamp, direction, htlc, preimage, chan_id)
|
||||||
|
|
||||||
def get_invoice_status(self, key):
|
|
||||||
if key not in self.invoices:
|
|
||||||
return PR_UNKNOWN
|
|
||||||
invoice, direction, status = self.invoices[key]
|
|
||||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
|
||||||
if status == PR_UNPAID and lnaddr.is_expired():
|
|
||||||
return PR_EXPIRED
|
|
||||||
else:
|
|
||||||
return status
|
|
||||||
|
|
||||||
def get_payments(self):
|
def get_payments(self):
|
||||||
# return one item per payment_hash
|
# return one item per payment_hash
|
||||||
# note: with AMP we will have several channels per payment
|
# note: with AMP we will have several channels per payment
|
||||||
|
@ -431,6 +431,20 @@ class LNWallet(LNWorker):
|
||||||
out[k].append(v)
|
out[k].append(v)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def parse_bech32_invoice(self, invoice):
|
||||||
|
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||||
|
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None
|
||||||
|
return {
|
||||||
|
'type': PR_TYPE_LN,
|
||||||
|
'invoice': invoice,
|
||||||
|
'amount': amount,
|
||||||
|
'message': lnaddr.get_description(),
|
||||||
|
'time': lnaddr.date,
|
||||||
|
'exp': lnaddr.get_expiry(),
|
||||||
|
'pubkey': bh2u(lnaddr.pubkey.serialize()),
|
||||||
|
'rhash': lnaddr.paymenthash.hex(),
|
||||||
|
}
|
||||||
|
|
||||||
def get_unsettled_payments(self):
|
def get_unsettled_payments(self):
|
||||||
out = []
|
out = []
|
||||||
for payment_hash, plist in self.get_payments().items():
|
for payment_hash, plist in self.get_payments().items():
|
||||||
|
@ -455,7 +469,7 @@ class LNWallet(LNWorker):
|
||||||
|
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
out = []
|
out = []
|
||||||
for payment_hash, plist in self.get_payments().items():
|
for key, plist in self.get_payments().items():
|
||||||
plist = list(filter(lambda x: x[3] == 'settled', plist))
|
plist = list(filter(lambda x: x[3] == 'settled', plist))
|
||||||
if len(plist) == 0:
|
if len(plist) == 0:
|
||||||
continue
|
continue
|
||||||
|
@ -464,11 +478,13 @@ class LNWallet(LNWorker):
|
||||||
direction = 'sent' if _direction == SENT else 'received'
|
direction = 'sent' if _direction == SENT else 'received'
|
||||||
amount_msat = int(_direction) * htlc.amount_msat
|
amount_msat = int(_direction) * htlc.amount_msat
|
||||||
timestamp = htlc.timestamp
|
timestamp = htlc.timestamp
|
||||||
label = self.wallet.get_label(payment_hash)
|
label = self.wallet.get_label(key)
|
||||||
req = self.get_request(payment_hash)
|
if _direction == SENT:
|
||||||
if req and _direction == SENT:
|
try:
|
||||||
req_amount_msat = -req['amount']*1000
|
inv = self.get_invoice_info(bfh(key))
|
||||||
fee_msat = req_amount_msat - amount_msat
|
fee_msat = inv.amount*1000 - amount_msat if inv.amount else None
|
||||||
|
except UnknownPaymentHash:
|
||||||
|
fee_msat = None
|
||||||
else:
|
else:
|
||||||
fee_msat = None
|
fee_msat = None
|
||||||
else:
|
else:
|
||||||
|
@ -489,7 +505,7 @@ class LNWallet(LNWorker):
|
||||||
'status': status,
|
'status': status,
|
||||||
'amount_msat': amount_msat,
|
'amount_msat': amount_msat,
|
||||||
'fee_msat': fee_msat,
|
'fee_msat': fee_msat,
|
||||||
'payment_hash': payment_hash
|
'payment_hash': key,
|
||||||
}
|
}
|
||||||
out.append(item)
|
out.append(item)
|
||||||
# add funding events
|
# add funding events
|
||||||
|
@ -831,20 +847,23 @@ class LNWallet(LNWorker):
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def _pay(self, invoice, amount_sat=None, attempts=1):
|
async def _pay(self, invoice, amount_sat=None, attempts=1):
|
||||||
addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||||
key = bh2u(addr.paymenthash)
|
key = bh2u(lnaddr.paymenthash)
|
||||||
if key in self.preimages:
|
amount = int(lnaddr.amount * COIN) if lnaddr.amount else None
|
||||||
|
status = self.get_invoice_status(lnaddr.paymenthash)
|
||||||
|
if status == PR_PAID:
|
||||||
raise PaymentFailure(_("This invoice has been paid already"))
|
raise PaymentFailure(_("This invoice has been paid already"))
|
||||||
|
info = InvoiceInfo(lnaddr.paymenthash, amount, SENT, PR_UNPAID)
|
||||||
|
self.save_invoice_info(info)
|
||||||
self._check_invoice(invoice, amount_sat)
|
self._check_invoice(invoice, amount_sat)
|
||||||
self.save_invoice(addr.paymenthash, invoice, SENT, PR_INFLIGHT)
|
self.wallet.set_label(key, lnaddr.get_description())
|
||||||
self.wallet.set_label(key, addr.get_description())
|
|
||||||
for i in range(attempts):
|
for i in range(attempts):
|
||||||
route = await self._create_route_from_invoice(decoded_invoice=addr)
|
route = await self._create_route_from_invoice(decoded_invoice=lnaddr)
|
||||||
if not self.get_channel_by_short_id(route[0].short_channel_id):
|
if not self.get_channel_by_short_id(route[0].short_channel_id):
|
||||||
scid = route[0].short_channel_id
|
scid = route[0].short_channel_id
|
||||||
raise Exception(f"Got route with unknown first channel: {scid}")
|
raise Exception(f"Got route with unknown first channel: {scid}")
|
||||||
self.network.trigger_callback('payment_status', key, 'progress', i)
|
self.network.trigger_callback('payment_status', key, 'progress', i)
|
||||||
if await self._pay_to_route(route, addr, invoice):
|
if await self._pay_to_route(route, lnaddr, invoice):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -854,10 +873,12 @@ class LNWallet(LNWorker):
|
||||||
if not chan:
|
if not chan:
|
||||||
raise Exception(f"PathFinder returned path with short_channel_id "
|
raise Exception(f"PathFinder returned path with short_channel_id "
|
||||||
f"{short_channel_id} that is not in channel list")
|
f"{short_channel_id} that is not in channel list")
|
||||||
|
self.set_invoice_status(addr.paymenthash, PR_INFLIGHT)
|
||||||
peer = self.peers[route[0].node_id]
|
peer = self.peers[route[0].node_id]
|
||||||
htlc = await peer.pay(route, chan, int(addr.amount * COIN * 1000), addr.paymenthash, addr.get_min_final_cltv_expiry())
|
htlc = await peer.pay(route, chan, int(addr.amount * COIN * 1000), addr.paymenthash, addr.get_min_final_cltv_expiry())
|
||||||
self.network.trigger_callback('htlc_added', htlc, addr, SENT)
|
self.network.trigger_callback('htlc_added', htlc, addr, SENT)
|
||||||
success = await self.pending_payments[(short_channel_id, htlc.htlc_id)]
|
success = await self.pending_payments[(short_channel_id, htlc.htlc_id)]
|
||||||
|
self.set_invoice_status(addr.paymenthash, (PR_PAID if success else PR_UNPAID))
|
||||||
return success
|
return success
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -933,119 +954,89 @@ class LNWallet(LNWorker):
|
||||||
raise PaymentFailure(_("No path found"))
|
raise PaymentFailure(_("No path found"))
|
||||||
return route
|
return route
|
||||||
|
|
||||||
def add_invoice(self, amount_sat, message, expiry):
|
def add_request(self, amount_sat, message, expiry):
|
||||||
coro = self._add_invoice_coro(amount_sat, message, expiry)
|
coro = self._add_request_coro(amount_sat, message, expiry)
|
||||||
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
|
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
|
||||||
try:
|
try:
|
||||||
return fut.result(timeout=5)
|
return fut.result(timeout=5)
|
||||||
except concurrent.futures.TimeoutError:
|
except concurrent.futures.TimeoutError:
|
||||||
raise Exception(_("add_invoice timed out"))
|
raise Exception(_("add invoice timed out"))
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def _add_invoice_coro(self, amount_sat, message, expiry):
|
async def _add_request_coro(self, amount_sat, message, expiry):
|
||||||
payment_preimage = os.urandom(32)
|
timestamp = int(time.time())
|
||||||
payment_hash = sha256(payment_preimage)
|
|
||||||
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
|
|
||||||
routing_hints = await self._calc_routing_hints_for_invoice(amount_sat)
|
routing_hints = await self._calc_routing_hints_for_invoice(amount_sat)
|
||||||
if not routing_hints:
|
if not routing_hints:
|
||||||
self.logger.info("Warning. No routing hints added to invoice. "
|
self.logger.info("Warning. No routing hints added to invoice. "
|
||||||
"Other clients will likely not be able to send to us.")
|
"Other clients will likely not be able to send to us.")
|
||||||
invoice = lnencode(LnAddr(payment_hash, amount_btc,
|
payment_preimage = os.urandom(32)
|
||||||
tags=[('d', message),
|
payment_hash = sha256(payment_preimage)
|
||||||
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
info = InvoiceInfo(payment_hash, amount_sat, RECEIVED, PR_UNPAID)
|
||||||
('x', expiry)]
|
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
|
||||||
+ routing_hints),
|
lnaddr = LnAddr(payment_hash, amount_btc,
|
||||||
self.node_keypair.privkey)
|
tags=[('d', message),
|
||||||
self.save_invoice(payment_hash, invoice, RECEIVED, PR_UNPAID)
|
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
||||||
|
('x', expiry)]
|
||||||
|
+ routing_hints,
|
||||||
|
date = timestamp)
|
||||||
|
invoice = lnencode(lnaddr, self.node_keypair.privkey)
|
||||||
|
key = bh2u(lnaddr.paymenthash)
|
||||||
|
req = {
|
||||||
|
'type': PR_TYPE_LN,
|
||||||
|
'amount': amount_sat,
|
||||||
|
'time': lnaddr.date,
|
||||||
|
'exp': expiry,
|
||||||
|
'message': message,
|
||||||
|
'rhash': key,
|
||||||
|
'invoice': invoice
|
||||||
|
}
|
||||||
self.save_preimage(payment_hash, payment_preimage)
|
self.save_preimage(payment_hash, payment_preimage)
|
||||||
self.wallet.set_label(bh2u(payment_hash), message)
|
self.save_invoice_info(info)
|
||||||
return payment_hash
|
self.wallet.add_payment_request(req)
|
||||||
|
self.wallet.set_label(key, message)
|
||||||
|
return key
|
||||||
|
|
||||||
def save_preimage(self, payment_hash: bytes, preimage: bytes):
|
def save_preimage(self, payment_hash: bytes, preimage: bytes):
|
||||||
assert sha256(preimage) == payment_hash
|
assert sha256(preimage) == payment_hash
|
||||||
key = bh2u(payment_hash)
|
self.preimages[bh2u(payment_hash)] = bh2u(preimage)
|
||||||
self.preimages[key] = bh2u(preimage)
|
|
||||||
self.storage.put('lightning_preimages', self.preimages)
|
self.storage.put('lightning_preimages', self.preimages)
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
|
|
||||||
def get_preimage(self, payment_hash: bytes) -> bytes:
|
def get_preimage(self, payment_hash: bytes) -> bytes:
|
||||||
try:
|
return bfh(self.preimages.get(bh2u(payment_hash)))
|
||||||
preimage = bfh(self.preimages[bh2u(payment_hash)])
|
|
||||||
assert sha256(preimage) == payment_hash
|
|
||||||
return preimage
|
|
||||||
except KeyError as e:
|
|
||||||
raise UnknownPaymentHash(payment_hash) from e
|
|
||||||
|
|
||||||
def save_new_invoice(self, invoice):
|
def get_invoice_info(self, payment_hash: bytes) -> bytes:
|
||||||
addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
key = payment_hash.hex()
|
||||||
self.save_invoice(addr.paymenthash, invoice, SENT, PR_UNPAID)
|
|
||||||
|
|
||||||
def save_invoice(self, payment_hash:bytes, invoice, direction, status):
|
|
||||||
key = bh2u(payment_hash)
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.invoices[key] = invoice, direction, status
|
if key not in self.invoices:
|
||||||
self.storage.put('lightning_invoices', self.invoices)
|
raise UnknownPaymentHash(payment_hash)
|
||||||
|
amount, direction, status = self.invoices[key]
|
||||||
|
return InvoiceInfo(payment_hash, amount, direction, status)
|
||||||
|
|
||||||
|
def save_invoice_info(self, info):
|
||||||
|
key = info.payment_hash.hex()
|
||||||
|
with self.lock:
|
||||||
|
self.invoices[key] = info.amount, info.direction, info.status
|
||||||
|
self.storage.put('lightning_invoices2', self.invoices)
|
||||||
self.storage.write()
|
self.storage.write()
|
||||||
|
|
||||||
def set_invoice_status(self, payment_hash, status):
|
def get_invoice_status(self, payment_hash):
|
||||||
key = bh2u(payment_hash)
|
try:
|
||||||
if key not in self.invoices:
|
info = self.get_invoice_info(payment_hash)
|
||||||
|
return info.status
|
||||||
|
except UnknownPaymentHash:
|
||||||
|
return PR_UNKNOWN
|
||||||
|
|
||||||
|
def set_invoice_status(self, payment_hash: bytes, status):
|
||||||
|
try:
|
||||||
|
info = self.get_invoice_info(payment_hash)
|
||||||
|
except UnknownPaymentHash:
|
||||||
# if we are forwarding
|
# if we are forwarding
|
||||||
return
|
return
|
||||||
invoice, direction, _ = self.invoices[key]
|
info = info._replace(status=status)
|
||||||
self.save_invoice(payment_hash, invoice, direction, status)
|
self.save_invoice_info(info)
|
||||||
if direction == RECEIVED and status == PR_PAID:
|
if info.direction == RECEIVED and info.status == PR_PAID:
|
||||||
self.network.trigger_callback('payment_received', self.wallet, key, PR_PAID)
|
self.network.trigger_callback('payment_received', self.wallet, bh2u(payment_hash), PR_PAID)
|
||||||
|
|
||||||
def get_invoice(self, payment_hash: bytes) -> LnAddr:
|
|
||||||
try:
|
|
||||||
invoice, direction, is_paid = self.invoices[bh2u(payment_hash)]
|
|
||||||
return lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
|
||||||
except KeyError as e:
|
|
||||||
raise UnknownPaymentHash(payment_hash) from e
|
|
||||||
|
|
||||||
def get_request(self, key):
|
|
||||||
if key not in self.invoices:
|
|
||||||
return
|
|
||||||
# todo: parse invoices when saving
|
|
||||||
invoice, direction, is_paid = self.invoices[key]
|
|
||||||
status = self.get_invoice_status(key)
|
|
||||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
|
||||||
amount_sat = int(lnaddr.amount*COIN) if lnaddr.amount else None
|
|
||||||
description = lnaddr.get_description()
|
|
||||||
timestamp = lnaddr.date
|
|
||||||
return {
|
|
||||||
'type': PR_TYPE_LN,
|
|
||||||
'status': status,
|
|
||||||
'amount': amount_sat,
|
|
||||||
'time': timestamp,
|
|
||||||
'exp': lnaddr.get_expiry(),
|
|
||||||
'message': description,
|
|
||||||
'rhash': key,
|
|
||||||
'invoice': invoice
|
|
||||||
}
|
|
||||||
|
|
||||||
@profiler
|
|
||||||
def get_invoices(self):
|
|
||||||
# invoices = outgoing
|
|
||||||
out = []
|
|
||||||
with self.lock:
|
|
||||||
invoice_items = list(self.invoices.items())
|
|
||||||
for key, (invoice, direction, status) in invoice_items:
|
|
||||||
if direction == SENT and status != PR_PAID:
|
|
||||||
out.append(self.get_request(key))
|
|
||||||
return out
|
|
||||||
|
|
||||||
@profiler
|
|
||||||
def get_requests(self):
|
|
||||||
# requests = incoming
|
|
||||||
out = []
|
|
||||||
with self.lock:
|
|
||||||
invoice_items = list(self.invoices.items())
|
|
||||||
for key, (invoice, direction, status) in invoice_items:
|
|
||||||
if direction == RECEIVED and status != PR_PAID:
|
|
||||||
out.append(self.get_request(key))
|
|
||||||
return out
|
|
||||||
|
|
||||||
async def _calc_routing_hints_for_invoice(self, amount_sat):
|
async def _calc_routing_hints_for_invoice(self, amount_sat):
|
||||||
"""calculate routing hints (BOLT-11 'r' field)"""
|
"""calculate routing hints (BOLT-11 'r' field)"""
|
||||||
|
|
|
@ -114,7 +114,7 @@ if [[ $1 == "open" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $1 == "alice_pays_carol" ]]; then
|
if [[ $1 == "alice_pays_carol" ]]; then
|
||||||
request=$($carol addinvoice 0.0001 "blah")
|
request=$($carol add_lightning_request 0.0001 -m "blah")
|
||||||
$alice lnpay $request
|
$alice lnpay $request
|
||||||
carol_balance=$($carol list_channels | jq -r '.[0].local_balance')
|
carol_balance=$($carol list_channels | jq -r '.[0].local_balance')
|
||||||
echo "carol balance: $carol_balance"
|
echo "carol balance: $carol_balance"
|
||||||
|
@ -140,12 +140,12 @@ if [[ $1 == "breach" ]]; then
|
||||||
channel=$($alice open_channel $bob_node 0.15)
|
channel=$($alice open_channel $bob_node 0.15)
|
||||||
new_blocks 3
|
new_blocks 3
|
||||||
wait_until_channel_open alice
|
wait_until_channel_open alice
|
||||||
request=$($bob addinvoice 0.01 "blah")
|
request=$($bob add_lightning_request 0.01 -m "blah")
|
||||||
echo "alice pays"
|
echo "alice pays"
|
||||||
$alice lnpay $request
|
$alice lnpay $request
|
||||||
sleep 2
|
sleep 2
|
||||||
ctx=$($alice get_channel_ctx $channel | jq '.hex' | tr -d '"')
|
ctx=$($alice get_channel_ctx $channel | jq '.hex' | tr -d '"')
|
||||||
request=$($bob addinvoice 0.01 "blah2")
|
request=$($bob add_lightning_request 0.01 -m "blah2")
|
||||||
echo "alice pays again"
|
echo "alice pays again"
|
||||||
$alice lnpay $request
|
$alice lnpay $request
|
||||||
echo "alice broadcasts old ctx"
|
echo "alice broadcasts old ctx"
|
||||||
|
@ -168,7 +168,7 @@ if [[ $1 == "redeem_htlcs" ]]; then
|
||||||
new_blocks 6
|
new_blocks 6
|
||||||
sleep 10
|
sleep 10
|
||||||
# alice pays bob
|
# alice pays bob
|
||||||
invoice=$($bob addinvoice 0.05 "test")
|
invoice=$($bob add_lightning_request 0.05 -m "test")
|
||||||
$alice lnpay $invoice --timeout=1 || true
|
$alice lnpay $invoice --timeout=1 || true
|
||||||
sleep 1
|
sleep 1
|
||||||
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
||||||
|
@ -214,7 +214,7 @@ if [[ $1 == "breach_with_unspent_htlc" ]]; then
|
||||||
new_blocks 3
|
new_blocks 3
|
||||||
wait_until_channel_open alice
|
wait_until_channel_open alice
|
||||||
echo "alice pays bob"
|
echo "alice pays bob"
|
||||||
invoice=$($bob addinvoice 0.05 "test")
|
invoice=$($bob add_lightning_request 0.05 -m "test")
|
||||||
$alice lnpay $invoice --timeout=1 || true
|
$alice lnpay $invoice --timeout=1 || true
|
||||||
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
||||||
if [[ "$settled" != "0" ]]; then
|
if [[ "$settled" != "0" ]]; then
|
||||||
|
@ -246,7 +246,7 @@ if [[ $1 == "breach_with_spent_htlc" ]]; then
|
||||||
new_blocks 3
|
new_blocks 3
|
||||||
wait_until_channel_open alice
|
wait_until_channel_open alice
|
||||||
echo "alice pays bob"
|
echo "alice pays bob"
|
||||||
invoice=$($bob addinvoice 0.05 "test")
|
invoice=$($bob add_lightning_request 0.05 -m "test")
|
||||||
$alice lnpay $invoice --timeout=1 || true
|
$alice lnpay $invoice --timeout=1 || true
|
||||||
ctx=$($alice get_channel_ctx $channel | jq '.hex' | tr -d '"')
|
ctx=$($alice get_channel_ctx $channel | jq '.hex' | tr -d '"')
|
||||||
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
|
||||||
|
@ -310,11 +310,11 @@ if [[ $1 == "watchtower" ]]; then
|
||||||
new_blocks 3
|
new_blocks 3
|
||||||
wait_until_channel_open alice
|
wait_until_channel_open alice
|
||||||
echo "alice pays bob"
|
echo "alice pays bob"
|
||||||
invoice1=$($bob addinvoice 0.05 "invoice1")
|
invoice1=$($bob add_lightning_request 0.05 -m "invoice1")
|
||||||
$alice lnpay $invoice1
|
$alice lnpay $invoice1
|
||||||
invoice2=$($bob addinvoice 0.05 "invoice2")
|
invoice2=$($bob add_lightning_request 0.05 -m "invoice2")
|
||||||
$alice lnpay $invoice2
|
$alice lnpay $invoice2
|
||||||
invoice3=$($bob addinvoice 0.05 "invoice3")
|
invoice3=$($bob add_lightning_request 0.05 -m "invoice3")
|
||||||
$alice lnpay $invoice3
|
$alice lnpay $invoice3
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -21,6 +21,7 @@ from electrum.channel_db import ChannelDB
|
||||||
from electrum.lnworker import LNWallet
|
from electrum.lnworker import LNWallet
|
||||||
from electrum.lnmsg import encode_msg, decode_msg
|
from electrum.lnmsg import encode_msg, decode_msg
|
||||||
from electrum.logging import console_stderr_handler
|
from electrum.logging import console_stderr_handler
|
||||||
|
from electrum.lnworker import InvoiceInfo, RECEIVED, PR_UNPAID
|
||||||
|
|
||||||
from .test_lnchannel import create_test_channels
|
from .test_lnchannel import create_test_channels
|
||||||
from . import SequentialTestCase
|
from . import SequentialTestCase
|
||||||
|
@ -80,6 +81,7 @@ class MockWallet:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class MockLNWallet:
|
class MockLNWallet:
|
||||||
|
storage = MockStorage()
|
||||||
def __init__(self, remote_keypair, local_keypair, chan, tx_queue):
|
def __init__(self, remote_keypair, local_keypair, chan, tx_queue):
|
||||||
self.chan = chan
|
self.chan = chan
|
||||||
self.remote_keypair = remote_keypair
|
self.remote_keypair = remote_keypair
|
||||||
|
@ -87,7 +89,6 @@ class MockLNWallet:
|
||||||
self.network = MockNetwork(tx_queue)
|
self.network = MockNetwork(tx_queue)
|
||||||
self.channels = {self.chan.channel_id: self.chan}
|
self.channels = {self.chan.channel_id: self.chan}
|
||||||
self.invoices = {}
|
self.invoices = {}
|
||||||
self.preimages = {}
|
|
||||||
self.inflight = {}
|
self.inflight = {}
|
||||||
self.wallet = MockWallet()
|
self.wallet = MockWallet()
|
||||||
self.localfeatures = LnLocalFeatures(0)
|
self.localfeatures = LnLocalFeatures(0)
|
||||||
|
@ -122,7 +123,11 @@ class MockLNWallet:
|
||||||
def save_invoice(*args, is_paid=False):
|
def save_invoice(*args, is_paid=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
get_invoice = LNWallet.get_invoice
|
preimages = {}
|
||||||
|
get_invoice_info = LNWallet.get_invoice_info
|
||||||
|
save_invoice_info = LNWallet.save_invoice_info
|
||||||
|
set_invoice_status = LNWallet.set_invoice_status
|
||||||
|
save_preimage = LNWallet.save_preimage
|
||||||
get_preimage = LNWallet.get_preimage
|
get_preimage = LNWallet.get_preimage
|
||||||
_create_route_from_invoice = LNWallet._create_route_from_invoice
|
_create_route_from_invoice = LNWallet._create_route_from_invoice
|
||||||
_check_invoice = staticmethod(LNWallet._check_invoice)
|
_check_invoice = staticmethod(LNWallet._check_invoice)
|
||||||
|
@ -207,19 +212,20 @@ class TestPeer(SequentialTestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prepare_invoice(w2 # receiver
|
def prepare_invoice(w2 # receiver
|
||||||
):
|
):
|
||||||
amount_btc = 100000/Decimal(COIN)
|
amount_sat = 100000
|
||||||
|
amount_btc = amount_sat/Decimal(COIN)
|
||||||
payment_preimage = os.urandom(32)
|
payment_preimage = os.urandom(32)
|
||||||
RHASH = sha256(payment_preimage)
|
RHASH = sha256(payment_preimage)
|
||||||
addr = LnAddr(
|
info = InvoiceInfo(RHASH, amount_sat, RECEIVED, PR_UNPAID)
|
||||||
|
w2.save_preimage(RHASH, payment_preimage)
|
||||||
|
w2.save_invoice_info(info)
|
||||||
|
lnaddr = LnAddr(
|
||||||
RHASH,
|
RHASH,
|
||||||
amount_btc,
|
amount_btc,
|
||||||
tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
|
||||||
('d', 'coffee')
|
('d', 'coffee')
|
||||||
])
|
])
|
||||||
pay_req = lnencode(addr, w2.node_keypair.privkey)
|
return lnencode(lnaddr, w2.node_keypair.privkey)
|
||||||
w2.preimages[bh2u(RHASH)] = bh2u(payment_preimage)
|
|
||||||
w2.invoices[bh2u(RHASH)] = (pay_req, True, False)
|
|
||||||
return pay_req
|
|
||||||
|
|
||||||
def test_payment(self):
|
def test_payment(self):
|
||||||
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers()
|
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers()
|
||||||
|
|
|
@ -541,16 +541,16 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
def save_invoice(self, invoice):
|
def save_invoice(self, invoice):
|
||||||
invoice_type = invoice['type']
|
invoice_type = invoice['type']
|
||||||
if invoice_type == PR_TYPE_LN:
|
if invoice_type == PR_TYPE_LN:
|
||||||
self.lnworker.save_new_invoice(invoice['invoice'])
|
key = invoice['rhash']
|
||||||
elif invoice_type == PR_TYPE_ONCHAIN:
|
elif invoice_type == PR_TYPE_ONCHAIN:
|
||||||
key = bh2u(sha256(repr(invoice))[0:16])
|
key = bh2u(sha256(repr(invoice))[0:16])
|
||||||
invoice['id'] = key
|
invoice['id'] = key
|
||||||
invoice['txid'] = None
|
invoice['txid'] = None
|
||||||
self.invoices[key] = invoice
|
|
||||||
self.storage.put('invoices', self.invoices)
|
|
||||||
self.storage.write()
|
|
||||||
else:
|
else:
|
||||||
raise Exception('Unsupported invoice type')
|
raise Exception('Unsupported invoice type')
|
||||||
|
self.invoices[key] = invoice
|
||||||
|
self.storage.put('invoices', self.invoices)
|
||||||
|
self.storage.write()
|
||||||
|
|
||||||
def clear_invoices(self):
|
def clear_invoices(self):
|
||||||
self.invoices = {}
|
self.invoices = {}
|
||||||
|
@ -560,29 +560,26 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
def get_invoices(self):
|
def get_invoices(self):
|
||||||
out = [self.get_invoice(key) for key in self.invoices.keys()]
|
out = [self.get_invoice(key) for key in self.invoices.keys()]
|
||||||
out = [x for x in out if x and x.get('status') != PR_PAID]
|
out = [x for x in out if x and x.get('status') != PR_PAID]
|
||||||
if self.lnworker:
|
|
||||||
out += self.lnworker.get_invoices()
|
|
||||||
out.sort(key=operator.itemgetter('time'))
|
out.sort(key=operator.itemgetter('time'))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def check_if_expired(self, item):
|
||||||
|
if item['status'] == PR_UNPAID and 'exp' in item and item['time'] + item['exp'] < time.time():
|
||||||
|
item['status'] = PR_EXPIRED
|
||||||
|
|
||||||
def get_invoice(self, key):
|
def get_invoice(self, key):
|
||||||
if key in self.invoices:
|
if key not in self.invoices:
|
||||||
item = copy.copy(self.invoices[key])
|
return
|
||||||
request_type = item.get('type')
|
item = copy.copy(self.invoices[key])
|
||||||
if request_type is None:
|
request_type = item.get('type')
|
||||||
# todo: convert old bip70 invoices
|
if request_type == PR_TYPE_ONCHAIN:
|
||||||
return
|
item['status'] = PR_PAID if item.get('txid') is not None else PR_UNPAID
|
||||||
# add status
|
elif request_type == PR_TYPE_LN:
|
||||||
if item.get('txid'):
|
item['status'] = self.lnworker.get_invoice_status(bfh(item['rhash']))
|
||||||
status = PR_PAID
|
else:
|
||||||
elif 'exp' in item and item['time'] + item['exp'] < time.time():
|
return
|
||||||
status = PR_EXPIRED
|
self.check_if_expired(item)
|
||||||
else:
|
return item
|
||||||
status = PR_UNPAID
|
|
||||||
item['status'] = status
|
|
||||||
return item
|
|
||||||
if self.lnworker:
|
|
||||||
return self.lnworker.get_request(key)
|
|
||||||
|
|
||||||
@profiler
|
@profiler
|
||||||
def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True):
|
def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True):
|
||||||
|
@ -1319,19 +1316,6 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
return True, conf
|
return True, conf
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
def get_payment_request(self, addr):
|
|
||||||
r = self.receive_requests.get(addr)
|
|
||||||
if not r:
|
|
||||||
return
|
|
||||||
out = copy.copy(r)
|
|
||||||
out['type'] = PR_TYPE_ONCHAIN
|
|
||||||
out['URI'] = self.get_request_URI(addr)
|
|
||||||
status, conf = self.get_request_status(addr)
|
|
||||||
out['status'] = status
|
|
||||||
if conf is not None:
|
|
||||||
out['confirmations'] = conf
|
|
||||||
return out
|
|
||||||
|
|
||||||
def get_request_URI(self, addr):
|
def get_request_URI(self, addr):
|
||||||
req = self.receive_requests[addr]
|
req = self.receive_requests[addr]
|
||||||
message = self.labels.get(addr, '')
|
message = self.labels.get(addr, '')
|
||||||
|
@ -1349,11 +1333,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
uri = create_bip21_uri(addr, amount, message, extra_query_params=extra_query_params)
|
uri = create_bip21_uri(addr, amount, message, extra_query_params=extra_query_params)
|
||||||
return str(uri)
|
return str(uri)
|
||||||
|
|
||||||
def get_request_status(self, key):
|
def get_request_status(self, address):
|
||||||
r = self.receive_requests.get(key)
|
r = self.receive_requests.get(address)
|
||||||
if r is None:
|
if r is None:
|
||||||
return PR_UNKNOWN
|
return PR_UNKNOWN
|
||||||
address = r['address']
|
|
||||||
amount = r.get('amount', 0) or 0
|
amount = r.get('amount', 0) or 0
|
||||||
timestamp = r.get('time', 0)
|
timestamp = r.get('time', 0)
|
||||||
if timestamp and type(timestamp) != int:
|
if timestamp and type(timestamp) != int:
|
||||||
|
@ -1372,14 +1355,23 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
return status, conf
|
return status, conf
|
||||||
|
|
||||||
def get_request(self, key):
|
def get_request(self, key):
|
||||||
if key in self.receive_requests:
|
req = self.receive_requests.get(key)
|
||||||
req = self.get_payment_request(key)
|
|
||||||
elif self.lnworker:
|
|
||||||
req = self.lnworker.get_request(key)
|
|
||||||
else:
|
|
||||||
req = None
|
|
||||||
if not req:
|
if not req:
|
||||||
return
|
return
|
||||||
|
req = copy.copy(req)
|
||||||
|
if req['type'] == PR_TYPE_ONCHAIN:
|
||||||
|
addr = req['address']
|
||||||
|
req['URI'] = self.get_request_URI(addr)
|
||||||
|
status, conf = self.get_request_status(addr)
|
||||||
|
req['status'] = status
|
||||||
|
if conf is not None:
|
||||||
|
req['confirmations'] = conf
|
||||||
|
elif req['type'] == PR_TYPE_LN:
|
||||||
|
req['status'] = self.lnworker.get_invoice_status(bfh(key))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
self.check_if_expired(req)
|
||||||
|
# add URL if we are running a payserver
|
||||||
if self.config.get('payserver_port'):
|
if self.config.get('payserver_port'):
|
||||||
host = self.config.get('payserver_host', 'localhost')
|
host = self.config.get('payserver_host', 'localhost')
|
||||||
port = self.config.get('payserver_port')
|
port = self.config.get('payserver_port')
|
||||||
|
@ -1405,8 +1397,16 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
from .bitcoin import TYPE_ADDRESS
|
from .bitcoin import TYPE_ADDRESS
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
_id = bh2u(sha256d(addr + "%d"%timestamp))[0:10]
|
_id = bh2u(sha256d(addr + "%d"%timestamp))[0:10]
|
||||||
r = {'time':timestamp, 'amount':amount, 'exp':expiration, 'address':addr, 'memo':message, 'id':_id, 'outputs': [(TYPE_ADDRESS, addr, amount)]}
|
return {
|
||||||
return r
|
'type': PR_TYPE_ONCHAIN,
|
||||||
|
'time':timestamp,
|
||||||
|
'amount':amount,
|
||||||
|
'exp':expiration,
|
||||||
|
'address':addr,
|
||||||
|
'memo':message,
|
||||||
|
'id':_id,
|
||||||
|
'outputs': [(TYPE_ADDRESS, addr, amount)]
|
||||||
|
}
|
||||||
|
|
||||||
def sign_payment_request(self, key, alias, alias_addr, password):
|
def sign_payment_request(self, key, alias, alias_addr, password):
|
||||||
req = self.receive_requests.get(key)
|
req = self.receive_requests.get(key)
|
||||||
|
@ -1419,17 +1419,23 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
self.storage.put('payment_requests', self.receive_requests)
|
self.storage.put('payment_requests', self.receive_requests)
|
||||||
|
|
||||||
def add_payment_request(self, req):
|
def add_payment_request(self, req):
|
||||||
addr = req['address']
|
if req['type'] == PR_TYPE_ONCHAIN:
|
||||||
if not bitcoin.is_address(addr):
|
addr = req['address']
|
||||||
raise Exception(_('Invalid Bitcoin address.'))
|
if not bitcoin.is_address(addr):
|
||||||
if not self.is_mine(addr):
|
raise Exception(_('Invalid Bitcoin address.'))
|
||||||
raise Exception(_('Address not in wallet.'))
|
if not self.is_mine(addr):
|
||||||
|
raise Exception(_('Address not in wallet.'))
|
||||||
|
key = addr
|
||||||
|
message = req['memo']
|
||||||
|
elif req['type'] == PR_TYPE_LN:
|
||||||
|
key = req['rhash']
|
||||||
|
message = req['message']
|
||||||
|
else:
|
||||||
|
raise Exception('Unknown request type')
|
||||||
amount = req.get('amount')
|
amount = req.get('amount')
|
||||||
message = req.get('memo')
|
self.receive_requests[key] = req
|
||||||
self.receive_requests[addr] = req
|
|
||||||
self.storage.put('payment_requests', self.receive_requests)
|
self.storage.put('payment_requests', self.receive_requests)
|
||||||
self.set_label(addr, message) # should be a default label
|
self.set_label(key, message) # should be a default label
|
||||||
return req
|
return req
|
||||||
|
|
||||||
def delete_request(self, key):
|
def delete_request(self, key):
|
||||||
|
@ -1457,8 +1463,6 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
def get_sorted_requests(self):
|
def get_sorted_requests(self):
|
||||||
""" sorted by timestamp """
|
""" sorted by timestamp """
|
||||||
out = [self.get_request(x) for x in self.receive_requests.keys()]
|
out = [self.get_request(x) for x in self.receive_requests.keys()]
|
||||||
if self.lnworker:
|
|
||||||
out += self.lnworker.get_requests()
|
|
||||||
out.sort(key=operator.itemgetter('time'))
|
out.sort(key=operator.itemgetter('time'))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue