Browse Source

Trampoline MPP: save fee level in sent_htlcs_info.

If multiple HTLCs fail at the same fee level with
TRAMPOLINE_INSUFFICIENT_FEE, bump trampoline_fee_level only once.
patch-4
ThomasV 3 years ago
parent
commit
4ebe41b3a7
  1. 1
      electrum/lnutil.py
  2. 26
      electrum/lnworker.py
  3. 8
      electrum/tests/test_lnpeer.py

1
electrum/lnutil.py

@ -323,6 +323,7 @@ class HtlcLog(NamedTuple):
error_bytes: Optional[bytes] = None
failure_msg: Optional['OnionRoutingFailure'] = None
sender_idx: Optional[int] = None
trampoline_fee_level: Optional[int] = None
def formatted_tuple(self):
route = self.route

26
electrum/lnworker.py

@ -634,7 +634,7 @@ class LNWallet(LNWorker):
self._channel_backups[bfh(channel_id)] = ChannelBackup(storage, sweep_address=self.sweep_address, lnworker=self)
self.sent_htlcs = defaultdict(asyncio.Queue) # type: Dict[bytes, asyncio.Queue[HtlcLog]]
self.sent_htlcs_routes = dict() # (RHASH, scid, htlc_id) -> route, payment_secret, amount_msat, bucket_msat
self.sent_htlcs_info = dict() # (RHASH, scid, htlc_id) -> route, payment_secret, amount_msat, bucket_msat, trampoline_fee_level
self.sent_buckets = dict() # payment_secret -> (amount_sent, amount_failed)
self.received_mpp_htlcs = dict() # RHASH -> mpp_status, htlc_set
@ -1199,7 +1199,8 @@ class LNWallet(LNWorker):
payment_hash=payment_hash,
payment_secret=bucket_payment_secret,
min_cltv_expiry=cltv_delta,
trampoline_onion=trampoline_onion)
trampoline_onion=trampoline_onion,
trampoline_fee_level=trampoline_fee_level)
util.trigger_callback('invoice_status', self.wallet, payment_hash.hex())
# 3. await a queue
self.logger.info(f"amount inflight {amount_inflight}")
@ -1244,7 +1245,11 @@ class LNWallet(LNWorker):
# TODO: parse the node policy here (not returned by eclair yet)
# TODO: erring node is always the first trampoline even if second
# trampoline demands more fees, we can't influence this
trampoline_fee_level += 1
if htlc_log.trampoline_fee_level == trampoline_fee_level:
trampoline_fee_level += 1
self.logger.info(f'raising trampoline fee level {trampoline_fee_level}')
else:
self.logger.info(f'NOT raising trampoline fee level, already at {trampoline_fee_level}')
continue
elif use_two_trampolines:
use_two_trampolines = False
@ -1266,7 +1271,8 @@ class LNWallet(LNWorker):
payment_hash: bytes,
payment_secret: Optional[bytes],
min_cltv_expiry: int,
trampoline_onion: bytes = None) -> None:
trampoline_onion: bytes = None,
trampoline_fee_level: int) -> None:
# send a single htlc
short_channel_id = route[0].short_channel_id
@ -1286,7 +1292,7 @@ class LNWallet(LNWorker):
trampoline_onion=trampoline_onion)
key = (payment_hash, short_channel_id, htlc.htlc_id)
self.sent_htlcs_routes[key] = route, payment_secret, amount_msat, total_msat, amount_receiver_msat
self.sent_htlcs_info[key] = route, payment_secret, amount_msat, total_msat, amount_receiver_msat, trampoline_fee_level
# if we sent MPP to a trampoline, add item to sent_buckets
if not self.channel_db and amount_msat != total_msat:
if payment_secret not in self.sent_buckets:
@ -1907,11 +1913,12 @@ class LNWallet(LNWorker):
self._on_maybe_forwarded_htlc_resolved(chan=chan, htlc_id=htlc_id)
q = self.sent_htlcs.get(payment_hash)
if q:
route, payment_secret, amount_msat, bucket_msat, amount_receiver_msat = self.sent_htlcs_routes[(payment_hash, chan.short_channel_id, htlc_id)]
route, payment_secret, amount_msat, bucket_msat, amount_receiver_msat, trampoline_fee_level = self.sent_htlcs_info[(payment_hash, chan.short_channel_id, htlc_id)]
htlc_log = HtlcLog(
success=True,
route=route,
amount_msat=amount_receiver_msat)
amount_msat=amount_receiver_msat,
trampoline_fee_level=trampoline_fee_level)
q.put_nowait(htlc_log)
else:
key = payment_hash.hex()
@ -1933,7 +1940,7 @@ class LNWallet(LNWorker):
# detect if it is part of a bucket
# if yes, wait until the bucket completely failed
key = (payment_hash, chan.short_channel_id, htlc_id)
route, payment_secret, amount_msat, bucket_msat, amount_receiver_msat = self.sent_htlcs_routes[key]
route, payment_secret, amount_msat, bucket_msat, amount_receiver_msat, trampoline_fee_level = self.sent_htlcs_info[key]
if error_bytes:
# TODO "decode_onion_error" might raise, catch and maybe blacklist/penalise someone?
try:
@ -1964,7 +1971,8 @@ class LNWallet(LNWorker):
amount_msat=amount_receiver_msat,
error_bytes=error_bytes,
failure_msg=failure_message,
sender_idx=sender_idx)
sender_idx=sender_idx,
trampoline_fee_level=trampoline_fee_level)
q.put_nowait(htlc_log)
else:
self.logger.info(f"received unknown htlc_failed, probably from previous session")

8
electrum/tests/test_lnpeer.py

@ -147,7 +147,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
self.enable_htlc_forwarding = True
self.received_mpp_htlcs = dict()
self.sent_htlcs = defaultdict(asyncio.Queue)
self.sent_htlcs_routes = dict()
self.sent_htlcs_info = dict()
self.sent_buckets = defaultdict(set)
self.trampoline_forwarding_failures = {}
self.inflight_payments = set()
@ -613,6 +613,7 @@ class TestPeer(TestCaseForTestnet):
payment_hash=lnaddr2.paymenthash,
min_cltv_expiry=lnaddr2.get_min_final_cltv_expiry(),
payment_secret=lnaddr2.payment_secret,
trampoline_fee_level=0,
)
p1.maybe_send_commitment = _maybe_send_commitment1
# bob sends htlc BUT NOT COMMITMENT_SIGNED
@ -627,6 +628,7 @@ class TestPeer(TestCaseForTestnet):
payment_hash=lnaddr1.paymenthash,
min_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(),
payment_secret=lnaddr1.payment_secret,
trampoline_fee_level=0,
)
p2.maybe_send_commitment = _maybe_send_commitment2
# sleep a bit so that they both receive msgs sent so far
@ -1193,7 +1195,9 @@ class TestPeer(TestCaseForTestnet):
amount_receiver_msat=amount_msat,
payment_hash=payment_hash,
payment_secret=payment_secret,
min_cltv_expiry=min_cltv_expiry)
min_cltv_expiry=min_cltv_expiry,
trampoline_fee_level=0,
)
await asyncio.gather(pay, p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
with self.assertRaises(PaymentFailure):
run(f())

Loading…
Cancel
Save