improve payment status callbacks:
- add 'computing route' status for lightning payments - use separate callbacks for invoice status and payment popups - show payment error and payment logs in kivy
This commit is contained in:
@@ -24,7 +24,7 @@ from aiorpcx import run_in_thread
|
||||
from . import constants
|
||||
from . import keystore
|
||||
from .util import profiler
|
||||
from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED
|
||||
from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING
|
||||
from .util import PR_TYPE_LN
|
||||
from .lnutil import LN_MAX_FUNDING_SAT
|
||||
from .keystore import BIP32_KeyStore
|
||||
@@ -69,6 +69,9 @@ if TYPE_CHECKING:
|
||||
from .wallet import Abstract_Wallet
|
||||
|
||||
|
||||
SAVED_PR_STATUS = [PR_PAID, PR_UNPAID, PR_INFLIGHT] # status that are persisted
|
||||
|
||||
|
||||
NUM_PEERS_TARGET = 4
|
||||
PEER_RETRY_INTERVAL = 600 # seconds
|
||||
PEER_RETRY_INTERVAL_FOR_CHANNELS = 30 # seconds
|
||||
@@ -421,7 +424,8 @@ class LNWallet(LNWorker):
|
||||
self.preimages = self.db.get_dict('lightning_preimages') # RHASH -> preimage
|
||||
self.sweep_address = wallet.get_receiving_address()
|
||||
self.lock = threading.RLock()
|
||||
self.logs = defaultdict(list) # type: Dict[str, List[PaymentAttemptLog]] # key is RHASH
|
||||
self.logs = defaultdict(list) # (not persisted) type: Dict[str, List[PaymentAttemptLog]] # key is RHASH
|
||||
self.is_routing = set() # (not persisted) keys of invoices that are in PR_ROUTING state
|
||||
# used in tests
|
||||
self.enable_htlc_settle = asyncio.Event()
|
||||
self.enable_htlc_settle.set()
|
||||
@@ -920,25 +924,35 @@ class LNWallet(LNWorker):
|
||||
self.wallet.set_label(key, lnaddr.get_description())
|
||||
log = self.logs[key]
|
||||
success = False
|
||||
reason = ''
|
||||
for i in range(attempts):
|
||||
try:
|
||||
# note: this call does path-finding which takes ~1 second
|
||||
# -> we will BLOCK the asyncio loop... (could just run in a thread and await,
|
||||
# but then the graph could change while the path-finding runs on it)
|
||||
self.set_invoice_status(key, PR_ROUTING)
|
||||
self.network.trigger_callback('invoice_status', key)
|
||||
route = self._create_route_from_invoice(decoded_invoice=lnaddr)
|
||||
self.set_payment_status(payment_hash, PR_INFLIGHT)
|
||||
self.set_invoice_status(key, PR_INFLIGHT)
|
||||
self.network.trigger_callback('invoice_status', key)
|
||||
payment_attempt_log = await self._pay_to_route(route, lnaddr)
|
||||
except Exception as e:
|
||||
log.append(PaymentAttemptLog(success=False, exception=e))
|
||||
self.set_payment_status(payment_hash, PR_UNPAID)
|
||||
self.set_invoice_status(key, PR_UNPAID)
|
||||
reason = str(e)
|
||||
break
|
||||
log.append(payment_attempt_log)
|
||||
success = payment_attempt_log.success
|
||||
if success:
|
||||
break
|
||||
self.logger.debug(f'payment attempts log for RHASH {key}: {repr(log)}')
|
||||
else:
|
||||
reason = 'failed after %d attempts' % attemps
|
||||
self.network.trigger_callback('invoice_status', key)
|
||||
if success:
|
||||
self.network.trigger_callback('payment_succeeded', key)
|
||||
else:
|
||||
self.network.trigger_callback('payment_failed', key, reason)
|
||||
self.logger.debug(f'payment attempts log for RHASH {key}: {repr(log)}')
|
||||
return success
|
||||
|
||||
async def _pay_to_route(self, route: LNPaymentRoute, lnaddr: LnAddr) -> PaymentAttemptLog:
|
||||
@@ -1038,6 +1052,7 @@ class LNWallet(LNWorker):
|
||||
f"min_final_cltv_expiry: {addr.get_min_final_cltv_expiry()}"))
|
||||
return addr
|
||||
|
||||
@profiler
|
||||
def _create_route_from_invoice(self, decoded_invoice) -> LNPaymentRoute:
|
||||
amount_msat = int(decoded_invoice.amount * COIN * 1000)
|
||||
invoice_pubkey = decoded_invoice.pubkey.serialize()
|
||||
@@ -1170,7 +1185,7 @@ class LNWallet(LNWorker):
|
||||
|
||||
def save_payment_info(self, info: PaymentInfo) -> None:
|
||||
key = info.payment_hash.hex()
|
||||
assert info.status in [PR_PAID, PR_UNPAID, PR_INFLIGHT]
|
||||
assert info.status in SAVED_PR_STATUS
|
||||
with self.lock:
|
||||
self.payments[key] = info.amount, info.direction, info.status
|
||||
self.wallet.save_db()
|
||||
@@ -1184,19 +1199,29 @@ class LNWallet(LNWorker):
|
||||
return status
|
||||
|
||||
def get_invoice_status(self, key):
|
||||
log = self.logs[key]
|
||||
if key in self.is_routing:
|
||||
return PR_ROUTING
|
||||
# status may be PR_FAILED
|
||||
status = self.get_payment_status(bfh(key))
|
||||
log = self.logs[key]
|
||||
if status == PR_UNPAID and log:
|
||||
status = PR_FAILED
|
||||
return status
|
||||
|
||||
def set_invoice_status(self, key, status):
|
||||
if status == PR_ROUTING:
|
||||
self.is_routing.add(key)
|
||||
elif key in self.is_routing:
|
||||
self.is_routing.remove(key)
|
||||
if status in SAVED_PR_STATUS:
|
||||
self.save_payment_status(bfh(key), status)
|
||||
|
||||
async def await_payment(self, payment_hash):
|
||||
success, preimage, reason = await self.pending_payments[payment_hash]
|
||||
self.pending_payments.pop(payment_hash)
|
||||
return success, preimage, reason
|
||||
|
||||
def set_payment_status(self, payment_hash: bytes, status):
|
||||
def save_payment_status(self, payment_hash: bytes, status):
|
||||
try:
|
||||
info = self.get_payment_info(payment_hash)
|
||||
except UnknownPaymentHash:
|
||||
@@ -1206,27 +1231,29 @@ class LNWallet(LNWorker):
|
||||
self.save_payment_info(info)
|
||||
|
||||
def payment_failed(self, chan, payment_hash: bytes, reason):
|
||||
self.set_payment_status(payment_hash, PR_UNPAID)
|
||||
self.save_payment_status(payment_hash, PR_UNPAID)
|
||||
f = self.pending_payments.get(payment_hash)
|
||||
if f and not f.cancelled():
|
||||
f.set_result((False, None, reason))
|
||||
else:
|
||||
chan.logger.info('received unexpected payment_failed, probably from previous session')
|
||||
self.network.trigger_callback('invoice_status', payment_hash.hex())
|
||||
self.network.trigger_callback('invoice_status', key)
|
||||
self.network.trigger_callback('payment_failed', payment_hash.hex())
|
||||
|
||||
def payment_sent(self, chan, payment_hash: bytes):
|
||||
self.set_payment_status(payment_hash, PR_PAID)
|
||||
self.save_payment_status(payment_hash, PR_PAID)
|
||||
preimage = self.get_preimage(payment_hash)
|
||||
f = self.pending_payments.get(payment_hash)
|
||||
if f and not f.cancelled():
|
||||
f.set_result((True, preimage, None))
|
||||
else:
|
||||
chan.logger.info('received unexpected payment_sent, probably from previous session')
|
||||
self.network.trigger_callback('invoice_status', payment_hash.hex())
|
||||
self.network.trigger_callback('invoice_status', key)
|
||||
self.network.trigger_callback('payment_succeeded', payment_hash.hex())
|
||||
self.network.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
|
||||
|
||||
def payment_received(self, chan, payment_hash: bytes):
|
||||
self.set_payment_status(payment_hash, PR_PAID)
|
||||
self.save_payment_status(payment_hash, PR_PAID)
|
||||
self.network.trigger_callback('request_status', payment_hash.hex(), PR_PAID)
|
||||
self.network.trigger_callback('ln_payment_completed', payment_hash, chan.channel_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user