1
0

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:
ThomasV
2020-03-10 13:27:02 +01:00
parent 5d4f8f3164
commit 3d69f3b0be
6 changed files with 76 additions and 27 deletions

View File

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