From 27c4e926bdcf862cf808628b94dd6ce2dffa4035 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 7 Mar 2018 10:36:56 +1030 Subject: [PATCH] test_lightningd.py: test case where onchaind restarts with different fees. This was revealed in #1114; onchaind isn't actually completely idempotent due to fee changes (and the now-fixed change in keys used). This triggers the bug by restarting with different fees, resulting in onchaind not recognizing its own proposal: 2018-03-05T09:38:15.550Z lightningd(23076): lightning_onchaind-022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59 chan #1: STATUS_FAIL_INTERNAL_ERROR: THEIR_UNILATERAL/OUR_HTLC spent with weird witness 3 Signed-off-by: Rusty Russell --- tests/test_lightningd.py | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index d7c03a845..8865b4fc3 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -1663,6 +1663,80 @@ class LightningDTests(BaseLightningDTests): # FIXME: Test wallet balance... wait_forget_channels(l2) + @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") + def test_onchain_feechange(self): + """Onchain handling when we restart with different fees""" + # HTLC 1->2, 2 fails just after they're both irrevocably committed + # We need 2 to drop to chain, because then 1's HTLC timeout tx + # is generated on-the-fly, and is thus feerate sensitive. + disconnects = ['-WIRE_UPDATE_FAIL_HTLC', 'permfail'] + l1 = self.node_factory.get_node() + l2 = self.node_factory.get_node(disconnect=disconnects) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port']) + self.fund_channel(l1, l2, 10**6) + + rhash = l2.rpc.invoice(10**8, 'onchain_timeout', 'desc')['payment_hash'] + # We underpay, so it fails. + routestep = { + 'msatoshi': 10**8 - 1, + 'id': l2.info['id'], + 'delay': 5, + 'channel': '1:1:1' + } + + self.executor.submit(l1.rpc.sendpay, to_json([routestep]), rhash) + + # l2 will drop to chain. + l2.daemon.wait_for_log('permfail') + l2.daemon.wait_for_log('sendrawtx exit 0') + bitcoind.generate_block(1) + l1.daemon.wait_for_log(' to ONCHAIN') + l2.daemon.wait_for_log(' to ONCHAIN') + + # Wait for timeout. + l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/OUR_HTLC by OUR_HTLC_TIMEOUT_TO_US .* in 5 blocks') + bitcoind.generate_block(5) + + l1.daemon.wait_for_log('sendrawtx exit 0') + + # Make sure that gets included. + + bitcoind.generate_block(1) + # Now we restart with different feerates. + l1.stop() + + l1.daemon.cmd_line.append('--override-fee-rates=20000/9000/2000') + l1.daemon.start() + + # We recognize different proposal as ours. + l1.daemon.wait_for_log('Resolved THEIR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TO_US') + + # We use 3 blocks for "reasonable depth", so add two more + bitcoind.generate_block(2) + + # Note that the very similar test_onchain_timeout looks for a + # different string: that's because it sees the JSONRPC response, + # and due to the l1 restart, there is none here. + l1.daemon.wait_for_log('WIRE_PERMANENT_CHANNEL_FAILURE') + + # 91 later, l2 is done + bitcoind.generate_block(90) + sync_blockheight([l2]) + assert not l2.daemon.is_in_log('onchaind complete, forgetting peer') + bitcoind.generate_block(1) + l2.daemon.wait_for_log('onchaind complete, forgetting peer') + + # Now, 6 blocks and l1 should be done. + bitcoind.generate_block(5) + sync_blockheight([l1]) + assert not l1.daemon.is_in_log('onchaind complete, forgetting peer') + bitcoind.generate_block(1) + l1.daemon.wait_for_log('onchaind complete, forgetting peer') + + # Payment failed, BTW + assert l2.rpc.listinvoices('onchain_timeout')['invoices'][0]['status'] == 'unpaid' + @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") def test_permfail_new_commit(self): # Test case where we have two possible commits: it will use new one.