1
0

Trampoline: Remember failed routes (fixes #7967).

Upon receiving UNKNOWN_NEXT_PEER, TEMPORARY_NODE_FAILURE
or TEMPORARY_CHANNEL_FAILURE, remember the trampoline route
that was used, and try other routes before raising the
trampoline fee.

Before this commit, we used to raise the trampoline fee
upon receiving any error message, in order to ensure
termination of the payment loop.

Note that we will still retry failing routes after we have
raised the trampoline fee. This choice is questionable, it
is unclear if doing so significantly increases the probability
of success.

Tests: add a test for trampoline handling of UNKNOWN_NEXT_PEER
This commit is contained in:
ThomasV
2022-09-09 19:52:36 +02:00
parent a488be61db
commit 68bf714ae6
4 changed files with 110 additions and 43 deletions

View File

@@ -49,8 +49,8 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
local_amount, remote_amount, privkeys, other_pubkeys,
seed, cur, nex, other_node_id, l_dust, r_dust, l_csv,
r_csv):
assert local_amount > 0
assert remote_amount > 0
#assert local_amount > 0
#assert remote_amount > 0
channel_id, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index)
state = {
"channel_id":channel_id.hex(),

View File

@@ -331,6 +331,15 @@ low_fee_channel = {
'remote_fee_rate_millionths': 1,
}
depleted_channel = {
'local_balance_msat': 0,
'remote_balance_msat': 10 * bitcoin.COIN * 1000,
'local_base_fee_msat': 1_000,
'local_fee_rate_millionths': 1,
'remote_base_fee_msat': 1_000,
'remote_fee_rate_millionths': 1,
}
GRAPH_DEFINITIONS = {
'square_graph': {
'alice': {
@@ -761,6 +770,7 @@ class TestPeer(TestCaseForTestnet):
min_cltv_expiry=lnaddr2.get_min_final_cltv_expiry(),
payment_secret=lnaddr2.payment_secret,
trampoline_fee_level=0,
trampoline_route=None,
)
p1.maybe_send_commitment = _maybe_send_commitment1
# bob sends htlc BUT NOT COMMITMENT_SIGNED
@@ -776,6 +786,7 @@ class TestPeer(TestCaseForTestnet):
min_cltv_expiry=lnaddr1.get_min_final_cltv_expiry(),
payment_secret=lnaddr1.payment_secret,
trampoline_fee_level=0,
trampoline_route=None,
)
p2.maybe_send_commitment = _maybe_send_commitment2
# sleep a bit so that they both receive msgs sent so far
@@ -1081,7 +1092,7 @@ class TestPeer(TestCaseForTestnet):
graph = self.prepare_chans_and_peers_in_graph(GRAPH_DEFINITIONS['square_graph'])
self._run_mpp(graph, {'mpp_invoice': False}, {'mpp_invoice': True})
def _run_trampoline_payment(self, is_legacy):
def _run_trampoline_payment(self, is_legacy, direct, drop_dave= []):
async def turn_on_trampoline_alice():
if graph.workers['alice'].network.channel_db:
graph.workers['alice'].network.channel_db.stop()
@@ -1091,9 +1102,16 @@ class TestPeer(TestCaseForTestnet):
async def pay(lnaddr, pay_req):
self.assertEqual(PR_UNPAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
result, log = await graph.workers['alice'].pay_invoice(pay_req, attempts=10)
self.assertTrue(result)
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
raise PaymentDone()
if result:
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
raise PaymentDone()
else:
raise NoPathFound()
def do_drop_dave(t):
# this will trigger UNKNOWN_NEXT_PEER
dave_node_id = graph.workers['dave'].node_keypair.pubkey
graph.workers[t].peers.pop(dave_node_id)
async def f():
await turn_on_trampoline_alice()
@@ -1103,16 +1121,21 @@ class TestPeer(TestCaseForTestnet):
await group.spawn(peer.htlc_switch())
await asyncio.sleep(0.2)
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=True)
for p in drop_dave:
do_drop_dave(p)
await group.spawn(pay(lnaddr, pay_req))
graph_definition = GRAPH_DEFINITIONS['square_graph'].copy()
# insert a channel from bob to carol for faster tests,
# otherwise will fail randomly
graph_definition['bob']['channels']['carol'] = high_fee_channel
if not direct:
# deplete channel from alice to carol
graph_definition['alice']['channels']['carol'] = depleted_channel
# insert a channel from bob to carol
graph_definition['bob']['channels']['carol'] = high_fee_channel
graph = self.prepare_chans_and_peers_in_graph(graph_definition)
peers = graph.peers.values()
if is_legacy:
# turn off trampoline features
# turn off trampoline features in invoice
graph.workers['dave'].features = graph.workers['dave'].features ^ LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT
# declare routing nodes as trampoline nodes
@@ -1121,16 +1144,26 @@ class TestPeer(TestCaseForTestnet):
graph.workers['carol'].name: LNPeerAddr(host="127.0.0.1", port=9735, pubkey=graph.workers['carol'].node_keypair.pubkey),
}
run(f())
@needs_test_with_all_chacha20_implementations
def test_payment_trampoline_legacy(self):
with self.assertRaises(PaymentDone):
run(f())
self._run_trampoline_payment(is_legacy=True, direct=False)
@needs_test_with_all_chacha20_implementations
def test_trampoline_payment_legacy(self):
self._run_trampoline_payment(True)
def test_payment_trampoline_e2e_direct(self):
with self.assertRaises(PaymentDone):
self._run_trampoline_payment(is_legacy=False, direct=True)
@needs_test_with_all_chacha20_implementations
def test_trampoline_payment_e2e(self):
self._run_trampoline_payment(False)
def test_payment_trampoline_e2e_indirect(self):
# must use two trampolines
with self.assertRaises(PaymentDone):
self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob'])
# both trampolines drop dave
with self.assertRaises(NoPathFound):
self._run_trampoline_payment(is_legacy=False, direct=False, drop_dave=['bob', 'carol'])
@needs_test_with_all_chacha20_implementations
def test_payment_multipart_trampoline_e2e(self):
@@ -1412,6 +1445,7 @@ class TestPeer(TestCaseForTestnet):
payment_secret=payment_secret,
min_cltv_expiry=min_cltv_expiry,
trampoline_fee_level=0,
trampoline_route=None,
)
await asyncio.gather(pay, p1._message_loop(), p2._message_loop(), p1.htlc_switch(), p2.htlc_switch())
with self.assertRaises(PaymentFailure):