|
62 | 62 | LnKeyFamily, LOCAL, REMOTE, MIN_FINAL_CLTV_DELTA_FOR_INVOICE, SENT, RECEIVED, HTLCOwner, UpdateAddHtlc, LnFeatures,
|
63 | 63 | ShortChannelID, HtlcLog, NoPathFound, InvalidGossipMsg, FeeBudgetExceeded, ImportedChannelBackupStorage,
|
64 | 64 | OnchainChannelBackupStorage, ln_compare_features, IncompatibleLightningFeatures, PaymentFeeBudget,
|
65 |
| - NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE, GossipForwardingMessage, MIN_FUNDING_SAT |
| 65 | + NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE, GossipForwardingMessage, MIN_FUNDING_SAT, MIN_FINAL_CLTV_DELTA_ACCEPTED |
66 | 66 | )
|
67 | 67 | from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket
|
68 | 68 | from .lnmsg import decode_msg
|
@@ -885,6 +885,13 @@ def __init__(self, wallet: 'Abstract_Wallet', xprv):
|
885 | 885 |
|
886 | 886 | # payment_hash -> callback:
|
887 | 887 | self.hold_invoice_callbacks = {} # type: Dict[bytes, Callable[[bytes], Awaitable[None]]]
|
| 888 | + self.cli_hold_invoice_callbacks = self.db.get_dict('cli_hold_invoice_cbs') # type: Dict[str, Tuple[Optional[str], int]] # payment_hash -> (callback_url, expiry_ts) |
| 889 | + for payment_hash, (callback_url, expiry_ts) in list(self.cli_hold_invoice_callbacks.items()): |
| 890 | + if expiry_ts < time.time(): |
| 891 | + # delete old callbacks that are not going to get called anymore |
| 892 | + del self.cli_hold_invoice_callbacks[payment_hash] |
| 893 | + else: # re-register the callback |
| 894 | + self.register_cli_hold_invoice(payment_hash, expiry_ts, callback_url) |
888 | 895 | self.payment_bundles = [] # lists of hashes. todo:persist
|
889 | 896 |
|
890 | 897 | self.nostr_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NOSTR_KEY)
|
@@ -2308,15 +2315,45 @@ def get_payment_info(self, payment_hash: bytes) -> Optional[PaymentInfo]:
|
2308 | 2315 | amount_msat, direction, status = self.payment_info[key]
|
2309 | 2316 | return PaymentInfo(payment_hash, amount_msat, direction, status)
|
2310 | 2317 |
|
2311 |
| - def add_payment_info_for_hold_invoice(self, payment_hash: bytes, lightning_amount_sat: int): |
2312 |
| - info = PaymentInfo(payment_hash, lightning_amount_sat * 1000, RECEIVED, PR_UNPAID) |
| 2318 | + def add_payment_info_for_hold_invoice(self, payment_hash: bytes, lightning_amount_sat: Optional[int]): |
| 2319 | + amount = lightning_amount_sat * 1000 if lightning_amount_sat else None |
| 2320 | + info = PaymentInfo(payment_hash, amount, RECEIVED, PR_UNPAID) |
2313 | 2321 | self.save_payment_info(info, write_to_disk=False)
|
2314 | 2322 |
|
2315 | 2323 | def register_hold_invoice(self, payment_hash: bytes, cb: Callable[[bytes], Awaitable[None]]):
|
2316 | 2324 | self.hold_invoice_callbacks[payment_hash] = cb
|
2317 | 2325 |
|
2318 | 2326 | def unregister_hold_invoice(self, payment_hash: bytes):
|
2319 | 2327 | self.hold_invoice_callbacks.pop(payment_hash)
|
| 2328 | + self.cli_hold_invoice_callbacks.pop(payment_hash.hex(), None) |
| 2329 | + |
| 2330 | + def register_cli_hold_invoice( |
| 2331 | + self, |
| 2332 | + payment_hash: str, |
| 2333 | + expiry_ts: int, |
| 2334 | + callback_url: Optional[str] = None, |
| 2335 | + ) -> None: |
| 2336 | + async def cli_hold_invoice_callback(payment_hash_bytes: bytes): |
| 2337 | + """Hold invoice callback for hold invoices registered via CLI.""" |
| 2338 | + self.logger.debug(f"Hold invoice {payment_hash_bytes.hex()} ready for settlement") |
| 2339 | + self.set_payment_status(payment_hash_bytes, PR_PAID) |
| 2340 | + if not callback_url: |
| 2341 | + return |
| 2342 | + amount_sat = (self.get_payment_mpp_amount_msat(payment_hash_bytes) or 0) // 1000 |
| 2343 | + data = { |
| 2344 | + "payment_hash": payment_hash_bytes.hex(), |
| 2345 | + "amount_sat": amount_sat |
| 2346 | + } |
| 2347 | + try: |
| 2348 | + async with make_aiohttp_session(proxy=None) as s: |
| 2349 | + await s.post(callback_url, json=data, raise_for_status=False) |
| 2350 | + except Exception as e: |
| 2351 | + self.logger.debug(f"hold invoice callback request to {callback_url} raised: {str(e)}") |
| 2352 | + |
| 2353 | + self.register_hold_invoice(bfh(payment_hash), cli_hold_invoice_callback) |
| 2354 | + if payment_hash not in self.cli_hold_invoice_callbacks: |
| 2355 | + # persist the callback so it can be re-registered after restart |
| 2356 | + self.cli_hold_invoice_callbacks[payment_hash] = (callback_url, expiry_ts) |
2320 | 2357 |
|
2321 | 2358 | def save_payment_info(self, info: PaymentInfo, *, write_to_disk: bool = True) -> None:
|
2322 | 2359 | key = info.payment_hash.hex()
|
@@ -2402,17 +2439,34 @@ def set_mpp_resolution(self, *, payment_key: bytes, resolution: RecvMPPResolutio
|
2402 | 2439 | self.received_mpp_htlcs[payment_key.hex()] = mpp_status._replace(resolution=resolution)
|
2403 | 2440 |
|
2404 | 2441 | def is_mpp_amount_reached(self, payment_key: bytes) -> bool:
|
2405 |
| - mpp_status = self.received_mpp_htlcs.get(payment_key.hex()) |
2406 |
| - if not mpp_status: |
| 2442 | + amounts = self.get_mpp_amounts(payment_key) |
| 2443 | + if amounts is None: |
2407 | 2444 | return False
|
2408 |
| - total = sum([_htlc.amount_msat for scid, _htlc in mpp_status.htlc_set]) |
2409 |
| - return total >= mpp_status.expected_msat |
| 2445 | + total, expected = amounts |
| 2446 | + return total >= expected |
2410 | 2447 |
|
2411 | 2448 | def is_accepted_mpp(self, payment_hash: bytes) -> bool:
|
2412 | 2449 | payment_key = self._get_payment_key(payment_hash)
|
2413 | 2450 | status = self.received_mpp_htlcs.get(payment_key.hex())
|
2414 | 2451 | return status and status.resolution == RecvMPPResolution.ACCEPTED
|
2415 | 2452 |
|
| 2453 | + def get_payment_mpp_amount_msat(self, payment_hash: bytes) -> Optional[int]: |
| 2454 | + """Returns the received mpp amount for given payment hash.""" |
| 2455 | + payment_key = self._get_payment_key(payment_hash) |
| 2456 | + amounts = self.get_mpp_amounts(payment_key) |
| 2457 | + if not amounts: |
| 2458 | + return None |
| 2459 | + total_msat, _ = amounts |
| 2460 | + return total_msat |
| 2461 | + |
| 2462 | + def get_mpp_amounts(self, payment_key: bytes) -> Optional[Tuple[int, int]]: |
| 2463 | + """Returns (total received amount, expected amount) or None.""" |
| 2464 | + mpp_status = self.received_mpp_htlcs.get(payment_key.hex()) |
| 2465 | + if not mpp_status: |
| 2466 | + return None |
| 2467 | + total = sum([_htlc.amount_msat for scid, _htlc in mpp_status.htlc_set]) |
| 2468 | + return total, mpp_status.expected_msat |
| 2469 | + |
2416 | 2470 | def get_first_timestamp_of_mpp(self, payment_key: bytes) -> int:
|
2417 | 2471 | mpp_status = self.received_mpp_htlcs.get(payment_key.hex())
|
2418 | 2472 | if not mpp_status:
|
|
0 commit comments