From c807db4b7fe5b20b4be66794601bf04b67947b01 Mon Sep 17 00:00:00 2001 From: niftynei Date: Sat, 29 Jul 2023 15:48:43 -0500 Subject: [PATCH] dual-fund tests: add tests for reported incompat with Eclair Bug Report: - initiate a channel open eclair -> cln - wait for the transaction to be published - eclair initiates rbf, and cancels it by sending tx_abort before exchanging commit_sig - at that point everything looks good, cln echoes the tx_abort and stays connected - eclair initiates another RBF attempt and sends tx_init_rbf: for some unknown reason, cln answers with channel_reestablish (??) followed by an error saying "Bad reestablish message: WIRE_TX_INIT_RBF" Diagnosis: CLN is doing a reconnect after a tx-abort is sent. Extra Test: Realized that if we abort, we won't correctly advanced to NORMAL if blocks are mined while we're in hanging state. CLN should advance after block containing channel open is mined. Reported-By: @t-bast --- tests/test_opening.py | 144 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/tests/test_opening.py b/tests/test_opening.py index 45e558c67..e7dfc5955 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -378,6 +378,150 @@ def test_v2_rbf_single(node_factory, bitcoind, chainparams): l1.daemon.wait_for_log('sendrawtx exit 0') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.openchannel('v2') +@pytest.mark.xfail +def test_v2_rbf_abort_retry(node_factory, bitcoind, chainparams): + l1, l2 = node_factory.get_nodes(2, opts={'wumbo': None, + 'allow_warning': True}) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + amount = 2**24 + chan_amount = 100000 + bitcoind.rpc.sendmany("", + {l1.rpc.newaddr()['bech32']: amount / 10**8 + 0.01, + l2.rpc.newaddr()['bech32']: amount / 10**8 + 0.01}) + bitcoind.generate_block(1) + # Wait for it to arrive. + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0) + wait_for(lambda: len(l2.rpc.listfunds()['outputs']) > 0) + + l1_utxos = ['{}:{}'.format(utxo['txid'], utxo['output']) for utxo in l1.rpc.listfunds()['outputs']] + + # setup l2 to dual-fund! + l2.rpc.call('funderupdate', {'policy': 'match', + 'policy_mod': 100, + 'leases_only': False}) + + res = l1.rpc.fundchannel(l2.info['id'], chan_amount) + chan_id = res['channel_id'] + vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin'] + prev_utxos = ["{}:{}".format(vin['txid'], vin['vout']) for vin in vins if "{}:{}".format(vin['txid'], vin['vout']) in l1_utxos] + + # Check that we're waiting for lockin + l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN') + + # Check that feerate info is correct + info_1 = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels']) + next_rate = "{}perkw".format(info_1['next_feerate'][:-5]) + + # Initiate an RBF + startweight = 42 + 172 # base weight, funding output + initpsbt = l1.rpc.utxopsbt(chan_amount, next_rate, startweight, + prev_utxos, reservedok=True, + min_witness_weight=110, + excess_as_change=True) + + # Do the bump + bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt']) + + # We abort the channel mid-way throught the RBF + l1.rpc.openchannel_abort(chan_id) + + with pytest.raises(RpcError): + l1.rpc.openchannel_update(chan_id, bump['psbt']) + + # - initiate a channel open eclair -> cln + # - wait for the transaction to be published + # - eclair initiates rbf, and cancels it by sending tx_abort before exchanging commit_sig + # - at that point everything looks good, cln echoes the tx_abort and stays connected + # - eclair initiates another RBF attempt and sends tx_init_rbf: for some unknown reason, cln answers with channel_reestablish (??) followed by an error saying "Bad reestablish message: WIRE_TX_INIT_RBF" + + # attempt to initiate an RBF again + info_1 = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels']) + next_rate = "{}perkw".format(int(info_1['next_feerate'][:-5]) * 2) + + # Gotta unreserve the psbt and re-reserve with higher feerate + l1.rpc.unreserveinputs(initpsbt['psbt']) + initpsbt = l1.rpc.utxopsbt(chan_amount, next_rate, startweight, + prev_utxos, reservedok=True, + min_witness_weight=110, + excess_as_change=True) + # Do the bump+sign + bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'], + funding_feerate=next_rate) + + update = l1.rpc.openchannel_update(chan_id, bump['psbt']) + signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt'] + l1.rpc.openchannel_signed(chan_id, signed_psbt) + + bitcoind.generate_block(1) + sync_blockheight(bitcoind, [l1]) + l1.daemon.wait_for_log(' to CHANNELD_NORMAL') + assert not l1.daemon.is_in_log('WIRE_CHANNEL_REESTABLISH') + assert not l2.daemon.is_in_log('WIRE_CHANNEL_REESTABLISH') + + +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.xfail +@pytest.mark.openchannel('v2') +def test_v2_rbf_abort_channel_opens(node_factory, bitcoind, chainparams): + l1, l2 = node_factory.get_nodes(2, opts={'wumbo': None, + 'allow_warning': True}) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + amount = 2**24 + chan_amount = 100000 + bitcoind.rpc.sendmany("", + {l1.rpc.newaddr()['bech32']: amount / 10**8 + 0.01, + l2.rpc.newaddr()['bech32']: amount / 10**8 + 0.01}) + bitcoind.generate_block(1) + # Wait for it to arrive. + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0) + wait_for(lambda: len(l2.rpc.listfunds()['outputs']) > 0) + + l1_utxos = ['{}:{}'.format(utxo['txid'], utxo['output']) for utxo in l1.rpc.listfunds()['outputs']] + + # setup l2 to dual-fund! + l2.rpc.call('funderupdate', {'policy': 'match', + 'policy_mod': 100, + 'leases_only': False}) + + res = l1.rpc.fundchannel(l2.info['id'], chan_amount) + chan_id = res['channel_id'] + vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin'] + prev_utxos = ["{}:{}".format(vin['txid'], vin['vout']) for vin in vins if "{}:{}".format(vin['txid'], vin['vout']) in l1_utxos] + + # Check that we're waiting for lockin + l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN') + + # Check that feerate info is correct + info_1 = only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels']) + next_rate = "{}perkw".format(info_1['next_feerate'][:-5]) + + # Initiate an RBF + startweight = 42 + 172 # base weight, funding output + initpsbt = l1.rpc.utxopsbt(chan_amount, next_rate, startweight, + prev_utxos, reservedok=True, + min_witness_weight=110, + excess_as_change=True) + + # Do the bump + bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt']) + + # We abort the channel mid-way throught the RBF + l1.rpc.openchannel_abort(chan_id) + + with pytest.raises(RpcError): + l1.rpc.openchannel_update(chan_id, bump['psbt']) + + # When the original open tx is mined, we should still arrive at + # NORMAL channel ops + bitcoind.generate_block(1) + sync_blockheight(bitcoind, [l1]) + l1.daemon.wait_for_log(' to CHANNELD_NORMAL') + + @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams):