From 353361a05c0db0b7ecb4a8c1e21ab3172d7011c8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 26 Jun 2022 14:09:01 +0930 Subject: [PATCH] pytest: test to demonstrate BROKEN message on onchaind htlc close. We assumed that if the outgoing htlc has failed, we should have always failed the incoming one. This is not true, as this testcase demonstrates: ``` lightningd-2: 2022-05-25T04:38:31.449Z DEBUG 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-onchaind-chan#2: Sending 1 missing htlc messages lightningd-2: 2022-05-25T04:38:31.449Z DEBUG 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-chan#2: onchain_failed_our_htlc lightningd-2: 2022-05-25T04:38:31.449Z DEBUG 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-chan#2: HTLC id 0 failonion = 0x55cb8bc045d8, failmsg = (nil), preimage = (nil) lightningd-2: 2022-05-25T04:38:31.449Z **BROKEN** 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-chan#2: HTLC id 0 already complete, but ->in not resolved! failonion = 206816a391d3a7666ddd5213914cbb68f5da1fc4a0937e729de5a1990c94d26312caa5f2778e8da0c6bdefc68dd1a3bc28b5b5650fc0bdb3c2247ecca94ed0bbb224c8448c2c71eb1656a8740cadb301bd1ee1c1e774a8fef817352f502e4217b11e93aa6877b88b37afab0e4d4e49ed0385be9ab9a1ab1ac0e3460e41cfafb30ed896cea96e346041919a6c524ce56c3e5f27c7cd78a36b6df221e90a1c6e048c72b4146a5a51885fb70649037fe7ace77a016ae3ec8aee97960d0e5f0582713f671df79d8dee11b82708b6d882ee5adbb328db1938e824110f57ead1b27410bc6f775c7bb4ae40c1768d77a166c9bfda8f634ba0ac4f8a9fe199894dd3754c5ce41c9694544c805ffc177517661f11221dd8dffac60ce1c8c5bf54cda8e5ea44d8ec6b, failmsg = (null), preimage = (null) lightningd-2: 2022-05-25T04:38:31.449Z **BROKEN** 035d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d-chan#2: MISSING incoming fail for 0: failing incoming now lightningd-2: 2022-05-25T04:38:31.449Z **BROKEN** 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-chan#1: MISSED incoming fail for 0: failing now lightningd-2: 2022-05-25T04:38:31.449Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-chan#1: HTLC in 0 RCVD_ADD_ACK_REVOCATION->SENT_REMOVE_HTLC ``` Signed-off-by: Rusty Russell --- tests/test_pay.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_pay.py b/tests/test_pay.py index dc22f42e2..52e787184 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5265,3 +5265,48 @@ def test_pay_bolt11_metadata(node_factory, bitcoind): l1.rpc.pay(inv) l2.daemon.wait_for_log("Unexpected payment_metadata {}".format(b'this is metadata'.hex())) + + +@pytest.mark.xfail(strict=True) +@pytest.mark.developer("needs to dev-disconnect") +def test_pay_middle_fail(node_factory, bitcoind, executor): + """Test the case where a HTLC is failed, but not on peer's side, then + we go onchain""" + # Set feerates the same so we don't have update_fee interfering. + # We want to disconnect on revoke-and-ack we send for failing htlc. + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, + opts=[{'feerates': (1500,) * 4}, + {'feerates': (1500,) * 4}, + {'feerates': (1500,) * 4, + 'disconnect': ['-WIRE_REVOKE_AND_ACK*2']}]) + + chanid12 = only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['short_channel_id'] + chanid23 = only_one(l2.rpc.getpeer(l3.info['id'])['channels'])['short_channel_id'] + + # Make a failing payment. + route = [{'amount_msat': 1011, + 'id': l2.info['id'], + 'delay': 20, + 'channel': chanid12}, + {'amount_msat': 1000, + 'id': l3.info['id'], + 'delay': 10, + 'channel': chanid23}] + + # Start payment, it will fail. + l1.rpc.sendpay(route, payment_hash='00' * 32) + + wait_for(lambda: only_one(l3.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False) + + # After this (cltv is actually +11, and we give it 1 block grace) + # l2 will go onchain since HTLC is not resolved. + bitcoind.generate_block(12) + sync_blockheight(bitcoind, [l1, l2, l3]) + wait_for(lambda: only_one(only_one(l2.rpc.listpeers(l3.info['id'])['peers'])['channels'])['state'] == 'AWAITING_UNILATERAL') + + # Three blocks and it will resolve the parent. + bitcoind.generate_block(3, wait_for_mempool=1) + + # And that will fail upstream + with pytest.raises(RpcError, match=r'WIRE_PERMANENT_CHANNEL_FAILURE'): + l1.rpc.waitsendpay('00' * 32)