Skip to content

Commit

Permalink
tests: update opening tests for new reconnect behavior
Browse files Browse the repository at this point in the history
Let's test that things stay together!

One cool thing to note is that now we sort of "magically" recover from
pretty brutal disconnects!

Very nice!
  • Loading branch information
niftynei committed Oct 27, 2023
1 parent ca2b209 commit d020f9e
Showing 1 changed file with 108 additions and 41 deletions.
149 changes: 108 additions & 41 deletions tests/test_opening.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,12 @@ def get_funded_channel_scid(n1, n2):

@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@pytest.mark.openchannel('v2')
def test_v2_open_sigs_restart(node_factory, bitcoind):
disconnects_1 = ['-WIRE_TX_SIGNATURES']
def test_v2_open_sigs_reconnect_2(node_factory, bitcoind):
""" We test reconnect where L2 drops after sending their tx-sigs """
disconnects_2 = ['+WIRE_TX_SIGNATURES']

l1, l2 = node_factory.get_nodes(2,
opts=[{'disconnect': disconnects_1,
'may_reconnect': True},
opts=[{'may_reconnect': True},
{'disconnect': disconnects_2,
'may_reconnect': True}])

Expand All @@ -156,23 +155,58 @@ def test_v2_open_sigs_restart(node_factory, bitcoind):
# Wait for it to arrive.
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)

# Fund the channel, should appear to finish ok even though the
# peer fails
# Fund the channel, should disconnect after getting l2's sigs
with pytest.raises(RpcError):
l1.rpc.fundchannel(l2.info['id'], chan_amount)

chan_id = first_channel_id(l1, l2)
log = l1.daemon.is_in_log('{} psbt'.format(chan_id))
assert log
psbt = re.search("psbt (.*)", log).group(1)

# peer reconnects, and we resend our sigs
l1.daemon.wait_for_log('Peer has reconnected, state DUALOPEND_OPEN_COMMITTED')
try:
# FIXME: why do we need to retry signed?
l1.rpc.openchannel_signed(chan_id, psbt)
except RpcError:
pass
l1.daemon.wait_for_log('peer_out WIRE_TX_SIGNATURES')
l1.daemon.wait_for_log('Broadcasting funding tx')
l2.daemon.wait_for_log('Broadcasting funding tx')
txid = l2.rpc.listpeerchannels(l1.info['id'])['channels'][0]['funding_txid']
bitcoind.generate_block(6, wait_for_mempool=txid)

# Make sure we're ok.
l1.daemon.wait_for_log(r'to CHANNELD_NORMAL')
l2.daemon.wait_for_log(r'to CHANNELD_NORMAL')


@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@pytest.mark.openchannel('v2')
def test_v2_open_sigs_reconnect_1(node_factory, bitcoind):
""" We test reconnect where L2 drops while sending tx-sigs.
Absolutely pure voodoo (the fundchannel command succeeds anyway after a
reconnect) """
disconnects_2 = ['-WIRE_TX_SIGNATURES']

l1, l2 = node_factory.get_nodes(2,
opts=[{'may_reconnect': True},
{'disconnect': disconnects_2,
'may_reconnect': True}])

l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
amount = 2**24
chan_amount = 100000
bitcoind.rpc.sendtoaddress(l1.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)

# Fund the channel, should disconnect after sending l2 sigs
l1.rpc.fundchannel(l2.info['id'], chan_amount)

# peer reconnects, and we resend our sigs
l1.daemon.wait_for_logs(['peer_in WIRE_CHANNEL_REESTABLISH',
'peer_out WIRE_COMMITMENT_SIGNED',
# Incredible that this works imo
'Unable to send our sigs, our psbt isn\'t signed',
'No channel open attempt/command!'])
l2.daemon.wait_for_logs(['peer_in WIRE_CHANNEL_REESTABLISH',
'peer_out WIRE_COMMITMENT_SIGNED',
'peer_out WIRE_TX_SIGNATURES'])

l1.daemon.wait_for_log('Broadcasting funding tx')
l2.daemon.wait_for_log('Broadcasting funding tx')
txid = l2.rpc.listpeerchannels(l1.info['id'])['channels'][0]['funding_txid']
bitcoind.generate_block(6, wait_for_mempool=txid)
Expand Down Expand Up @@ -213,9 +247,14 @@ def test_v2_fail_second(node_factory, bitcoind):

# We should have deleted the 'in-progress' channel info
only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])
only_one(l2.rpc.listpeerchannels(l1.info['id'])['channels'])

# check that tx-abort was sent
l1.daemon.wait_for_log(r'peer_out WIRE_TX_ABORT')
l2.daemon.wait_for_log(r'peer_out WIRE_TX_ABORT')

# FIXME: check that tx-abort was sent
# Should be able to reattempt without reconnecting
assert l1.rpc.getpeer(l2.info['id'])['connected']
start = l1.rpc.openchannel_init(l2.info['id'], amount, psbt)
assert len(l1.rpc.listpeerchannels(l2.info['id'])['channels']) == 2

Expand Down Expand Up @@ -244,23 +283,12 @@ def test_v2_open_sigs_restart_while_dead(node_factory, bitcoind):
# Wait for it to arrive.
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)

# Fund the channel, should appear to finish ok even though the
# peer fails
# Make a channel happen, with multiple disconnects!
with pytest.raises(RpcError):
l1.rpc.fundchannel(l2.info['id'], chan_amount)

chan_id = first_channel_id(l1, l2)
log = l1.daemon.is_in_log('{} psbt'.format(chan_id))
assert log
psbt = re.search("psbt (.*)", log).group(1)

l1.daemon.wait_for_log('Peer has reconnected, state DUALOPEND_OPEN_COMMITTED')
try:
# FIXME: why do we need to retry signed?
l1.rpc.openchannel_signed(chan_id, psbt)
except RpcError:
pass

l1.daemon.wait_for_log('Broadcasting funding tx')
l1.daemon.wait_for_log('sendrawtx exit 0')
l2.daemon.wait_for_log('Broadcasting funding tx')
l2.daemon.wait_for_log('sendrawtx exit 0')

Expand Down Expand Up @@ -811,7 +839,9 @@ def test_rbf_reconnect_tx_construct(node_factory, bitcoind, chainparams):
'-WIRE_TX_ADD_OUTPUT',
'+WIRE_TX_ADD_OUTPUT',
'-WIRE_TX_COMPLETE',
'+WIRE_TX_COMPLETE']
'+WIRE_TX_COMPLETE',
'-WIRE_COMMITMENT_SIGNED',
'+WIRE_COMMITMENT_SIGNED']

l1, l2 = node_factory.get_nodes(2,
opts=[{'disconnect': disconnects,
Expand All @@ -837,7 +867,10 @@ def test_rbf_reconnect_tx_construct(node_factory, bitcoind, chainparams):
# Check that we're waiting for lockin
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')

next_feerate = find_next_feerate(l1, l2)
# rbf the lease with a higher amount
rate = int(find_next_feerate(l1, l2)[:-5])
# We 4x the feerate to beat the min-relay fee
next_feerate = '{}perkw'.format(rate * 4)

# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
Expand All @@ -847,25 +880,57 @@ def test_rbf_reconnect_tx_construct(node_factory, bitcoind, chainparams):
excess_as_change=True)

# Run through TX_ADD wires
for d in disconnects[1:-2]:
for d in disconnects[1:-4]:
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError):
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
wait_for(lambda: l1.rpc.getpeer(l2.info['id'])['connected'] is False)

# Now we finish off the completes failure check
for d in disconnects[-2:]:
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
# The two TX_COMPLETE breaks + first COMMITMENT_SIGNED
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
for d in disconnects[-4:-1]:
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
with pytest.raises(RpcError):
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
wait_for(lambda: l1.rpc.getpeer(l2.info['id'])['connected'] is False)

# Now we succeed
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
# l1 should remember, l2 has forgotten
# l2 should send tx-abort, to reset
l2.daemon.wait_for_log(r'tx-abort: Sent next_funding_txid .* doesn\'t match ours .*')
l1.daemon.wait_for_log(r'Cleaned up incomplete inflight')
# abort doesn't cause a disconnect
assert l1.rpc.getpeer(l2.info['id'])['connected']

# The final COMMITMENT_SIGNED disconnects
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
with pytest.raises(RpcError):
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
wait_for(lambda: l1.rpc.getpeer(l2.info['id'])['connected'] is False)
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
# l2 reconnects, but doesn't have l1's commitment
l2.daemon.wait_for_log(r'No commitment, not sending our sigs \(reconnected\)')
# l1 gets l2's commitment sigs, but doesn't have a command anymore
l1.daemon.wait_for_log(r'No commitment, not sending our sigs \(reconnected\)')
l1.daemon.wait_for_log(r'No channel open attempt/command!')
# everyone is still connected
assert l1.rpc.getpeer(l2.info['id'])['connected']
inflights = only_one(l1.rpc.listpeerchannels()['channels'])['inflight']
assert len(inflights) == 2
assert len(only_one(l2.rpc.listpeerchannels()['channels'])['inflight']) == 2

# we can call update again! it should work this time :)
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
assert update['commitments_secured']
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
l1.rpc.openchannel_signed(chan_id, signed_psbt)

l2.daemon.wait_for_log('Broadcasting funding tx')
txid = l2.rpc.listpeerchannels(l1.info['id'])['channels'][0]['funding_txid']
bitcoind.generate_block(6, wait_for_mempool=txid)

# Make sure we're ok.
l1.daemon.wait_for_log(r'to CHANNELD_NORMAL')
l2.daemon.wait_for_log(r'to CHANNELD_NORMAL')


@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
Expand Down Expand Up @@ -963,6 +1028,9 @@ def test_rbf_reconnect_tx_sigs(node_factory, bitcoind, chainparams):
# mine a block?
bitcoind.generate_block(1)
sync_blockheight(bitcoind, [l1])
# l1 dropped l2's sigs, and they get them again, but
# we're in a weird state because the channel is already in 'ready' mode
l1.daemon.wait_for_log(r'Got WIRE_TX_SIGNATURES after channel_ready .* ignoring')
l1.daemon.wait_for_log(' to CHANNELD_NORMAL')

# Check that they have matching funding txid
Expand Down Expand Up @@ -1979,7 +2047,6 @@ def test_zeroreserve(node_factory, bitcoind):
l3c2 = l3.rpc.listpeerchannels(l2.info['id'])['channels'][0]
l3c3 = l3.rpc.listpeerchannels(l1.info['id'])['channels'][0]
l1c3 = l1.rpc.listpeerchannels(l3.info['id'])['channels'][0]

# l1 imposed a 0sat reserve on l2, while l2 imposed the default 1% reserve on l1
assert l1c1['their_reserve_msat'] == l2c1['our_reserve_msat'] == Millisatoshi('0sat')
assert l1c1['our_reserve_msat'] == l2c1['their_reserve_msat'] == Millisatoshi('10000sat')
Expand Down

0 comments on commit d020f9e

Please sign in to comment.