1
0

Merge pull request #10325 from f321x/debug_not_enough_balance

lightning: fix race when doing concurrent payments
This commit is contained in:
ghost43
2025-11-27 16:06:52 +00:00
committed by GitHub
2 changed files with 26 additions and 18 deletions

View File

@@ -914,6 +914,7 @@ class LNWallet(LNWorker):
self._paysessions = dict() # type: Dict[bytes, PaySession] self._paysessions = dict() # type: Dict[bytes, PaySession]
self.sent_htlcs_info = dict() # type: Dict[SentHtlcKey, SentHtlcInfo] self.sent_htlcs_info = dict() # type: Dict[SentHtlcKey, SentHtlcInfo]
self.received_mpp_htlcs = self.db.get_dict('received_mpp_htlcs') # type: Dict[str, ReceivedMPPStatus] # payment_key -> ReceivedMPPStatus self.received_mpp_htlcs = self.db.get_dict('received_mpp_htlcs') # type: Dict[str, ReceivedMPPStatus] # payment_key -> ReceivedMPPStatus
self._channel_sending_capacity_lock = asyncio.Lock()
# detect inflight payments # detect inflight payments
self.inflight_payments = set() # (not persisted) keys of invoices that are in PR_INFLIGHT state self.inflight_payments = set() # (not persisted) keys of invoices that are in PR_INFLIGHT state
@@ -1698,10 +1699,16 @@ class LNWallet(LNWorker):
try: try:
while True: while True:
if (amount_to_send := paysession.get_outstanding_amount_to_send()) > 0: if (amount_to_send := paysession.get_outstanding_amount_to_send()) > 0:
remaining_fee_budget_msat = (budget.fee_msat * amount_to_send) // amount_to_pay
# splitting the amount of the payment between our channels requires the correct
# available channel balance. to prevent concurrent splitting attempts from
# using stale channel balances for the split calculation a lock needs to be
# taken until the htlcs are added to the channel so the next splitting attempt
# acts on a correct channel balance.
async with self._channel_sending_capacity_lock:
# 1. create a set of routes for remaining amount. # 1. create a set of routes for remaining amount.
# note: path-finding runs in a separate thread so that we don't block the asyncio loop # note: path-finding runs in a separate thread so that we don't block the asyncio loop
# graph updates might occur during the computation # graph updates might occur during the computation
remaining_fee_budget_msat = (budget.fee_msat * amount_to_send) // amount_to_pay
routes = self.create_routes_for_payment( routes = self.create_routes_for_payment(
paysession=paysession, paysession=paysession,
amount_msat=amount_to_send, amount_msat=amount_to_send,

View File

@@ -218,6 +218,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
self._payment_bundles_pkey_to_canon = {} # type: Dict[bytes, bytes] self._payment_bundles_pkey_to_canon = {} # type: Dict[bytes, bytes]
self._payment_bundles_canon_to_pkeylist = {} # type: Dict[bytes, Sequence[bytes]] self._payment_bundles_canon_to_pkeylist = {} # type: Dict[bytes, Sequence[bytes]]
self.config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0 self.config.INITIAL_TRAMPOLINE_FEE_LEVEL = 0
self._channel_sending_capacity_lock = asyncio.Lock()
self.logger.info(f"created LNWallet[{name}] with nodeID={local_keypair.pubkey.hex()}") self.logger.info(f"created LNWallet[{name}] with nodeID={local_keypair.pubkey.hex()}")