mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
lightning:
* store invoices for both directions * do not store lightning_payments_inflight, lightning_payments_completed in lnworker * payment history is returned by get_payments method of LNChannel * command line: lightning history, lightning_invoices * re-enable push_msat
This commit is contained in:
parent
d80b709aa4
commit
0e8dba897e
9 changed files with 120 additions and 149 deletions
|
@ -47,6 +47,7 @@ from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text
|
||||||
from .address_synchronizer import TX_HEIGHT_LOCAL
|
from .address_synchronizer import TX_HEIGHT_LOCAL
|
||||||
from .import lightning
|
from .import lightning
|
||||||
from .mnemonic import Mnemonic
|
from .mnemonic import Mnemonic
|
||||||
|
from .lnutil import SENT, RECEIVED
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .network import Network
|
from .network import Network
|
||||||
|
@ -108,6 +109,8 @@ class Commands:
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
self.network = network
|
self.network = network
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
if self.wallet:
|
||||||
|
self.lnworker = self.wallet.lnworker
|
||||||
|
|
||||||
def _run(self, method, args, password_getter, **kwargs):
|
def _run(self, method, args, password_getter, **kwargs):
|
||||||
"""This wrapper is called from the Qt python console."""
|
"""This wrapper is called from the Qt python console."""
|
||||||
|
@ -766,33 +769,33 @@ class Commands:
|
||||||
# lightning network commands
|
# lightning network commands
|
||||||
@command('wpn')
|
@command('wpn')
|
||||||
def open_channel(self, connection_string, amount, channel_push=0, password=None):
|
def open_channel(self, connection_string, amount, channel_push=0, password=None):
|
||||||
return self.wallet.lnworker.open_channel(connection_string, satoshis(amount), satoshis(channel_push), password)
|
return self.lnworker.open_channel(connection_string, satoshis(amount), satoshis(channel_push), password)
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def reestablish_channel(self):
|
def reestablish_channel(self):
|
||||||
self.wallet.lnworker.reestablish_channel()
|
self.lnworker.reestablish_channel()
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def lnpay(self, invoice):
|
def lnpay(self, invoice):
|
||||||
addr, peer, f = self.wallet.lnworker.pay(invoice)
|
addr, peer, f = self.lnworker.pay(invoice)
|
||||||
return f.result()
|
return f.result()
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def addinvoice(self, requested_amount, message):
|
def addinvoice(self, requested_amount, message):
|
||||||
# using requested_amount because it is documented in param_descriptions
|
# using requested_amount because it is documented in param_descriptions
|
||||||
return self.wallet.lnworker.add_invoice(satoshis(requested_amount), message)
|
return self.lnworker.add_invoice(satoshis(requested_amount), message)
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def nodeid(self):
|
def nodeid(self):
|
||||||
return bh2u(self.wallet.lnworker.node_keypair.pubkey)
|
return bh2u(self.lnworker.node_keypair.pubkey)
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
def listchannels(self):
|
def listchannels(self):
|
||||||
return list(self.wallet.lnworker.list_channels())
|
return list(self.lnworker.list_channels())
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def dumpgraph(self):
|
def dumpgraph(self):
|
||||||
return list(map(bh2u, self.wallet.lnworker.channel_db.nodes.keys()))
|
return list(map(bh2u, self.lnworker.channel_db.nodes.keys()))
|
||||||
|
|
||||||
@command('n')
|
@command('n')
|
||||||
def inject_fees(self, fees):
|
def inject_fees(self, fees):
|
||||||
|
@ -805,47 +808,35 @@ class Commands:
|
||||||
self.network.path_finder.blacklist.clear()
|
self.network.path_finder.blacklist.clear()
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
def listinvoices(self):
|
def lightning_invoices(self):
|
||||||
report = self.wallet.lnworker._list_invoices()
|
from .util import pr_tooltips
|
||||||
return '\n'.join(self._format_ln_invoices(report))
|
out = []
|
||||||
|
for payment_hash, (preimage, pay_req, direction, pay_timestamp) in self.lnworker.invoices.items():
|
||||||
def _format_ln_invoices(self, report):
|
status = pr_tooltips[self.lnworker.get_invoice_status(payment_hash)]
|
||||||
from .lnutil import SENT
|
out.append({'payment_hash':payment_hash, 'invoice':pay_req, 'preimage':preimage, 'status':status, 'direction':direction})
|
||||||
if report['settled']:
|
return out
|
||||||
yield 'Settled invoices:'
|
|
||||||
yield '-----------------'
|
|
||||||
for date, direction, htlc, preimage in sorted(report['settled']):
|
|
||||||
# astimezone converts to local time
|
|
||||||
# replace removes the tz info since we don't need to display it
|
|
||||||
yield 'Paid at: ' + date.astimezone().replace(tzinfo=None).isoformat(sep=' ', timespec='minutes')
|
|
||||||
yield 'We paid' if direction == SENT else 'They paid'
|
|
||||||
yield str(htlc)
|
|
||||||
yield 'Preimage: ' + (bh2u(preimage) if preimage else 'Not available') # if delete_invoice was called
|
|
||||||
yield ''
|
|
||||||
if report['unsettled']:
|
|
||||||
yield 'Your unsettled invoices:'
|
|
||||||
yield '------------------------'
|
|
||||||
for addr, preimage, pay_req in report['unsettled']:
|
|
||||||
yield pay_req
|
|
||||||
yield str(addr)
|
|
||||||
yield 'Preimage: ' + bh2u(preimage)
|
|
||||||
yield ''
|
|
||||||
if report['inflight']:
|
|
||||||
yield 'Outgoing payments in progress:'
|
|
||||||
yield '------------------------------'
|
|
||||||
for addr, htlc, direction in report['inflight']:
|
|
||||||
yield str(addr)
|
|
||||||
yield str(htlc)
|
|
||||||
yield ''
|
|
||||||
|
|
||||||
|
@command('w')
|
||||||
|
def lightning_history(self):
|
||||||
|
out = []
|
||||||
|
for chan_id, htlc, direction, status in self.lnworker.get_payments().values():
|
||||||
|
item = {
|
||||||
|
'direction': 'sent' if direction == SENT else 'received',
|
||||||
|
'status':status,
|
||||||
|
'amout_msat':htlc.amount_msat,
|
||||||
|
'payment_hash':bh2u(htlc.payment_hash),
|
||||||
|
'chan_id':bh2u(chan_id),
|
||||||
|
'htlc_id':htlc.htlc_id,
|
||||||
|
'cltv_expiry':htlc.cltv_expiry
|
||||||
|
}
|
||||||
|
out.append(item)
|
||||||
|
return out
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
def closechannel(self, channel_point, force=False):
|
def closechannel(self, channel_point, force=False):
|
||||||
chan_id = bytes(reversed(bfh(channel_point)))
|
chan_id = bytes(reversed(bfh(channel_point)))
|
||||||
if force:
|
coro = self.lnworker.force_close_channel(chan_id) if force else self.lnworker.force_close_channel(chan_id)
|
||||||
return self.network.run_from_another_thread(self.wallet.lnworker.force_close_channel(chan_id))
|
return self.network.run_from_another_thread(coro)
|
||||||
else:
|
|
||||||
return self.network.run_from_another_thread(self.wallet.lnworker.close_channel(chan_id))
|
|
||||||
|
|
||||||
def eval_bool(x: str) -> bool:
|
def eval_bool(x: str) -> bool:
|
||||||
if x == 'false': return False
|
if x == 'false': return False
|
||||||
|
|
|
@ -56,11 +56,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
||||||
parentItem = model.invisibleRootItem()
|
parentItem = model.invisibleRootItem()
|
||||||
folder_types = {'settled': _('Fulfilled HTLCs'), 'inflight': _('HTLCs in current commitment transaction')}
|
folder_types = {'settled': _('Fulfilled HTLCs'), 'inflight': _('HTLCs in current commitment transaction')}
|
||||||
self.folders = {}
|
self.folders = {}
|
||||||
|
|
||||||
self.keyname_rows = {}
|
self.keyname_rows = {}
|
||||||
|
|
||||||
invoices = dict(self.window.wallet.lnworker.invoices)
|
|
||||||
|
|
||||||
for keyname, i in folder_types.items():
|
for keyname, i in folder_types.items():
|
||||||
myFont=QtGui.QFont()
|
myFont=QtGui.QFont()
|
||||||
myFont.setBold(True)
|
myFont.setBold(True)
|
||||||
|
@ -70,23 +67,26 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
||||||
self.folders[keyname] = folder
|
self.folders[keyname] = folder
|
||||||
mapping = {}
|
mapping = {}
|
||||||
num = 0
|
num = 0
|
||||||
if keyname == 'inflight':
|
|
||||||
for lnaddr, i, direction in htlcs[keyname]:
|
invoices = dict(self.window.wallet.lnworker.invoices)
|
||||||
it = self.make_inflight(lnaddr, i, direction)
|
for pay_hash, item in htlcs.items():
|
||||||
self.folders[keyname].appendRow(it)
|
chan_id, i, direction, status = item
|
||||||
mapping[i.payment_hash] = num
|
if pay_hash in invoices:
|
||||||
num += 1
|
preimage, invoice, direction, timestamp = invoices[pay_hash]
|
||||||
elif keyname == 'settled':
|
lnaddr = lndecode(invoice)
|
||||||
for date, direction, i, preimage in htlcs[keyname]:
|
if status == 'inflight':
|
||||||
it = self.make_htlc_item(i, direction)
|
it = self.make_inflight(lnaddr, i, direction)
|
||||||
hex_pay_hash = bh2u(i.payment_hash)
|
self.folders['inflight'].appendRow(it)
|
||||||
if hex_pay_hash in invoices:
|
mapping[i.payment_hash] = num
|
||||||
# if we made the invoice and still have it, we can show more info
|
num += 1
|
||||||
invoice = invoices[hex_pay_hash][1]
|
elif status == 'settled':
|
||||||
self.append_lnaddr(it, lndecode(invoice))
|
it = self.make_htlc_item(i, direction)
|
||||||
self.folders[keyname].appendRow(it)
|
# if we made the invoice and still have it, we can show more info
|
||||||
mapping[i.payment_hash] = num
|
if pay_hash in invoices:
|
||||||
num += 1
|
self.append_lnaddr(it, lndecode(invoice))
|
||||||
|
self.folders['settled'].appendRow(it)
|
||||||
|
mapping[i.payment_hash] = num
|
||||||
|
num += 1
|
||||||
|
|
||||||
self.keyname_rows[keyname] = mapping
|
self.keyname_rows[keyname] = mapping
|
||||||
return model
|
return model
|
||||||
|
@ -171,8 +171,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
|
||||||
# add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
|
# add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
|
||||||
form_layout.addRow(_('Payments (HTLCs):'), None)
|
form_layout.addRow(_('Payments (HTLCs):'), None)
|
||||||
w = QtWidgets.QTreeView(self)
|
w = QtWidgets.QTreeView(self)
|
||||||
htlcs = window.wallet.lnworker._list_invoices(chan_id)
|
htlc_dict = chan.get_payments()
|
||||||
w.setModel(self.make_model(htlcs))
|
w.setModel(self.make_model(htlc_dict))
|
||||||
w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
|
w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
vbox.addWidget(w)
|
vbox.addWidget(w)
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
|
||||||
UnknownBaseUnit, DECIMAL_POINT_DEFAULT, UserFacingException,
|
UnknownBaseUnit, DECIMAL_POINT_DEFAULT, UserFacingException,
|
||||||
get_new_wallet_name, send_exception_to_crash_reporter,
|
get_new_wallet_name, send_exception_to_crash_reporter,
|
||||||
InvalidBitcoinURI, InvoiceError)
|
InvalidBitcoinURI, InvoiceError)
|
||||||
from electrum.lnutil import PaymentFailure
|
from electrum.lnutil import PaymentFailure, SENT, RECEIVED
|
||||||
from electrum.transaction import Transaction, TxOutput
|
from electrum.transaction import Transaction, TxOutput
|
||||||
from electrum.address_synchronizer import AddTransactionException
|
from electrum.address_synchronizer import AddTransactionException
|
||||||
from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet,
|
from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet,
|
||||||
|
@ -1941,6 +1941,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||||
#self.amount_e.textEdited.emit("")
|
#self.amount_e.textEdited.emit("")
|
||||||
self.payto_e.is_lightning = True
|
self.payto_e.is_lightning = True
|
||||||
self.show_send_tab_onchain_fees(False)
|
self.show_send_tab_onchain_fees(False)
|
||||||
|
# save
|
||||||
|
self.wallet.lnworker.save_invoice(None, invoice, SENT)
|
||||||
|
|
||||||
def show_send_tab_onchain_fees(self, b: bool):
|
def show_send_tab_onchain_fees(self, b: bool):
|
||||||
self.feecontrol_fields.setVisible(b)
|
self.feecontrol_fields.setVisible(b)
|
||||||
|
|
|
@ -31,7 +31,8 @@ from PyQt5.QtCore import Qt, QItemSelectionModel
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import format_time, age
|
from electrum.util import format_time, age
|
||||||
from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
|
from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT, pr_tooltips
|
||||||
|
from electrum.lnutil import SENT, RECEIVED
|
||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
from electrum.wallet import InternalAddressCorruption
|
from electrum.wallet import InternalAddressCorruption
|
||||||
from electrum.bitcoin import COIN
|
from electrum.bitcoin import COIN
|
||||||
|
@ -95,7 +96,7 @@ class RequestList(MyTreeView):
|
||||||
return
|
return
|
||||||
req = self.parent.get_request_URI(key)
|
req = self.parent.get_request_URI(key)
|
||||||
elif request_type == REQUEST_TYPE_LN:
|
elif request_type == REQUEST_TYPE_LN:
|
||||||
preimage, req = self.wallet.lnworker.invoices.get(key, (None, None))
|
preimage, req, direction, pay_timestamp = self.wallet.lnworker.invoices.get(key, (None, None, None))
|
||||||
if req is None:
|
if req is None:
|
||||||
self.update()
|
self.update()
|
||||||
return
|
return
|
||||||
|
@ -145,7 +146,9 @@ class RequestList(MyTreeView):
|
||||||
self.filter()
|
self.filter()
|
||||||
# lightning
|
# lightning
|
||||||
lnworker = self.wallet.lnworker
|
lnworker = self.wallet.lnworker
|
||||||
for key, (preimage_hex, invoice) in lnworker.invoices.items():
|
for key, (preimage_hex, invoice, direction, pay_timestamp) in lnworker.invoices.items():
|
||||||
|
if direction == SENT:
|
||||||
|
continue
|
||||||
status = lnworker.get_invoice_status(key)
|
status = lnworker.get_invoice_status(key)
|
||||||
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||||
amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
|
amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
|
||||||
|
@ -181,7 +184,7 @@ class RequestList(MyTreeView):
|
||||||
if request_type == REQUEST_TYPE_BITCOIN:
|
if request_type == REQUEST_TYPE_BITCOIN:
|
||||||
req = self.wallet.receive_requests.get(addr)
|
req = self.wallet.receive_requests.get(addr)
|
||||||
elif request_type == REQUEST_TYPE_LN:
|
elif request_type == REQUEST_TYPE_LN:
|
||||||
preimage, req = self.wallet.lnworker.invoices.get(addr)
|
preimage, req, direction, pay_timestamp = self.wallet.lnworker.invoices.get(addr)
|
||||||
if req is None:
|
if req is None:
|
||||||
self.update()
|
self.update()
|
||||||
return
|
return
|
||||||
|
|
|
@ -47,12 +47,6 @@ pr_icons = {
|
||||||
PR_INFLIGHT:"lightning.png",
|
PR_INFLIGHT:"lightning.png",
|
||||||
}
|
}
|
||||||
|
|
||||||
pr_tooltips = {
|
|
||||||
PR_UNPAID:_('Pending'),
|
|
||||||
PR_PAID:_('Paid'),
|
|
||||||
PR_EXPIRED:_('Expired'),
|
|
||||||
PR_INFLIGHT:_('Inflight')
|
|
||||||
}
|
|
||||||
|
|
||||||
expiration_values = [
|
expiration_values = [
|
||||||
(_('1 hour'), 60*60),
|
(_('1 hour'), 60*60),
|
||||||
|
|
|
@ -420,7 +420,7 @@ class Peer(PrintError):
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
|
async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
|
||||||
push_msat: int, temp_channel_id: bytes) -> Channel:
|
push_msat: int, temp_channel_id: bytes) -> Channel:
|
||||||
assert push_msat == 0, "push_msat not supported currently"
|
#assert push_msat == 0, "push_msat not supported currently"
|
||||||
wallet = self.lnworker.wallet
|
wallet = self.lnworker.wallet
|
||||||
# dry run creating funding tx to see if we even have enough funds
|
# dry run creating funding tx to see if we even have enough funds
|
||||||
funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)],
|
funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)],
|
||||||
|
@ -549,7 +549,7 @@ class Peer(PrintError):
|
||||||
raise Exception('wrong chain_hash')
|
raise Exception('wrong chain_hash')
|
||||||
funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
|
funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
|
||||||
push_msat = int.from_bytes(payload['push_msat'], 'big')
|
push_msat = int.from_bytes(payload['push_msat'], 'big')
|
||||||
assert push_msat == 0, "push_msat not supported currently"
|
#assert push_msat == 0, "push_msat not supported currently"
|
||||||
feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
|
feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
|
||||||
|
|
||||||
temp_chan_id = payload['temporary_channel_id']
|
temp_chan_id = payload['temporary_channel_id']
|
||||||
|
|
|
@ -171,6 +171,17 @@ class Channel(PrintError):
|
||||||
self.local_commitment = None
|
self.local_commitment = None
|
||||||
self.remote_commitment = None
|
self.remote_commitment = None
|
||||||
|
|
||||||
|
def get_payments(self):
|
||||||
|
out = {}
|
||||||
|
for subject in LOCAL, REMOTE:
|
||||||
|
log = self.hm.log[subject]
|
||||||
|
for htlc_id, htlc in log.get('adds', {}).items():
|
||||||
|
rhash = bh2u(htlc.payment_hash)
|
||||||
|
status = 'settled' if htlc_id in log.get('settles',{}) else 'inflight'
|
||||||
|
direction = SENT if subject is LOCAL else RECEIVED
|
||||||
|
out[rhash] = (self.channel_id, htlc, direction, status)
|
||||||
|
return out
|
||||||
|
|
||||||
def set_local_commitment(self, ctx):
|
def set_local_commitment(self, ctx):
|
||||||
ctn = extract_ctn_from_tx_and_chan(ctx, self)
|
ctn = extract_ctn_from_tx_and_chan(ctx, self)
|
||||||
assert self.signature_fits(ctx), (self.log[LOCAL])
|
assert self.signature_fits(ctx), (self.log[LOCAL])
|
||||||
|
|
|
@ -19,6 +19,7 @@ import dns.exception
|
||||||
|
|
||||||
from . import constants
|
from . import constants
|
||||||
from . import keystore
|
from . import keystore
|
||||||
|
from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
|
||||||
from .keystore import BIP32_KeyStore
|
from .keystore import BIP32_KeyStore
|
||||||
from .bitcoin import COIN
|
from .bitcoin import COIN
|
||||||
from .transaction import Transaction
|
from .transaction import Transaction
|
||||||
|
@ -66,10 +67,7 @@ class LNWorker(PrintError):
|
||||||
|
|
||||||
def __init__(self, wallet: 'Abstract_Wallet'):
|
def __init__(self, wallet: 'Abstract_Wallet'):
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
# invoices we are currently trying to pay (might be pending HTLCs on a commitment transaction)
|
self.invoices = self.wallet.storage.get('lightning_invoices', {}) # type: Dict[str, Tuple[str,str]] # RHASH -> (preimage, invoice, direction, pay_timestamp)
|
||||||
self.invoices = self.wallet.storage.get('lightning_invoices', {}) # type: Dict[str, Tuple[str,str]] # RHASH -> (preimage, invoice)
|
|
||||||
self.inflight = self.wallet.storage.get('lightning_payments_inflight', {}) # type: Dict[bytes, Tuple[str, Optional[int], str]]
|
|
||||||
self.completed = self.wallet.storage.get('lightning_payments_completed', {})
|
|
||||||
self.sweep_address = wallet.get_receiving_address()
|
self.sweep_address = wallet.get_receiving_address()
|
||||||
self.lock = threading.RLock()
|
self.lock = threading.RLock()
|
||||||
self.ln_keystore = self._read_ln_keystore()
|
self.ln_keystore = self._read_ln_keystore()
|
||||||
|
@ -122,73 +120,34 @@ class LNWorker(PrintError):
|
||||||
self.wallet.storage.write()
|
self.wallet.storage.write()
|
||||||
self.print_error('saved lightning gossip timestamp')
|
self.print_error('saved lightning gossip timestamp')
|
||||||
|
|
||||||
def payment_completed(self, chan, direction, htlc, preimage):
|
def payment_completed(self, chan, direction, htlc, _preimage):
|
||||||
assert type(direction) is Direction
|
|
||||||
key = bh2u(htlc.payment_hash)
|
|
||||||
chan_id = chan.channel_id
|
chan_id = chan.channel_id
|
||||||
|
key = bh2u(htlc.payment_hash)
|
||||||
|
if key not in self.invoices:
|
||||||
|
return
|
||||||
|
preimage, invoice, direction, timestamp = self.invoices.get(key)
|
||||||
if direction == SENT:
|
if direction == SENT:
|
||||||
assert htlc.payment_hash not in self.invoices
|
preimage = _preimage
|
||||||
self.inflight.pop(key)
|
now = time.time()
|
||||||
self.wallet.storage.put('lightning_payments_inflight', self.inflight)
|
self.invoices[key] = preimage, invoice, direction, now
|
||||||
if not preimage:
|
self.wallet.storage.put('lightning_invoices', self.invoices)
|
||||||
preimage, _addr = self.get_invoice(htlc.payment_hash)
|
|
||||||
tupl = (time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage), bh2u(chan_id))
|
|
||||||
self.completed[key] = tupl
|
|
||||||
self.wallet.storage.put('lightning_payments_completed', self.completed)
|
|
||||||
self.wallet.storage.write()
|
self.wallet.storage.write()
|
||||||
self.network.trigger_callback('ln_payment_completed', tupl[0], direction, htlc, preimage, chan_id)
|
self.network.trigger_callback('ln_payment_completed', now, direction, htlc, preimage, chan_id)
|
||||||
|
|
||||||
def get_invoice_status(self, key):
|
def get_invoice_status(self, payment_hash):
|
||||||
from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
|
if payment_hash not in self.invoices:
|
||||||
if key in self.completed:
|
|
||||||
return PR_PAID
|
|
||||||
elif key in self.inflight:
|
|
||||||
return PR_INFLIGHT
|
|
||||||
elif key in self.invoices:
|
|
||||||
return PR_UNPAID
|
|
||||||
else:
|
|
||||||
return PR_UNKNOWN
|
return PR_UNKNOWN
|
||||||
|
preimage, _addr, direction, timestamp = self.invoices.get(payment_hash)
|
||||||
|
if timestamp is None:
|
||||||
|
return PR_UNPAID
|
||||||
|
return PR_PAID
|
||||||
|
|
||||||
def _list_invoices(self, chan_id=None):
|
def get_payments(self):
|
||||||
invoices = dict(self.invoices)
|
# note: with AMP we will have several channels per payment
|
||||||
settled = []
|
out = {}
|
||||||
unsettled = []
|
for chan in self.channels.values():
|
||||||
inflight = []
|
out.update(chan.get_payments())
|
||||||
for date, direction, htlc, hex_preimage, hex_chan_id in self.completed.values():
|
return out
|
||||||
direction = Direction(direction)
|
|
||||||
if chan_id is not None:
|
|
||||||
if bfh(hex_chan_id) != chan_id:
|
|
||||||
continue
|
|
||||||
htlcobj = UpdateAddHtlc(*htlc)
|
|
||||||
if direction == RECEIVED:
|
|
||||||
preimage = bfh(invoices.pop(bh2u(htlcobj.payment_hash))[0])
|
|
||||||
else:
|
|
||||||
preimage = bfh(hex_preimage)
|
|
||||||
# FIXME use fromisoformat when minimum Python is 3.7
|
|
||||||
settled.append((datetime.fromtimestamp(date, timezone.utc), direction, htlcobj, preimage))
|
|
||||||
for preimage, pay_req in invoices.values():
|
|
||||||
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
|
|
||||||
unsettled.append((addr, bfh(preimage), pay_req))
|
|
||||||
for pay_req, amount_sat, this_chan_id in self.inflight.values():
|
|
||||||
if chan_id is not None and bfh(this_chan_id) != chan_id:
|
|
||||||
continue
|
|
||||||
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
|
|
||||||
if amount_sat is not None:
|
|
||||||
addr.amount = Decimal(amount_sat) / COIN
|
|
||||||
htlc = self.find_htlc_for_addr(addr, None if chan_id is None else [chan_id])
|
|
||||||
if not htlc:
|
|
||||||
self.print_error('Warning, in-flight HTLC not found in any channel')
|
|
||||||
inflight.append((addr, htlc, SENT))
|
|
||||||
# not adding received htlcs to inflight because they should have been settled
|
|
||||||
# immediatly and therefore let's not spend time trying to show it in the GUI
|
|
||||||
return {'settled': settled, 'unsettled': unsettled, 'inflight': inflight}
|
|
||||||
|
|
||||||
def find_htlc_for_addr(self, addr, whitelist=None):
|
|
||||||
channels = [y for x,y in self.channels.items() if whitelist is None or x in whitelist]
|
|
||||||
for chan in channels:
|
|
||||||
for htlc in chan.hm.log[LOCAL]['adds'].values():
|
|
||||||
if htlc.payment_hash == addr.paymenthash:
|
|
||||||
return htlc
|
|
||||||
|
|
||||||
def _read_ln_keystore(self) -> BIP32_KeyStore:
|
def _read_ln_keystore(self) -> BIP32_KeyStore:
|
||||||
xprv = self.wallet.storage.get('lightning_privkey2')
|
xprv = self.wallet.storage.get('lightning_privkey2')
|
||||||
|
@ -447,9 +406,6 @@ class LNWorker(PrintError):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
assert False, 'Found route with short channel ID we don\'t have: ' + repr(route[0].short_channel_id)
|
assert False, 'Found route with short channel ID we don\'t have: ' + repr(route[0].short_channel_id)
|
||||||
self.inflight[bh2u(addr.paymenthash)] = (invoice, amount_sat, bh2u(chan_id))
|
|
||||||
self.wallet.storage.put('lightning_payments_inflight', self.inflight)
|
|
||||||
self.wallet.storage.write()
|
|
||||||
return addr, peer, self._pay_to_route(route, addr)
|
return addr, peer, self._pay_to_route(route, addr)
|
||||||
|
|
||||||
async def _pay_to_route(self, route, addr):
|
async def _pay_to_route(self, route, addr):
|
||||||
|
@ -545,14 +501,20 @@ class LNWorker(PrintError):
|
||||||
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
|
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
|
||||||
+ routing_hints),
|
+ routing_hints),
|
||||||
self.node_keypair.privkey)
|
self.node_keypair.privkey)
|
||||||
self.invoices[bh2u(RHASH)] = (bh2u(payment_preimage), pay_req)
|
|
||||||
|
self.save_invoice(bh2u(payment_preimage), pay_req, RECEIVED)
|
||||||
|
return pay_req
|
||||||
|
|
||||||
|
def save_invoice(self, preimage, invoice, direction):
|
||||||
|
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
|
||||||
|
key = bh2u(lnaddr.paymenthash)
|
||||||
|
self.invoices[key] = preimage, invoice, direction, None
|
||||||
self.wallet.storage.put('lightning_invoices', self.invoices)
|
self.wallet.storage.put('lightning_invoices', self.invoices)
|
||||||
self.wallet.storage.write()
|
self.wallet.storage.write()
|
||||||
return pay_req
|
|
||||||
|
|
||||||
def get_invoice(self, payment_hash: bytes) -> Tuple[bytes, LnAddr]:
|
def get_invoice(self, payment_hash: bytes) -> Tuple[bytes, LnAddr]:
|
||||||
try:
|
try:
|
||||||
preimage_hex, pay_req = self.invoices[bh2u(payment_hash)]
|
preimage_hex, pay_req, direction,timestamp = self.invoices[bh2u(payment_hash)]
|
||||||
preimage = bfh(preimage_hex)
|
preimage = bfh(preimage_hex)
|
||||||
assert sha256(preimage) == payment_hash
|
assert sha256(preimage) == payment_hash
|
||||||
return preimage, lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
|
return preimage, lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
|
||||||
|
|
|
@ -80,6 +80,14 @@ PR_UNKNOWN = 2 # sent but not propagated
|
||||||
PR_PAID = 3 # send and propagated
|
PR_PAID = 3 # send and propagated
|
||||||
PR_INFLIGHT = 4 # lightning
|
PR_INFLIGHT = 4 # lightning
|
||||||
|
|
||||||
|
pr_tooltips = {
|
||||||
|
PR_UNPAID:_('Pending'),
|
||||||
|
PR_PAID:_('Paid'),
|
||||||
|
PR_UNKNOWN:_('Unknown'),
|
||||||
|
PR_EXPIRED:_('Expired'),
|
||||||
|
PR_INFLIGHT:_('Inflight')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class UnknownBaseUnit(Exception): pass
|
class UnknownBaseUnit(Exception): pass
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue