2021-02-04 20:17:53 +01:00
from fixtures import * # noqa: F401,F403
from fixtures import TEST_NETWORK
2021-07-08 23:23:16 +02:00
from pyln . client import RpcError , Millisatoshi
2021-02-04 20:17:53 +01:00
from utils import (
2023-06-29 02:14:09 +02:00
only_one , wait_for , sync_blockheight , first_channel_id , calc_lease_fee , check_coin_moves
2021-02-04 20:17:53 +01:00
)
2022-05-20 15:45:54 +02:00
from pathlib import Path
2022-05-25 16:14:47 +02:00
from pprint import pprint
2021-02-10 22:17:02 +01:00
import pytest
2021-03-16 01:50:31 +01:00
import re
2021-02-04 20:17:53 +01:00
import unittest
def find_next_feerate ( node , peer ) :
2023-01-12 02:25:55 +01:00
chan = only_one ( node . rpc . listpeerchannels ( peer . info [ ' id ' ] ) [ ' channels ' ] )
2021-02-04 20:17:53 +01:00
return chan [ ' next_feerate ' ]
2021-06-11 23:32:27 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
@pytest.mark.openchannel ( ' v2 ' )
2022-10-19 20:35:45 +02:00
@pytest.mark.developer ( " requres ' dev-queryrates ' + ' dev-force-features ' " )
2021-06-11 23:32:27 +02:00
def test_queryrates ( node_factory , bitcoind ) :
2022-10-19 20:35:45 +02:00
2023-06-29 02:14:09 +02:00
opts = { ' dev-no-reconnect ' : None ,
' experimental-anchors ' : None }
2022-10-19 20:35:45 +02:00
l1 , l2 = node_factory . get_nodes ( 2 , opts = opts )
2021-06-11 23:32:27 +02:00
amount = 10 * * 6
l1 . fundwallet ( amount * 10 )
l2 . fundwallet ( amount * 10 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
2021-07-06 19:17:51 +02:00
with pytest . raises ( RpcError , match = r ' not advertising liquidity ' ) :
2021-07-06 20:48:35 +02:00
l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount * 10 )
2021-06-11 23:32:27 +02:00
l2 . rpc . call ( ' funderupdate ' , { ' policy ' : ' match ' ,
' policy_mod ' : 100 ,
2021-07-25 12:36:18 +02:00
' per_channel_max_msat ' : ' 1btc ' ,
2021-06-11 23:32:27 +02:00
' fuzz_percent ' : 0 ,
' lease_fee_base_msat ' : ' 2sat ' ,
' funding_weight ' : 1000 ,
' lease_fee_basis ' : 140 ,
' channel_fee_max_base_msat ' : ' 3sat ' ,
' channel_fee_max_proportional_thousandths ' : 101 } )
2021-07-06 20:48:35 +02:00
result = l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount )
2021-06-11 23:32:27 +02:00
assert result [ ' our_funding_msat ' ] == Millisatoshi ( amount * 1000 )
assert result [ ' their_funding_msat ' ] == Millisatoshi ( amount * 1000 )
assert result [ ' funding_weight ' ] == 1000
assert result [ ' lease_fee_base_msat ' ] == Millisatoshi ( 2000 )
assert result [ ' lease_fee_basis ' ] == 140
assert result [ ' channel_fee_max_base_msat ' ] == Millisatoshi ( 3000 )
assert result [ ' channel_fee_max_proportional_thousandths ' ] == 101
2021-03-12 01:19:40 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v1 ' ) # Mixed v1 + v2, v2 manually turned on
2021-03-11 03:23:03 +01:00
def test_multifunding_v2_best_effort ( node_factory , bitcoind ) :
'''
Check that best_effort flag works .
'''
disconnects = [ " -WIRE_INIT " ,
" -WIRE_ACCEPT_CHANNEL " ,
" -WIRE_FUNDING_SIGNED " ]
2021-03-12 01:19:40 +01:00
l1 = node_factory . get_node ( options = { ' experimental-dual-fund ' : None } ,
2021-03-11 03:23:03 +01:00
allow_warning = True ,
may_reconnect = True )
2021-03-12 01:19:40 +01:00
l2 = node_factory . get_node ( options = { ' experimental-dual-fund ' : None } ,
2021-03-11 03:23:03 +01:00
allow_warning = True ,
may_reconnect = True )
l3 = node_factory . get_node ( disconnect = disconnects )
l4 = node_factory . get_node ( )
l1 . fundwallet ( 2000000 )
destinations = [ { " id " : ' {} @localhost: {} ' . format ( l2 . info [ ' id ' ] , l2 . port ) ,
" amount " : 50000 } ,
{ " id " : ' {} @localhost: {} ' . format ( l3 . info [ ' id ' ] , l3 . port ) ,
" amount " : 50000 } ,
{ " id " : ' {} @localhost: {} ' . format ( l4 . info [ ' id ' ] , l4 . port ) ,
" amount " : 50000 } ]
for i , d in enumerate ( disconnects ) :
failed_sign = d == " -WIRE_FUNDING_SIGNED "
# Should succeed due to best-effort flag.
min_channels = 1 if failed_sign else 2
l1 . rpc . multifundchannel ( destinations , minchannels = min_channels )
bitcoind . generate_block ( 6 , wait_for_mempool = 1 )
# l3 should fail to have channels; l2 also fails on last attempt
node_list = [ l1 , l4 ] if failed_sign else [ l1 , l2 , l4 ]
for node in node_list :
node . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
# There should be working channels to l2 and l4 for every run
# but the last
working_chans = [ l4 ] if failed_sign else [ l2 , l4 ]
for ldest in working_chans :
inv = ldest . rpc . invoice ( 5000 , ' i {} ' . format ( i ) , ' i {} ' . format ( i ) ) [ ' bolt11 ' ]
l1 . rpc . pay ( inv )
# Function to find the SCID of the channel that is
# currently open.
# Cannot use LightningNode.get_channel_scid since
# it assumes the *first* channel found is the one
# wanted, but in our case we close channels and
# open again, so multiple channels may remain
# listed.
def get_funded_channel_scid ( n1 , n2 ) :
2023-01-12 02:25:55 +01:00
channels = n1 . rpc . listpeerchannels ( n2 . info [ ' id ' ] ) [ ' channels ' ]
assert channels and len ( channels ) != 0
2021-03-11 03:23:03 +01:00
for c in channels :
state = c [ ' state ' ]
if state in ( ' DUALOPEND_AWAITING_LOCKIN ' , ' CHANNELD_AWAITING_LOCKIN ' , ' CHANNELD_NORMAL ' ) :
return c [ ' short_channel_id ' ]
assert False
# Now close channels to l2 and l4, for the next run.
if not failed_sign :
l1 . rpc . close ( get_funded_channel_scid ( l1 , l2 ) )
l1 . rpc . close ( get_funded_channel_scid ( l1 , l4 ) )
for node in node_list :
node . daemon . wait_for_log ( r ' to CLOSINGD_COMPLETE ' )
# With 2 down, it will fail to fund channel
l2 . stop ( )
l3 . stop ( )
2021-10-09 18:27:54 +02:00
with pytest . raises ( RpcError , match = r ' (Connection refused|Bad file descriptor) ' ) :
2021-03-11 03:23:03 +01:00
l1 . rpc . multifundchannel ( destinations , minchannels = 2 )
# This works though.
l1 . rpc . multifundchannel ( destinations , minchannels = 1 )
2021-03-16 01:50:31 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-03-16 01:50:31 +01:00
def test_v2_open_sigs_restart ( node_factory , bitcoind ) :
disconnects_1 = [ ' -WIRE_TX_SIGNATURES ' ]
disconnects_2 = [ ' +WIRE_TX_SIGNATURES ' ]
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' disconnect ' : disconnects_1 ,
2021-03-16 01:50:31 +01:00
' may_reconnect ' : True } ,
2021-04-26 21:23:40 +02:00
{ ' disconnect ' : disconnects_2 ,
2021-03-16 01:50:31 +01:00
' 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 appear to finish ok even though the
# peer fails
2021-03-18 22:22:03 +01:00
with pytest . raises ( RpcError ) :
2021-03-16 01:50:31 +01:00
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = first_channel_id ( l1 , l2 )
2021-03-18 22:22:03 +01:00
log = l1 . daemon . is_in_log ( ' {} psbt ' . format ( chan_id ) )
2021-03-18 19:49:15 +01:00
assert log
2021-03-18 22:22:03 +01:00
psbt = re . search ( " psbt (.*) " , log ) . group ( 1 )
2021-03-16 01:50:31 +01:00
l1 . daemon . wait_for_log ( ' Peer has reconnected, state DUALOPEND_OPEN_INIT ' )
2022-07-27 00:07:39 +02:00
try :
# FIXME: why do we need to retry signed?
2021-03-16 01:50:31 +01:00
l1 . rpc . openchannel_signed ( chan_id , psbt )
2022-07-27 00:07:39 +02:00
except RpcError :
pass
2021-03-16 01:50:31 +01:00
l2 . daemon . wait_for_log ( ' Broadcasting funding tx ' )
2023-01-12 02:25:55 +01:00
txid = l2 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] [ 0 ] [ ' funding_txid ' ]
2021-03-22 21:46:08 +01:00
bitcoind . generate_block ( 6 , wait_for_mempool = txid )
2021-03-16 01:50:31 +01:00
# Make sure we're ok.
l1 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
2021-03-18 19:49:15 +01:00
l2 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
2021-03-16 01:50:31 +01:00
2022-11-23 01:06:55 +01:00
@pytest.mark.openchannel ( ' v2 ' )
def test_v2_fail_second ( node_factory , bitcoind ) :
""" Open a channel succeeds; opening a second channel
failure should not drop the connection """
l1 , l2 = node_factory . line_graph ( 2 , wait_for_announce = True )
# Should have one channel between them.
2023-05-23 12:28:25 +02:00
only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2022-11-23 01:06:55 +01:00
amount = 2 * * 24 - 1
l1 . fundwallet ( amount + 10000000 )
# make sure we can generate PSBTs.
addr = l1 . rpc . newaddr ( ) [ ' bech32 ' ]
bitcoind . rpc . sendtoaddress ( addr , ( amount + 1000000 ) / 10 * * 8 )
bitcoind . generate_block ( 1 )
wait_for ( lambda : len ( l1 . rpc . listfunds ( ) [ " outputs " ] ) != 0 )
# Some random (valid) psbt
psbt = l1 . rpc . fundpsbt ( amount , ' 253perkw ' , 250 , reserve = 0 ) [ ' psbt ' ]
start = l1 . rpc . openchannel_init ( l2 . info [ ' id ' ] , amount , psbt )
2023-05-23 12:28:25 +02:00
# They will both see a pair of channels
assert len ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] ) == 2
assert len ( l2 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] ) == 2
2022-11-23 01:06:55 +01:00
# We can abort a channel
l1 . rpc . openchannel_abort ( start [ ' channel_id ' ] )
# We should have deleted the 'in-progress' channel info
2023-05-23 12:28:25 +02:00
only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2022-11-23 01:06:55 +01:00
# FIXME: check that tx-abort was sent
# Should be able to reattempt without reconnecting
start = l1 . rpc . openchannel_init ( l2 . info [ ' id ' ] , amount , psbt )
2023-05-23 12:28:25 +02:00
assert len ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] ) == 2
2022-11-23 01:06:55 +01:00
2021-03-16 01:50:31 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-03-16 01:50:31 +01:00
def test_v2_open_sigs_restart_while_dead ( node_factory , bitcoind ) :
# Same thing as above, except the transaction mines
# while we're asleep
disconnects_1 = [ ' -WIRE_TX_SIGNATURES ' ]
disconnects_2 = [ ' +WIRE_TX_SIGNATURES ' ]
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' disconnect ' : disconnects_1 ,
2021-03-18 19:49:15 +01:00
' may_reconnect ' : True ,
' may_fail ' : True } ,
2021-04-26 21:23:40 +02:00
{ ' disconnect ' : disconnects_2 ,
2021-03-18 19:49:15 +01:00
' may_reconnect ' : True ,
' may_fail ' : True } ] )
2021-03-16 01:50:31 +01:00
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 appear to finish ok even though the
# peer fails
2021-03-18 22:22:03 +01:00
with pytest . raises ( RpcError ) :
2021-03-16 01:50:31 +01:00
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = first_channel_id ( l1 , l2 )
2021-03-18 22:22:03 +01:00
log = l1 . daemon . is_in_log ( ' {} psbt ' . format ( chan_id ) )
2021-03-18 19:49:15 +01:00
assert log
2021-03-18 22:22:03 +01:00
psbt = re . search ( " psbt (.*) " , log ) . group ( 1 )
2021-03-16 01:50:31 +01:00
l1 . daemon . wait_for_log ( ' Peer has reconnected, state DUALOPEND_OPEN_INIT ' )
2022-07-27 00:07:39 +02:00
try :
# FIXME: why do we need to retry signed?
2021-03-16 01:50:31 +01:00
l1 . rpc . openchannel_signed ( chan_id , psbt )
2022-07-27 00:07:39 +02:00
except RpcError :
pass
2021-03-16 01:50:31 +01:00
l2 . daemon . wait_for_log ( ' Broadcasting funding tx ' )
2021-04-08 02:58:50 +02:00
l2 . daemon . wait_for_log ( ' sendrawtx exit 0 ' )
2021-03-16 01:50:31 +01:00
l1 . stop ( )
l2 . stop ( )
bitcoind . generate_block ( 6 )
l1 . restart ( )
l2 . restart ( )
# Make sure we're ok.
l2 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
l1 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
2021-02-04 20:17:53 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-07-09 21:13:20 +02:00
def test_v2_rbf_single ( node_factory , bitcoind , chainparams ) :
2021-04-26 21:23:40 +02:00
l1 , l2 = node_factory . get_nodes ( 2 , opts = { ' wumbo ' : None } )
2021-02-04 20:17:53 +01:00
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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
next_feerate = find_next_feerate ( l1 , l2 )
# Check that feerate info is correct
2023-01-12 02:25:55 +01:00
info_1 = only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2021-02-04 20:17:53 +01:00
assert info_1 [ ' initial_feerate ' ] == info_1 [ ' last_feerate ' ]
rate = int ( info_1 [ ' last_feerate ' ] [ : - 5 ] )
2021-07-09 21:13:20 +02:00
assert int ( info_1 [ ' next_feerate ' ] [ : - 5 ] ) == rate * 65 / / 64
2021-02-04 20:17:53 +01:00
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , 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 ' ] )
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
# Check that feerate info has incremented
2023-01-12 02:25:55 +01:00
info_2 = only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2021-02-04 20:17:53 +01:00
assert info_1 [ ' initial_feerate ' ] == info_2 [ ' initial_feerate ' ]
assert info_1 [ ' next_feerate ' ] == info_2 [ ' last_feerate ' ]
rate = int ( info_2 [ ' last_feerate ' ] [ : - 5 ] )
2021-07-09 21:13:20 +02:00
assert int ( info_2 [ ' next_feerate ' ] [ : - 5 ] ) == rate * 65 / / 64
2021-02-04 20:17:53 +01:00
# Sign our inputs, and continue
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
2021-07-09 21:13:20 +02:00
# Fails because we didn't put enough feerate in.
with pytest . raises ( RpcError , match = r ' insufficient fee ' ) :
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
# Do it again, with a higher feerate
2023-01-12 02:25:55 +01:00
info_2 = only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2021-07-09 21:13:20 +02:00
assert info_1 [ ' initial_feerate ' ] == info_2 [ ' initial_feerate ' ]
assert info_1 [ ' next_feerate ' ] == info_2 [ ' last_feerate ' ]
rate = int ( info_2 [ ' last_feerate ' ] [ : - 5 ] )
assert int ( info_2 [ ' next_feerate ' ] [ : - 5 ] ) == rate * 65 / / 64
# We 4x the feerate to beat the min-relay fee
next_rate = ' {} perkw ' . format ( rate * 65 / / 64 * 4 )
# 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 ' ] )
assert update [ ' commitments_secured ' ]
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
2021-02-04 20:17:53 +01:00
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l1 ] )
l1 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
# Check that feerate info is gone
2023-01-12 02:25:55 +01:00
info_1 = only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2021-02-04 20:17:53 +01:00
assert ' initial_feerate ' not in info_1
assert ' last_feerate ' not in info_1
assert ' next_feerate ' not in info_1
# Shut l2 down, force close the channel.
l2 . stop ( )
resp = l1 . rpc . close ( l2 . info [ ' id ' ] , unilateraltimeout = 1 )
assert resp [ ' type ' ] == ' unilateral '
l1 . daemon . wait_for_log ( ' to CHANNELD_SHUTTING_DOWN ' )
l1 . daemon . wait_for_log ( ' sendrawtx exit 0 ' )
2021-02-10 22:17:02 +01:00
2023-07-29 22:48:43 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
@pytest.mark.openchannel ( ' v2 ' )
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.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 ' )
2021-08-05 19:32:58 +02:00
@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 ) :
opts = { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
2022-01-17 14:35:40 +01:00
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
2023-06-29 02:14:09 +02:00
' experimental-anchors ' : None ,
2021-08-05 19:32:58 +02:00
' may_reconnect ' : True }
2022-10-19 20:35:45 +02:00
2021-08-05 19:32:58 +02:00
l1 , l2 = node_factory . get_nodes ( 2 , opts = opts )
# what happens when we RBF?
feerate = 2000
amount = 500000
l1 . fundwallet ( 20000000 )
l2 . fundwallet ( 20000000 )
# l1 leases a channel from l2
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
rates = l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount )
chan_id = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = rates [ ' compact_lease ' ] ) [ ' channel_id ' ]
vins = [ x for x in l1 . rpc . listfunds ( ) [ ' outputs ' ] if x [ ' reserved ' ] ]
assert only_one ( vins )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' output ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
est_fees = calc_lease_fee ( amount , feerate , rates )
# This should be the accepter's amount
2023-01-12 02:25:55 +01:00
fundings = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' funding ' ]
2022-07-29 05:44:17 +02:00
assert Millisatoshi ( amount * 1000 ) == fundings [ ' remote_funds_msat ' ]
assert Millisatoshi ( est_fees + amount * 1000 ) == fundings [ ' local_funds_msat ' ]
assert Millisatoshi ( est_fees ) == fundings [ ' fee_paid_msat ' ]
assert ' fee_rcvd_msat ' not in fundings
2021-08-05 19:32:58 +02:00
# 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 )
2022-10-24 20:00:10 +02:00
# Restart the node between open + rbf; works as expected
l1 . restart ( )
2021-08-05 19:32:58 +02:00
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True ) [ ' psbt ' ]
2022-10-24 20:00:10 +02:00
# reconnect after restart
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
2021-08-05 19:32:58 +02:00
# do the bump
bump = l1 . rpc . openchannel_bump ( chan_id , amount , initpsbt ,
funding_feerate = next_feerate )
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
2022-10-07 22:37:06 +02:00
2021-08-05 19:32:58 +02:00
# Sign our inputs, and continue
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
2022-10-07 22:37:06 +02:00
# There's data in the datastore now (l2 only)
assert l1 . rpc . listdatastore ( ) == { ' datastore ' : [ ] }
only_one ( l2 . rpc . listdatastore ( " funder/ {} " . format ( chan_id ) ) [ ' datastore ' ] )
2021-08-05 19:32:58 +02:00
# what happens when the channel opens?
bitcoind . generate_block ( 6 )
l1 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
2022-10-07 22:37:06 +02:00
# Datastore should be cleaned up!
assert l1 . rpc . listdatastore ( ) == { ' datastore ' : [ ] }
2022-10-24 20:00:10 +02:00
wait_for ( lambda : l2 . rpc . listdatastore ( ) == { ' datastore ' : [ ] } )
2022-10-07 22:37:06 +02:00
2021-08-05 19:32:58 +02:00
# This should be the accepter's amount
2023-01-12 02:25:55 +01:00
fundings = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' funding ' ]
# The is still there!
assert Millisatoshi ( amount * 1000 ) == Millisatoshi ( fundings [ ' remote_funds_msat ' ] )
2021-08-05 19:32:58 +02:00
wait_for ( lambda : [ c [ ' active ' ] for c in l1 . rpc . listchannels ( l1 . get_channel_scid ( l2 ) ) [ ' channels ' ] ] == [ True , True ] )
# send some payments, mine a block or two
inv = l2 . rpc . invoice ( 10 * * 4 , ' 1 ' , ' no_1 ' )
l1 . rpc . pay ( inv [ ' bolt11 ' ] )
# l2 attempts to close a channel that it leased, should succeed
# (channel isnt leased)
l2 . rpc . close ( l1 . get_channel_scid ( l2 ) )
l1 . daemon . wait_for_log ( ' State changed from CLOSINGD_SIGEXCHANGE to CLOSINGD_COMPLETE ' )
2021-02-12 00:01:07 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2022-07-18 14:12:28 +02:00
@pytest.mark.developer ( " uses dev-no-reconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-12 00:01:07 +01:00
def test_v2_rbf_multi ( node_factory , bitcoind , chainparams ) :
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = { ' may_reconnect ' : True ,
2022-07-18 14:12:28 +02:00
' dev-no-reconnect ' : None ,
2021-03-09 22:14:08 +01:00
' allow_warning ' : True } )
2021-02-12 00:01:07 +01:00
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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
2021-03-09 22:14:08 +01:00
# Attempt to do abort, should fail since we've
# already gotten an inflight
with pytest . raises ( RpcError ) :
l1 . rpc . openchannel_abort ( chan_id )
2021-07-09 21:13:20 +02:00
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
# We 4x the feerate to beat the min-relay fee
next_feerate = ' {} perkw ' . format ( rate * 4 )
2021-02-12 00:01:07 +01:00
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
# Do the bump
2021-07-09 21:13:20 +02:00
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount ,
initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-02-12 00:01:07 +01:00
2021-03-09 22:14:08 +01:00
# Abort this open attempt! We will re-try
aborted = l1 . rpc . openchannel_abort ( chan_id )
assert not aborted [ ' channel_canceled ' ]
2022-12-01 22:36:06 +01:00
# We no longer disconnect on aborts, because magic!
assert only_one ( l1 . rpc . listpeers ( ) [ ' peers ' ] ) [ ' connected ' ]
2021-03-09 22:14:08 +01:00
2021-07-09 21:13:20 +02:00
# Do the bump, again, same feerate
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount ,
initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-03-09 22:14:08 +01:00
2021-02-12 00:01:07 +01:00
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
# Sign our inputs, and continue
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
2021-07-09 21:13:20 +02:00
# We 2x the feerate to beat the min-relay fee
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
next_feerate = ' {} perkw ' . format ( rate * 2 )
2021-02-12 00:01:07 +01:00
2021-07-09 21:13:20 +02:00
# Initiate another RBF, double the channel amount this time
2021-02-12 00:01:07 +01:00
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount * 2 , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
# Do the bump
2021-07-09 21:13:20 +02:00
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount * 2 ,
initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-02-12 00:01:07 +01:00
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
# Sign our inputs, and continue
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 ' )
2021-02-10 22:17:02 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-10 22:17:02 +01:00
def test_rbf_reconnect_init ( node_factory , bitcoind , chainparams ) :
2022-07-11 16:14:51 +02:00
disconnects = [ ' -WIRE_TX_INIT_RBF ' ,
' +WIRE_TX_INIT_RBF ' ]
2021-02-10 22:17:02 +01:00
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' disconnect ' : disconnects ,
2021-02-10 22:17:02 +01:00
' may_reconnect ' : True } ,
2021-04-26 21:23:40 +02:00
{ ' may_reconnect ' : True } ] )
2021-02-10 22:17:02 +01:00
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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
next_feerate = find_next_feerate ( l1 , l2 )
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
# Do the bump!?
for d in disconnects :
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
with pytest . raises ( RpcError ) :
l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
assert l1 . rpc . getpeer ( l2 . info [ ' id ' ] ) is not None
# This should succeed
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-10 22:17:02 +01:00
def test_rbf_reconnect_ack ( node_factory , bitcoind , chainparams ) :
2022-07-11 16:14:51 +02:00
disconnects = [ ' -WIRE_TX_ACK_RBF ' ,
' +WIRE_TX_ACK_RBF ' ]
2021-02-10 22:17:02 +01:00
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' may_reconnect ' : True } ,
{ ' disconnect ' : disconnects ,
2021-02-10 22:17:02 +01:00
' 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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
next_feerate = find_next_feerate ( l1 , l2 )
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
# Do the bump!?
for d in disconnects :
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
with pytest . raises ( RpcError ) :
l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
assert l1 . rpc . getpeer ( l2 . info [ ' id ' ] ) is not None
# This should succeed
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
2021-02-11 00:46:26 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-11 00:46:26 +01:00
def test_rbf_reconnect_tx_construct ( node_factory , bitcoind , chainparams ) :
disconnects = [ ' =WIRE_TX_ADD_INPUT ' , # Initial funding succeeds
' -WIRE_TX_ADD_INPUT ' ,
' +WIRE_TX_ADD_INPUT ' ,
' -WIRE_TX_ADD_OUTPUT ' ,
' +WIRE_TX_ADD_OUTPUT ' ,
' -WIRE_TX_COMPLETE ' ,
' +WIRE_TX_COMPLETE ' ]
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' disconnect ' : disconnects ,
pytest: disable autoreconnect in test_rbf_reconnect_tx_construct
It reconnects itself, so we don't see the ['connected'] is False.
```
2022-07-20T20:37:06.3808161Z @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
2022-07-20T20:37:06.3809031Z @pytest.mark.developer("uses dev-disconnect")
2022-07-20T20:37:06.3809547Z @pytest.mark.openchannel('v2')
2022-07-20T20:37:06.3810058Z def test_rbf_reconnect_tx_construct(node_factory, bitcoind, chainparams):
...
2022-07-20T20:37:06.3864163Z # Now we finish off the completes failure check
2022-07-20T20:37:06.3864604Z for d in disconnects[-2:]:
2022-07-20T20:37:06.3865162Z l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
2022-07-20T20:37:06.3865711Z bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
2022-07-20T20:37:06.3866169Z with pytest.raises(RpcError):
2022-07-20T20:37:06.3866674Z update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
2022-07-20T20:37:06.3867367Z > wait_for(lambda: l1.rpc.getpeer(l2.info['id'])['connected'] is False)
...
2022-07-20T20:37:06.5215961Z lightningd-1 2022-07-20T20:21:49.691Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-connectd: dev_disconnect: -WIRE_TX_COMPLETE (WIRE_TX_COMPLETE)
2022-07-20T20:37:06.5216482Z lightningd-1 2022-07-20T20:21:49.691Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-dualopend-chan#1: peer_out WIRE_TX_COMPLETE
2022-07-20T20:37:06.5216756Z lightningd-1 2022-07-20T20:21:49.691Z DEBUG connectd: drain_peer
2022-07-20T20:37:06.5217064Z lightningd-1 2022-07-20T20:21:49.692Z DEBUG connectd: drain_peer draining subd!
2022-07-20T20:37:06.5217549Z lightningd-1 2022-07-20T20:21:49.692Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-lightningd: peer_disconnect_done
2022-07-20T20:37:06.5218482Z lightningd-1 2022-07-20T20:21:49.692Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-lightningd: Reconnecting in 1 seconds
2022-07-20T20:37:06.5219110Z lightningd-1 2022-07-20T20:21:49.692Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-lightningd: Will try reconnect in 1 seconds
2022-07-20T20:37:06.5219427Z lightningd-1 2022-07-20T20:21:49.692Z DEBUG gossipd: REPLY WIRE_GOSSIPD_GET_ADDRS_REPLY with 0 fds
2022-07-20T20:37:06.5219994Z lightningd-1 2022-07-20T20:21:49.696Z DEBUG plugin-funder: Cleaning up inflights for peer id 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59
2022-07-20T20:37:06.5220305Z lightningd-1 2022-07-20T20:21:49.696Z DEBUG connectd: maybe_free_peer freeing peer!
2022-07-20T20:37:06.5220743Z lightningd-2 2022-07-20T20:21:49.699Z DEBUG 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518-gossipd: seeker: chosen as startup peer
2022-07-20T20:37:06.5221136Z lightningd-2 2022-07-20T20:21:49.699Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-hsmd: Got WIRE_HSMD_ECDH_REQ
2022-07-20T20:37:06.5221380Z lightningd-2 2022-07-20T20:21:49.699Z DEBUG connectd: drain_peer
2022-07-20T20:37:06.5221805Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-hsmd: Got WIRE_HSMD_READY_CHANNEL
2022-07-20T20:37:06.5223761Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-dualopend-chan#1: signature 30440220338460c0e75c08c21f4f4f96806d81426aee48e06ccc54e87892b332f55fe49e0220527573286f801a0d23317978a9aabbbbcb95c2ceb614596c0ab50bb72b96158b01 on tx 020000000105f2aa5f346f46007b9f8b128e8c6e4d40d0477aefd81250ba99adc9349aabe300000000009db0e280024a01000000000000220020be7935a77ca9ab70a4b8b1906825637767fed3c00824aa90c988983587d684881e6301000000000022002047021684129f8aa1c0b5b97dc99607f5f0850b548813a5da346fda22511284759a3ed620 using key 02324266de8403b3ab157a09f1f784d587af61831c998c151bcc21bb74c2b2314b
2022-07-20T20:37:06.5224194Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-connectd: Connected out, starting crypto
2022-07-20T20:37:06.5224723Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-gossipd: seeker: chosen as startup peer
2022-07-20T20:37:06.5225006Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG hsmd: Client: Received message 31 from client
2022-07-20T20:37:06.5225461Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-dualopend-chan#1: peer_out WIRE_COMMITMENT_SIGNED
2022-07-20T20:37:06.5225852Z lightningd-1 2022-07-20T20:21:49.700Z DEBUG 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59-connectd: Connect OUT
```
2022-07-21 06:49:14 +02:00
' may_reconnect ' : True ,
' dev-no-reconnect ' : None } ,
{ ' may_reconnect ' : True ,
' dev-no-reconnect ' : None } ] )
2021-02-11 00:46:26 +01:00
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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
next_feerate = find_next_feerate ( l1 , l2 )
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
# Run through TX_ADD wires
2021-12-28 00:27:09 +01:00
for d in disconnects [ 1 : - 2 ] :
2021-02-11 00:46:26 +01:00
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
with pytest . raises ( RpcError ) :
l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
2022-07-18 14:12:28 +02:00
wait_for ( lambda : l1 . rpc . getpeer ( l2 . info [ ' id ' ] ) [ ' connected ' ] is False )
2021-02-11 00:46:26 +01:00
# Now we finish off the completes failure check
2021-12-28 00:27:09 +01:00
for d in disconnects [ - 2 : ] :
2021-02-11 00:46:26 +01:00
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
with pytest . raises ( RpcError ) :
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
2022-07-18 14:12:28 +02:00
wait_for ( lambda : l1 . rpc . getpeer ( l2 . info [ ' id ' ] ) [ ' connected ' ] is False )
2021-02-11 00:46:26 +01:00
# Now we succeed
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
2021-02-12 00:01:07 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:58:58 +02:00
@pytest.mark.developer ( " uses dev-disconnect " )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-12 00:01:07 +01:00
def test_rbf_reconnect_tx_sigs ( node_factory , bitcoind , chainparams ) :
disconnects = [ ' =WIRE_TX_SIGNATURES ' , # Initial funding succeeds
' -WIRE_TX_SIGNATURES ' , # When we send tx-sigs, RBF
' =WIRE_TX_SIGNATURES ' , # When we reconnect
' +WIRE_TX_SIGNATURES ' ] # When we RBF again
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = [ { ' disconnect ' : disconnects ,
2021-03-16 01:50:31 +01:00
' may_reconnect ' : True } ,
2021-04-26 21:23:40 +02:00
{ ' may_reconnect ' : True } ] )
2021-02-12 00:01:07 +01:00
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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' Broadcasting funding tx ' )
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
2021-08-02 20:48:37 +02:00
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
# We 4x the feerate to beat the min-relay fee
next_feerate = ' {} perkw ' . format ( rate * 4 )
2021-02-12 00:01:07 +01:00
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
2021-08-02 20:48:37 +02:00
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-02-12 00:01:07 +01:00
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
# Sign our inputs, and continue
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
# First time we error when we send our sigs
with pytest . raises ( RpcError , match = ' Owning subdaemon dualopend died ' ) :
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
# We reconnect and try again. feerate should have bumped
2021-08-02 20:48:37 +02:00
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
# We 4x the feerate to beat the min-relay fee
next_feerate = ' {} perkw ' . format ( rate * 4 )
2021-02-12 00:01:07 +01:00
# Initiate an RBF
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
# l2 gets our sigs and broadcasts them
l2 . daemon . wait_for_log ( ' peer_in WIRE_CHANNEL_REESTABLISH ' )
l2 . daemon . wait_for_log ( ' peer_in WIRE_TX_SIGNATURES ' )
l2 . daemon . wait_for_log ( ' sendrawtx exit 0 ' )
# Wait until we've done re-establish, if we try to
# RBF again too quickly, it'll fail since they haven't
# had time to process our sigs yet
l1 . daemon . wait_for_log ( ' peer_in WIRE_CHANNEL_REESTABLISH ' )
l1 . daemon . wait_for_log ( ' peer_in WIRE_TX_SIGNATURES ' )
2021-12-28 00:27:09 +01:00
# 2nd RBF
2021-08-02 20:48:37 +02:00
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-02-12 00:01:07 +01:00
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
signed_psbt = l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
# Second time we error after we send our sigs
with pytest . raises ( RpcError , match = ' Owning subdaemon dualopend died ' ) :
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
# l2 gets our sigs
l2 . daemon . wait_for_log ( ' peer_in WIRE_TX_SIGNATURES ' )
l2 . daemon . wait_for_log ( ' sendrawtx exit 0 ' )
# mine a block?
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l1 ] )
l1 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
# Check that they have matching funding txid
2023-01-12 02:25:55 +01:00
l1_funding_txid = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' funding_txid ' ]
l2_funding_txid = only_one ( l2 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' funding_txid ' ]
2021-02-12 00:01:07 +01:00
assert l1_funding_txid == l2_funding_txid
2021-02-12 00:14:26 +01:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-02-12 00:14:26 +01:00
def test_rbf_no_overlap ( node_factory , bitcoind , chainparams ) :
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-04-26 21:23:40 +02:00
opts = { ' allow_warning ' : True } )
2021-02-12 00:14:26 +01:00
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 . 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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount )
chan_id = res [ ' channel_id ' ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
next_feerate = find_next_feerate ( l1 , l2 )
2021-05-07 21:50:17 +02:00
# Initiate an RBF (this grabs the non-reserved utxo, which isnt the
# one we started with)
2021-02-12 00:14:26 +01:00
startweight = 42 + 172 # base weight, funding output
initpsbt = l1 . rpc . fundpsbt ( chan_amount , next_feerate , startweight ,
min_witness_weight = 110 ,
excess_as_change = True )
# Do the bump
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
with pytest . raises ( RpcError , match = ' No overlapping input present. ' ) :
l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
2021-04-27 23:12:14 +02:00
2021-05-07 21:31:45 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
@pytest.mark.openchannel ( ' v2 ' )
2021-05-20 02:16:49 +02:00
@pytest.mark.developer ( " uses dev-sign-last-tx " )
2021-05-07 21:31:45 +02:00
def test_rbf_fails_to_broadcast ( node_factory , bitcoind , chainparams ) :
l1 , l2 = node_factory . get_nodes ( 2 ,
2021-05-19 23:51:56 +02:00
opts = { ' allow_warning ' : True ,
' may_reconnect ' : True } )
2021-05-07 21:31:45 +02:00
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 )
# Really low feerate means that the bump wont work the first time
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount , feerate = ' 253perkw ' )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-19 23:51:56 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
2021-05-07 21:31:45 +02:00
2021-05-19 23:51:56 +02:00
def run_retry ( ) :
startweight = 42 + 173
2021-07-09 21:13:20 +02:00
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
# We 2x the feerate to beat the min-relay fee
next_feerate = ' {} perkw ' . format ( rate * 2 )
2021-05-19 23:51:56 +02:00
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
2021-05-07 21:31:45 +02:00
2021-05-19 23:51:56 +02:00
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
2021-07-09 21:13:20 +02:00
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount ,
initpsbt [ ' psbt ' ] ,
funding_feerate = next_feerate )
2021-05-20 02:16:49 +02:00
# We should be able to call this with while an open is progress
# but not yet committed
l1 . rpc . dev_sign_last_tx ( l2 . info [ ' id ' ] )
2021-05-19 23:51:56 +02:00
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
2021-05-07 21:31:45 +02:00
2021-05-19 23:51:56 +02:00
return l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
2021-05-07 21:31:45 +02:00
2021-05-19 23:51:56 +02:00
signed_psbt = run_retry ( )
2021-07-09 21:13:20 +02:00
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-07-09 21:13:20 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
2021-05-07 21:31:45 +02:00
2021-07-09 21:13:20 +02:00
# Restart and listpeers, used to crash
2021-05-07 21:31:45 +02:00
l1 . restart ( )
l1 . rpc . listpeers ( )
2021-07-09 21:13:20 +02:00
# We've restarted. Let's RBF
2021-05-19 23:51:56 +02:00
signed_psbt = run_retry ( )
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-07-09 21:13:20 +02:00
assert len ( inflights ) == 3
2021-05-19 23:51:56 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
l1 . restart ( )
# Are inflights the same post restart
prev_inflights = inflights
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-19 23:51:56 +02:00
assert prev_inflights == inflights
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
2021-05-20 02:16:49 +02:00
# Produce a signature for every inflight
last_txs = l1 . rpc . dev_sign_last_tx ( l2 . info [ ' id ' ] )
assert len ( last_txs [ ' inflights ' ] ) == len ( inflights )
for last_tx , inflight in zip ( last_txs [ ' inflights ' ] , inflights ) :
assert last_tx [ ' funding_txid ' ] == inflight [ ' funding_txid ' ]
assert last_txs [ ' tx ' ]
2021-05-07 21:31:45 +02:00
2021-05-20 02:18:39 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
@pytest.mark.openchannel ( ' v2 ' )
def test_rbf_broadcast_close_inflights ( node_factory , bitcoind , chainparams ) :
"""
Close a channel before it ' s mined, and the most recent transaction
hasn ' t made it to the mempool. Should publish all the commitment
transactions that we have .
"""
l1 , l2 = node_factory . get_nodes ( 2 ,
opts = { ' allow_warning ' : 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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount , feerate = ' 7500perkw ' )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-20 02:18:39 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
# Make it such that l1 and l2 cannot broadcast transactions
# (mimics failing to reach the miner with replacement)
def censoring_sendrawtx ( r ) :
return { ' id ' : r [ ' id ' ] , ' result ' : { } }
l1 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , censoring_sendrawtx )
l2 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , censoring_sendrawtx )
def run_retry ( ) :
startweight = 42 + 173
next_feerate = find_next_feerate ( l1 , l2 )
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
return l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
signed_psbt = run_retry ( )
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-20 02:18:39 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] not in bitcoind . rpc . getrawmempool ( )
2023-01-12 02:25:55 +01:00
cmtmt_txid = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' scratch_txid ' ]
2021-05-20 02:18:39 +02:00
assert cmtmt_txid == inflights [ - 1 ] [ ' scratch_txid ' ]
# l2 goes offline
l2 . stop ( )
# l1 drops to chain.
l1 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , None )
l1 . rpc . close ( chan_id , 1 )
l1 . daemon . wait_for_logs ( [ ' Broadcasting txid {} ' . format ( inflights [ 0 ] [ ' scratch_txid ' ] ) ,
' Broadcasting txid {} ' . format ( inflights [ 1 ] [ ' scratch_txid ' ] ) ,
' sendrawtx exit 0 ' ,
' sendrawtx exit 25 ' ] )
assert inflights [ 0 ] [ ' scratch_txid ' ] in bitcoind . rpc . getrawmempool ( )
assert inflights [ 1 ] [ ' scratch_txid ' ] not in bitcoind . rpc . getrawmempool ( )
2021-05-20 23:55:45 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
@pytest.mark.openchannel ( ' v2 ' )
def test_rbf_non_last_mined ( node_factory , bitcoind , chainparams ) :
"""
What happens if a ' non-tip ' RBF transaction is mined ?
"""
l1 , l2 = node_factory . get_nodes ( 2 ,
opts = { ' allow_warning ' : True ,
' 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 )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , chan_amount , feerate = ' 7500perkw ' )
chan_id = res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
# Check that we're waiting for lockin
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-20 23:55:45 +02:00
assert inflights [ - 1 ] [ ' funding_txid ' ] in bitcoind . rpc . getrawmempool ( )
def run_retry ( ) :
startweight = 42 + 173
2021-07-09 21:13:20 +02:00
rate = int ( find_next_feerate ( l1 , l2 ) [ : - 5 ] )
# We 2x the feerate to beat the min-relay fee
next_feerate = ' {} perkw ' . format ( rate * 2 )
2021-05-20 23:55:45 +02:00
initpsbt = l1 . rpc . utxopsbt ( chan_amount , next_feerate , startweight ,
prev_utxos , reservedok = True ,
min_witness_weight = 110 ,
excess_as_change = True )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
bump = l1 . rpc . openchannel_bump ( chan_id , chan_amount , initpsbt [ ' psbt ' ] )
update = l1 . rpc . openchannel_update ( chan_id , bump [ ' psbt ' ] )
assert update [ ' commitments_secured ' ]
return l1 . rpc . signpsbt ( update [ ' psbt ' ] ) [ ' signed_psbt ' ]
# Make a second inflight
signed_psbt = run_retry ( )
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
# Make it such that l1 and l2 cannot broadcast transactions
# (mimics failing to reach the miner with replacement)
def censoring_sendrawtx ( r ) :
return { ' id ' : r [ ' id ' ] , ' result ' : { } }
l1 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , censoring_sendrawtx )
l2 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , censoring_sendrawtx )
# Make a 3rd inflight that won't make it into the mempool
signed_psbt = run_retry ( )
l1 . rpc . openchannel_signed ( chan_id , signed_psbt )
l1 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , None )
l2 . daemon . rpcproxy . mock_rpc ( ' sendrawtransaction ' , None )
# We fetch out our inflights list
2023-01-12 02:25:55 +01:00
inflights = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' inflight ' ]
2021-05-20 23:55:45 +02:00
# l2 goes offline
l2 . stop ( )
# The funding transaction gets mined (should be the 2nd inflight)
bitcoind . generate_block ( 6 , wait_for_mempool = 1 )
# l2 comes back up
l2 . start ( )
# everybody's got the right things now
l1 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
l2 . daemon . wait_for_log ( r ' to CHANNELD_NORMAL ' )
2023-01-12 02:25:55 +01:00
channel = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] )
2021-05-20 23:55:45 +02:00
assert channel [ ' funding_txid ' ] == inflights [ 1 ] [ ' funding_txid ' ]
assert channel [ ' scratch_txid ' ] == inflights [ 1 ] [ ' scratch_txid ' ]
# We delete inflights when the channel is in normal ops
assert ' inflights ' not in channel
# l2 stops, again
l2 . stop ( )
# l1 drops to chain.
l1 . rpc . close ( chan_id , 1 )
l1 . daemon . wait_for_log ( ' Broadcasting txid {} ' . format ( channel [ ' scratch_txid ' ] ) )
# The funding transaction gets mined (should be the 2nd inflight)
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
l1 . daemon . wait_for_log ( r ' to ONCHAIN ' )
2021-04-27 23:12:14 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
2021-04-26 21:23:40 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2021-04-27 23:12:14 +02:00
def test_funder_options ( node_factory , bitcoind ) :
2021-04-26 21:23:40 +02:00
l1 , l2 , l3 = node_factory . get_nodes ( 3 )
2021-04-27 23:12:14 +02:00
l1 . fundwallet ( 10 * * 7 )
# Check the default options
funder_opts = l1 . rpc . call ( ' funderupdate ' )
assert funder_opts [ ' policy ' ] == ' fixed '
assert funder_opts [ ' policy_mod ' ] == 0
2021-07-15 18:48:36 +02:00
assert funder_opts [ ' min_their_funding_msat ' ] == Millisatoshi ( ' 10000000msat ' )
assert funder_opts [ ' max_their_funding_msat ' ] == Millisatoshi ( ' 4294967295000msat ' )
assert funder_opts [ ' per_channel_min_msat ' ] == Millisatoshi ( ' 10000000msat ' )
assert funder_opts [ ' per_channel_max_msat ' ] == Millisatoshi ( ' 4294967295000msat ' )
assert funder_opts [ ' reserve_tank_msat ' ] == Millisatoshi ( ' 0msat ' )
2021-07-14 19:53:56 +02:00
assert funder_opts [ ' fuzz_percent ' ] == 0
2021-04-27 23:12:14 +02:00
assert funder_opts [ ' fund_probability ' ] == 100
2021-08-05 19:31:49 +02:00
assert funder_opts [ ' leases_only ' ]
2021-04-27 23:12:14 +02:00
# l2 funds a chanenl with us. We don't contribute
l2 . rpc . connect ( l1 . info [ ' id ' ] , ' localhost ' , l1 . port )
l2 . fundchannel ( l1 , 10 * * 6 )
2023-01-12 02:25:55 +01:00
chan_info = only_one ( l2 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] )
2021-04-27 23:12:14 +02:00
# l1 contributed nothing
2022-07-29 05:44:17 +02:00
assert chan_info [ ' funding ' ] [ ' remote_funds_msat ' ] == Millisatoshi ( ' 0msat ' )
assert chan_info [ ' funding ' ] [ ' local_funds_msat ' ] != Millisatoshi ( ' 0msat ' )
2021-04-27 23:12:14 +02:00
# Change all the options
funder_opts = l1 . rpc . call ( ' funderupdate ' ,
{ ' policy ' : ' available ' ,
' policy_mod ' : 100 ,
2021-07-15 18:48:36 +02:00
' min_their_funding_msat ' : ' 100000msat ' ,
' max_their_funding_msat ' : ' 2000000000msat ' ,
' per_channel_min_msat ' : ' 8000000msat ' ,
' per_channel_max_msat ' : ' 10000000000msat ' ,
' reserve_tank_msat ' : ' 3000000msat ' ,
2021-04-27 23:12:14 +02:00
' fund_probability ' : 99 ,
2021-08-05 19:31:49 +02:00
' fuzz_percent ' : 0 ,
' leases_only ' : False } )
2021-04-27 23:12:14 +02:00
assert funder_opts [ ' policy ' ] == ' available '
assert funder_opts [ ' policy_mod ' ] == 100
2021-07-15 18:48:36 +02:00
assert funder_opts [ ' min_their_funding_msat ' ] == Millisatoshi ( ' 100000msat ' )
assert funder_opts [ ' max_their_funding_msat ' ] == Millisatoshi ( ' 2000000000msat ' )
assert funder_opts [ ' per_channel_min_msat ' ] == Millisatoshi ( ' 8000000msat ' )
assert funder_opts [ ' per_channel_max_msat ' ] == Millisatoshi ( ' 10000000000msat ' )
assert funder_opts [ ' reserve_tank_msat ' ] == Millisatoshi ( ' 3000000msat ' )
2021-04-27 23:12:14 +02:00
assert funder_opts [ ' fuzz_percent ' ] == 0
assert funder_opts [ ' fund_probability ' ] == 99
# Set the fund probability back up to 100.
funder_opts = l1 . rpc . call ( ' funderupdate ' ,
{ ' fund_probability ' : 100 } )
l3 . rpc . connect ( l1 . info [ ' id ' ] , ' localhost ' , l1 . port )
l3 . fundchannel ( l1 , 10 * * 6 )
2023-01-12 02:25:55 +01:00
chan_info = only_one ( l3 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] )
2022-10-19 18:26:21 +02:00
log = l1 . daemon . wait_for_log ( r ' Policy available \ (100 % \ ) returned funding amount of ' )
match = re . search ( r ' Policy available \ (100 % \ ) returned funding amount of ( \ d*sat) ' , log )
assert match and len ( match . groups ( ) ) == 1
2021-07-09 18:47:15 +02:00
# l1 contributed all its funds!
2022-10-19 18:26:21 +02:00
assert chan_info [ ' funding ' ] [ ' remote_funds_msat ' ] == Millisatoshi ( match . groups ( ) [ 0 ] )
2022-07-29 05:44:17 +02:00
assert chan_info [ ' funding ' ] [ ' local_funds_msat ' ] == Millisatoshi ( ' 1000000000msat ' )
2021-05-05 01:53:24 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd doesnt yet support PSBT features we need ' )
def test_funder_contribution_limits ( node_factory , bitcoind ) :
opts = { ' experimental-dual-fund ' : None ,
' feerates ' : ( 5000 , 5000 , 5000 , 5000 ) }
l1 , l2 , l3 = node_factory . get_nodes ( 3 , opts = opts )
2021-06-04 12:47:24 +02:00
# We do a lot of these, so do them all then mine all at once.
addr , txid = l1 . fundwallet ( 10 * * 8 , mine_block = False )
l1msgs = [ ' Owning output .* txid {} CONFIRMED ' . format ( txid ) ]
2021-05-05 01:53:24 +02:00
# Give l2 lots of utxos
2021-06-04 12:47:24 +02:00
l2msgs = [ ]
for amt in ( 10 * * 3 , # this one is too small to add
10 * * 5 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 ) :
addr , txid = l2 . fundwallet ( amt , mine_block = False )
l2msgs . append ( ' Owning output .* txid {} CONFIRMED ' . format ( txid ) )
2021-05-05 01:53:24 +02:00
# Give l3 lots of utxos
2021-06-04 12:47:24 +02:00
l3msgs = [ ]
for amt in ( 10 * * 3 , # this one is too small to add
10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 , 10 * * 4 ) :
addr , txid = l3 . fundwallet ( amt , mine_block = False )
l3msgs . append ( ' Owning output .* txid {} CONFIRMED ' . format ( txid ) )
bitcoind . generate_block ( 1 )
l1 . daemon . wait_for_logs ( l1msgs )
l2 . daemon . wait_for_logs ( l2msgs )
l3 . daemon . wait_for_logs ( l3msgs )
2021-05-05 01:53:24 +02:00
# Contribute 100% of available funds to l2, all 6 utxos (smallest utxo
# 10**3 is left out)
l2 . rpc . call ( ' funderupdate ' ,
{ ' policy ' : ' available ' ,
' policy_mod ' : 100 ,
2021-07-15 18:48:36 +02:00
' min_their_funding_msat ' : ' 1000msat ' ,
' per_channel_min_msat ' : ' 1000000msat ' ,
2021-05-05 01:53:24 +02:00
' fund_probability ' : 100 ,
2021-08-05 19:31:49 +02:00
' fuzz_percent ' : 0 ,
' leases_only ' : False } )
2021-05-05 01:53:24 +02:00
2023-01-13 01:34:22 +01:00
# Set our contribution to 50k sat, should only use 6 of 12 available utxos
2021-05-05 01:53:24 +02:00
l3 . rpc . call ( ' funderupdate ' ,
{ ' policy ' : ' fixed ' ,
' policy_mod ' : ' 50000sat ' ,
2021-07-15 18:48:36 +02:00
' min_their_funding_msat ' : ' 1000msat ' ,
' per_channel_min_msat ' : ' 1000sat ' ,
' per_channel_max_msat ' : ' 500000sat ' ,
2021-05-05 01:53:24 +02:00
' fund_probability ' : 100 ,
2021-08-05 19:31:49 +02:00
' fuzz_percent ' : 0 ,
' leases_only ' : False } )
2021-05-05 01:53:24 +02:00
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
l1 . fundchannel ( l2 , 10 * * 7 )
2023-01-13 01:34:22 +01:00
assert l2 . daemon . is_in_log ( ' Policy .* returned funding amount of 141780sat ' )
2021-05-05 01:53:24 +02:00
assert l2 . daemon . is_in_log ( r ' calling `signpsbt` .* 6 inputs ' )
l1 . rpc . connect ( l3 . info [ ' id ' ] , ' localhost ' , l3 . port )
l1 . fundchannel ( l3 , 10 * * 7 )
assert l3 . daemon . is_in_log ( ' Policy .* returned funding amount of 50000sat ' )
2023-01-13 01:34:22 +01:00
assert l3 . daemon . is_in_log ( r ' calling `signpsbt` .* 6 inputs ' )
2022-05-20 15:45:54 +02:00
2022-08-10 14:28:29 +02:00
@pytest.mark.openchannel ( ' v2 ' )
2023-06-29 02:14:09 +02:00
@pytest.mark.developer ( " requres ' dev-disconnect ' " )
2022-08-10 14:28:29 +02:00
def test_inflight_dbload ( node_factory , bitcoind ) :
""" Bad db field access breaks Postgresql on startup with opening leases """
disconnects = [ " @WIRE_COMMITMENT_SIGNED " ]
2022-10-19 20:35:45 +02:00
opts = [ { ' experimental-dual-fund ' : None , ' dev-no-reconnect ' : None ,
2023-06-29 02:14:09 +02:00
' may_reconnect ' : True , ' disconnect ' : disconnects ,
' experimental-anchors ' : None } ,
2022-10-19 20:35:45 +02:00
{ ' experimental-dual-fund ' : None , ' dev-no-reconnect ' : None ,
' may_reconnect ' : True , ' funder-policy ' : ' match ' ,
' funder-policy-mod ' : 100 , ' lease-fee-base-sat ' : ' 100sat ' ,
2023-06-29 02:14:09 +02:00
' lease-fee-basis ' : 100 ,
' experimental-anchors ' : None } ]
2022-10-19 20:35:45 +02:00
l1 , l2 = node_factory . get_nodes ( 2 , opts = opts )
2022-08-10 14:28:29 +02:00
feerate = 2000
amount = 500000
l1 . fundwallet ( 20000000 )
l2 . fundwallet ( 20000000 )
# l1 leases a channel from l2
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
rates = l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = rates [ ' compact_lease ' ] )
l1 . daemon . wait_for_log ( r ' dev_disconnect: @WIRE_COMMITMENT_SIGNED ' )
l1 . restart ( )
2022-05-20 15:45:54 +02:00
def test_zeroconf_mindepth ( bitcoind , node_factory ) :
""" Check that funder/fundee can customize mindepth.
Zeroconf will use this to set the mindepth to 0 , which coupled
with an artificial depth = 0 event that will result in an immediate
` channel_ready ` being sent .
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroconf-selective.py "
l1 , l2 = node_factory . get_nodes ( 2 , opts = [
{ } ,
{
' plugin ' : str ( plugin_path ) ,
' zeroconf-allow ' : ' 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 ' ,
' zeroconf-mindepth ' : ' 2 ' ,
} ,
] )
# Try to open a mindepth=6 channel
l1 . fundwallet ( 10 * * 6 )
l1 . connect ( l2 )
assert ( int ( l1 . rpc . listpeers ( ) [ ' peers ' ] [ 0 ] [ ' features ' ] , 16 ) >> 50 ) & 0x02 != 0
# Now start the negotiation, l1 should have negotiated zeroconf,
# and use their own mindepth=6, while l2 uses mindepth=2 from the
# plugin
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , ' all ' , mindepth = 6 )
assert l1 . db . query ( ' SELECT minimum_depth FROM channels ' ) == [ { ' minimum_depth ' : 6 } ]
assert l2 . db . query ( ' SELECT minimum_depth FROM channels ' ) == [ { ' minimum_depth ' : 2 } ]
bitcoind . generate_block ( 2 , wait_for_mempool = 1 ) # Confirm on the l2 side.
2022-09-10 04:10:31 +02:00
l2 . daemon . wait_for_log ( r ' peer_out WIRE_CHANNEL_READY ' )
2022-09-10 04:11:31 +02:00
# l1 should not be sending channel_ready yet, it is
2022-05-20 15:45:54 +02:00
# configured to wait for 6 confirmations.
2022-09-10 04:10:31 +02:00
assert not l1 . daemon . is_in_log ( r ' peer_out WIRE_CHANNEL_READY ' )
2022-05-20 15:45:54 +02:00
bitcoind . generate_block ( 4 ) # Confirm on the l2 side.
2022-09-10 04:10:31 +02:00
l1 . daemon . wait_for_log ( r ' peer_out WIRE_CHANNEL_READY ' )
2022-05-20 15:45:54 +02:00
2023-01-12 02:25:55 +01:00
wait_for ( lambda : only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' state ' ] == " CHANNELD_NORMAL " )
wait_for ( lambda : only_one ( l2 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' state ' ] == " CHANNELD_NORMAL " )
2022-05-25 16:14:47 +02:00
def test_zeroconf_open ( bitcoind , node_factory ) :
""" Let ' s open a zeroconf channel
Just test that both parties opting in results in a channel that is
immediately usable .
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroconf-selective.py "
l1 , l2 = node_factory . get_nodes ( 2 , opts = [
{ } ,
{
' plugin ' : str ( plugin_path ) ,
' zeroconf-allow ' : ' 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 '
} ,
] )
# Try to open a mindepth=0 channel
l1 . fundwallet ( 10 * * 6 )
l1 . connect ( l2 )
assert ( int ( l1 . rpc . listpeers ( ) [ ' peers ' ] [ 0 ] [ ' features ' ] , 16 ) >> 50 ) & 0x02 != 0
# Now start the negotiation, l1 should have negotiated zeroconf,
# and use their own mindepth=6, while l2 uses mindepth=2 from the
# plugin
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , ' all ' , mindepth = 0 )
assert l1 . db . query ( ' SELECT minimum_depth FROM channels ' ) == [ { ' minimum_depth ' : 0 } ]
assert l2 . db . query ( ' SELECT minimum_depth FROM channels ' ) == [ { ' minimum_depth ' : 0 } ]
l1 . daemon . wait_for_logs ( [
2022-09-10 04:10:31 +02:00
r ' peer_in WIRE_CHANNEL_READY ' ,
2022-05-25 16:14:47 +02:00
r ' Peer told us that they \' ll use alias=[0-9x]+ for this channel ' ,
] )
l2 . daemon . wait_for_logs ( [
2022-09-10 04:10:31 +02:00
r ' peer_in WIRE_CHANNEL_READY ' ,
2022-05-25 16:14:47 +02:00
r ' Peer told us that they \' ll use alias=[0-9x]+ for this channel ' ,
] )
2023-01-12 02:25:55 +01:00
wait_for ( lambda : only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' state ' ] == ' CHANNELD_NORMAL ' )
wait_for ( lambda : only_one ( l2 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' state ' ] == ' CHANNELD_NORMAL ' )
2022-05-25 16:14:47 +02:00
wait_for ( lambda : l2 . rpc . listincoming ( ) [ ' incoming ' ] != [ ] )
inv = l2 . rpc . invoice ( 10 * * 8 , ' lbl ' , ' desc ' ) [ ' bolt11 ' ]
details = l1 . rpc . decodepay ( inv )
pprint ( details )
assert ( ' routes ' in details and len ( details [ ' routes ' ] ) == 1 )
hop = details [ ' routes ' ] [ 0 ] [ 0 ] # First (and only) hop of hint 0
2023-01-12 02:25:55 +01:00
l1alias = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] ) [ ' alias ' ] [ ' local ' ]
2022-05-25 16:14:47 +02:00
assert ( hop [ ' pubkey ' ] == l1 . info [ ' id ' ] ) # l1 is the entrypoint
assert ( hop [ ' short_channel_id ' ] == l1alias ) # Alias has to make sense to entrypoint
l1 . rpc . pay ( inv )
# Ensure lightningd knows about the balance change before
# attempting the other way around.
l2 . daemon . wait_for_log ( r ' Balance [0-9]+msat -> [0-9]+msat ' )
# Inverse payments should work too
pprint ( l2 . rpc . listpeers ( ) )
inv = l1 . rpc . invoice ( 10 * * 5 , ' lbl ' , ' desc ' ) [ ' bolt11 ' ]
l2 . rpc . pay ( inv )
2022-07-19 21:35:56 +02:00
def test_zeroconf_public ( bitcoind , node_factory , chainparams ) :
2022-05-25 17:04:25 +02:00
""" Test that we transition correctly from zeroconf to public
The differences being that a public channel MUST use the public
scid . l1 and l2 open a zeroconf channel , then l3 learns about it
after 6 confirmations .
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroconf-selective.py "
2022-07-19 21:35:56 +02:00
coin_mvt_plugin = Path ( __file__ ) . parent / " plugins " / " coin_movements.py "
2022-05-25 17:04:25 +02:00
l1 , l2 , l3 = node_factory . get_nodes ( 3 , opts = [
2022-07-19 21:35:56 +02:00
{ ' plugin ' : str ( coin_mvt_plugin ) } ,
2022-05-25 17:04:25 +02:00
{
' plugin ' : str ( plugin_path ) ,
' zeroconf-allow ' : ' 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 '
} ,
{ }
] )
2022-07-27 23:18:05 +02:00
# Advances blockheight to 102
2022-05-25 17:04:25 +02:00
l1 . fundwallet ( 10 * * 6 )
2022-07-27 23:18:05 +02:00
push_msat = 20000 * 1000
2022-05-25 17:04:25 +02:00
l1 . connect ( l2 )
2022-07-27 23:18:05 +02:00
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , ' all ' , mindepth = 0 , push_msat = push_msat )
2022-05-25 17:04:25 +02:00
# Wait for the update to be signed (might not be the most reliable
# signal)
l1 . daemon . wait_for_log ( r ' Got WIRE_HSMD_CUPDATE_SIG_REQ ' )
l2 . daemon . wait_for_log ( r ' Got WIRE_HSMD_CUPDATE_SIG_REQ ' )
2023-01-12 02:25:55 +01:00
l1chan = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] )
l2chan = only_one ( l2 . rpc . listpeerchannels ( ) [ ' channels ' ] )
2022-07-27 23:18:05 +02:00
channel_id = l1chan [ ' channel_id ' ]
2022-05-25 17:04:25 +02:00
# We have no confirmation yet, so no `short_channel_id`
assert ( ' short_channel_id ' not in l1chan )
assert ( ' short_channel_id ' not in l2chan )
2022-07-19 21:35:56 +02:00
# Channel is "proposed"
2023-01-13 01:34:22 +01:00
chan_val = 993888000 if chainparams [ ' elements ' ] else 996363000
2022-07-19 21:35:56 +02:00
l1_mvts = [
{ ' type ' : ' chain_mvt ' , ' credit_msat ' : chan_val , ' debit_msat ' : 0 , ' tags ' : [ ' channel_proposed ' , ' opener ' ] } ,
2022-07-27 23:18:05 +02:00
{ ' type ' : ' channel_mvt ' , ' credit_msat ' : 0 , ' debit_msat ' : 20000000 , ' tags ' : [ ' pushed ' ] , ' fees_msat ' : ' 0msat ' } ,
2022-07-19 21:35:56 +02:00
]
check_coin_moves ( l1 , l1chan [ ' channel_id ' ] , l1_mvts , chainparams )
2022-07-27 23:18:05 +02:00
# Check that the channel_open event has blockheight of zero
for n in [ l1 , l2 ] :
evs = n . rpc . bkpr_listaccountevents ( channel_id ) [ ' events ' ]
open_ev = only_one ( [ e for e in evs if e [ ' tag ' ] == ' channel_proposed ' ] )
assert open_ev [ ' blockheight ' ] == 0
# Call inspect, should have pending event in it
tx = only_one ( n . rpc . bkpr_inspect ( channel_id ) [ ' txs ' ] )
assert ' blockheight ' not in tx
assert only_one ( tx [ ' outputs ' ] ) [ ' output_tag ' ] == ' channel_proposed '
# Now add 1 confirmation, we should get a `short_channel_id` (block 103)
2022-05-25 17:04:25 +02:00
bitcoind . generate_block ( 1 )
l1 . daemon . wait_for_log ( r ' Funding tx [a-f0-9] {64} depth 1 of 0 ' )
l2 . daemon . wait_for_log ( r ' Funding tx [a-f0-9] {64} depth 1 of 0 ' )
2023-01-12 02:25:55 +01:00
l1chan = only_one ( l1 . rpc . listpeerchannels ( ) [ ' channels ' ] )
l2chan = only_one ( l2 . rpc . listpeerchannels ( ) [ ' channels ' ] )
2022-05-25 17:04:25 +02:00
assert ( ' short_channel_id ' in l1chan )
assert ( ' short_channel_id ' in l2chan )
2022-07-27 23:18:05 +02:00
# We also now have an 'open' event, the push event isn't re-recorded
2022-07-19 21:35:56 +02:00
l1_mvts + = [
{ ' type ' : ' chain_mvt ' , ' credit_msat ' : chan_val , ' debit_msat ' : 0 , ' tags ' : [ ' channel_open ' , ' opener ' ] } ,
]
2022-07-27 23:18:05 +02:00
check_coin_moves ( l1 , channel_id , l1_mvts , chainparams )
# Check that there is a channel_open event w/ real blockheight
for n in [ l1 , l2 ] :
evs = n . rpc . bkpr_listaccountevents ( channel_id ) [ ' events ' ]
# Still has the channel-proposed event
only_one ( [ e for e in evs if e [ ' tag ' ] == ' channel_proposed ' ] )
open_ev = only_one ( [ e for e in evs if e [ ' tag ' ] == ' channel_open ' ] )
assert open_ev [ ' blockheight ' ] == 103
# Call inspect, should have open event in it
tx = only_one ( n . rpc . bkpr_inspect ( channel_id ) [ ' txs ' ] )
assert tx [ ' blockheight ' ] == 103
assert only_one ( tx [ ' outputs ' ] ) [ ' output_tag ' ] == ' channel_open '
2022-07-19 21:35:56 +02:00
2022-05-25 17:04:25 +02:00
# Now make it public, we should be switching over to the real
# scid.
bitcoind . generate_block ( 5 )
# Wait for l3 to learn about the channel, it'll have checked the
# funding outpoint, scripts, etc.
2022-06-06 15:23:35 +02:00
l3 . connect ( l1 )
2022-05-25 17:04:25 +02:00
wait_for ( lambda : len ( l3 . rpc . listchannels ( ) [ ' channels ' ] ) == 2 )
2022-07-27 23:18:05 +02:00
# Close the zerconf channel, check that we mark it as onchain_resolved ok
l1 . rpc . close ( l2 . info [ ' id ' ] )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
# Channel should be marked resolved
for n in [ l1 , l2 ] :
wait_for ( lambda : only_one ( [ x for x in n . rpc . bkpr_listbalances ( ) [ ' accounts ' ] if x [ ' account ' ] == channel_id ] ) [ ' account_resolved ' ] )
2022-05-25 16:14:47 +02:00
2022-05-25 17:03:31 +02:00
def test_zeroconf_forward ( node_factory , bitcoind ) :
""" Ensure that we can use zeroconf channels in forwards.
Test that we add routehints using the zeroconf channel , and then
ensure that l2 uses the alias from the routehint to forward the
payment . Then do the inverse by sending from l3 to l1 , first hop
being the zeroconf channel
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroconf-selective.py "
opts = [
{ } ,
{ } ,
{
' plugin ' : str ( plugin_path ) ,
' zeroconf-allow ' : ' 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59 '
}
]
l1 , l2 , l3 = node_factory . get_nodes ( 3 , opts = opts )
l1 . connect ( l2 )
l1 . fundchannel ( l2 , 10 * * 6 )
bitcoind . generate_block ( 6 )
l2 . connect ( l3 )
l2 . fundwallet ( 10 * * 7 )
l2 . rpc . fundchannel ( l3 . info [ ' id ' ] , 10 * * 6 , mindepth = 0 )
wait_for ( lambda : l3 . rpc . listincoming ( ) [ ' incoming ' ] != [ ] )
pytest: fix flake in test_zeroconf_forward
pay failed (non-DEVELOPER) because one node didn't see blocks in time:
```
inv = l3.rpc.invoice(42 * 10**6, 'inv1', 'desc')['bolt11']
> l1.rpc.pay(inv)
tests/test_opening.py:1394:
...
> raise RpcError(method, payload, resp['error'])
E pyln.client.lightning.RpcError: RPC call failed: method: pay, payload: {'bolt11': 'lnbcrt420u1p3dnwv7sp5qnquuwndgz35ywfg3p3dtu07ywmju78r8s0379eaxjxkv5d8jueqpp5kmaxgsye02dzmdlkqkedqvrh2evdl45sz7njrm5dff42dvp4v5qsdq8v3jhxccxqyjw5qcqp9rzjqgkjyd3q5dv6gllh77kygly9c3kfy0d9xwyjyxsq2nq3c83u5vw4n0wkf0y9gwfwhgqqqqqpqqqqqzsqqc9qyysgqad2x2zv0axa3hrfz7nurw4plvspvxlld9wtcg3xxjyxqlzm773a4fkyl09gs8uskj4m7len8r4pf4rh7v9snh3grrpawhk9qsd7vwmcqa9rgxg'}, error: {'code': 210, 'message': 'Ran out of routes to try after 176 attempts: see `paystatus`', 'attempts': [{'status': 'pending', 'partid': 1, 'amount_msat': 42000000msat}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 2, 'amount_msat': 9278783msat, 'parent_partid': 1}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 10, 'amount_msat': 9278783msat, 'parent_partid': 2}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 15, 'amount_msat': 9278783msat, 'parent_partid': 10}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 18, 'amount_msat': 9278783msat, 'parent_partid': 15}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 23, 'amount_msat': 9278783msat, 'parent_partid': 18}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 29, 'amount_msat': 9278783msat, 'parent_partid': 23}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 33, 'amount_msat': 9278783msat, 'parent_partid': 29}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 39, 'amount_msat': 9278783msat, 'parent_partid': 33}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 43, 'amount_msat': 9278783msat, 'parent_partid': 39}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 48, 'amount_msat': 9278783msat, 'parent_partid': 43}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 54, 'amount_msat': 9278783msat, 'parent_partid': 48}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 59, 'amount_msat': 4659837msat, 'parent_partid': 54}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 69, 'amount_msat': 4659837msat, 'parent_partid': 59}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 82, 'amount_msat': 4659837msat, 'parent_partid': 69}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 92, 'amount_msat': 4659837msat, 'parent_partid': 82}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 102, 'amount_msat': 4659837msat, 'parent_partid': 92}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 112, 'amount_msat': 4659837msat, 'parent_partid': 102}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 122, 'amount_msat': 4659837msat, 'parent_partid': 112}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 131, 'amount_msat': 4659837msat, 'parent_partid': 122}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 141, 'amount_msat': 4659837msat, 'parent_partid': 131}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 147, 'amount_msat': 4659837msat, 'parent_partid': 141}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 158, 'amount_msat': 4659837msat, 'parent_partid': 147}, {'status': 'pending', 'partid': 175, 'amount_msat': 2250620msat, 'parent_partid': 158}, {'status': 'pending', 'partid': 176, 'amount_msat': 2409217msat, 'parent_partid': 158}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 60, 'amount_msat': 4618946msat, 'parent_partid': 54}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 70, 'amount_msat': 4618946msat, 'parent_partid': 60}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 81, 'amount_msat': 4618946msat, 'parent_partid': 70}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 91, 'amount_msat': 4618946msat, 'parent_partid': 81}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 101, 'amount_msat': 4618946msat, 'parent_partid': 91}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 111, 'amount_msat': 4618946msat, 'parent_partid': 101}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 121, 'amount_msat': 4618946msat, 'parent_partid': 111}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 132, 'amount_msat': 4618946msat, 'parent_partid': 121}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 142, 'amount_msat': 4618946msat, 'parent_partid': 132}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 148, 'amount_msat': 4618946msat, 'parent_partid': 142}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 157, 'amount_msat': 4618946msat, 'parent_partid': 148}, {'status': 'pending', 'partid': 168, 'amount_msat': 2320055msat, 'parent_partid': 157}, {'status': 'pending', 'partid': 169, 'amount_msat': 2298891msat, 'parent_partid': 157}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 3, 'amount_msat': 9016551msat, 'parent_partid': 1}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 7, 'amount_msat': 9016551msat, 'parent_partid': 3}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 12, 'amount_msat': 9016551msat, 'parent_partid': 7}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 17, 'amount_msat': 9016551msat, 'parent_partid': 12}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 21, 'amount_msat': 9016551msat, 'parent_partid': 17}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 25, 'amount_msat': 9016551msat, 'parent_partid': 21}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 30, 'amount_msat': 9016551msat, 'parent_partid': 25}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 36, 'amount_msat': 9016551msat, 'parent_partid': 30}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 40, 'amount_msat': 9016551msat, 'parent_partid': 36}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 47, 'amount_msat': 9016551msat, 'parent_partid': 40}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 51, 'amount_msat': 9016551msat, 'parent_partid': 47}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 56, 'amount_msat': 4458645msat, 'parent_partid': 51}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 63, 'amount_msat': 4458645msat, 'parent_partid': 56}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 77, 'amount_msat': 4458645msat, 'parent_partid': 63}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 87, 'amount_msat': 4458645msat, 'parent_partid': 77}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 94, 'amount_msat': 4458645msat, 'parent_partid': 87}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 105, 'amount_msat': 4458645msat, 'parent_partid': 94}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 120, 'amount_msat': 4458645msat, 'parent_partid': 105}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 124, 'amount_msat': 4458645msat, 'parent_partid': 120}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 134, 'amount_msat': 4458645msat, 'parent_partid': 124}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 143, 'amount_msat': 4458645msat, 'parent_partid': 134}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 154, 'amount_msat': 4458645msat, 'parent_partid': 143}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 164, 'amount_msat': 2306527msat, 'parent_partid': 154}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 165, 'amount_msat': 2152118msat, 'parent_partid': 154}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 57, 'amount_msat': 4557906msat, 'parent_partid': 51}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 68, 'amount_msat': 4557906msat, 'parent_partid': 57}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 76, 'amount_msat': 4557906msat, 'parent_partid': 68}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 86, 'amount_msat': 4557906msat, 'parent_partid': 76}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 96, 'amount_msat': 4557906msat, 'parent_partid': 86}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 103, 'amount_msat': 4557906msat, 'parent_partid': 96}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 115, 'amount_msat': 4557906msat, 'parent_partid': 103}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 125, 'amount_msat': 4557906msat, 'parent_partid': 115}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 136, 'amount_msat': 4557906msat, 'parent_partid': 125}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 146, 'amount_msat': 4557906msat, 'parent_partid': 136}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 161, 'amount_msat': 4557906msat, 'parent_partid': 146}, {'status': 'pending', 'partid': 173, 'amount_msat': 2208152msat, 'parent_partid': 161}, {'status': 'pending', 'partid': 174, 'amount_msat': 2349754msat, 'parent_partid': 161}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 4, 'amount_msat': 10655305msat, 'parent_partid': 1}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 9, 'amount_msat': 10655305msat, 'parent_partid': 4}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 13, 'amount_msat': 10655305msat, 'parent_partid': 9}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 19, 'amount_msat': 10655305msat, 'parent_partid': 13}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 24, 'amount_msat': 10655305msat, 'parent_partid': 19}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 28, 'amount_msat': 10655305msat, 'parent_partid': 24}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 34, 'amount_msat': 10655305msat, 'parent_partid': 28}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 38, 'amount_msat': 10655305msat, 'parent_partid': 34}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 44, 'amount_msat': 10655305msat, 'parent_partid': 38}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 49, 'amount_msat': 10655305msat, 'parent_partid': 44}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 53, 'amount_msat': 10655305msat, 'parent_partid': 49}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 61, 'amount_msat': 4872267msat, 'parent_partid': 53}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 71, 'amount_msat': 4872267msat, 'parent_partid': 61}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 79, 'amount_msat': 4872267msat, 'parent_partid': 71}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 89, 'amount_msat': 4872267msat, 'parent_partid': 79}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 100, 'amount_msat': 4872267msat, 'parent_partid': 89}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 109, 'amount_msat': 4872267msat, 'parent_partid': 100}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 114, 'amount_msat': 4872267msat, 'parent_partid': 109}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 126, 'amount_msat': 4872267msat, 'parent_partid': 114}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 139, 'amount_msat': 4872267msat, 'parent_partid': 126}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 149, 'amount_msat': 4872267msat, 'parent_partid': 139}, {'status': 'failed', 'failreason': 'Cannot split payment any further without exceeding the maximum number of HTLCs allowed by our channels', 'partid': 162, 'amount_msat': 4872267msat, 'parent_partid': 149}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 62, 'amount_msat': 5783038msat, 'parent_partid': 53}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 72, 'amount_msat': 5783038msat, 'parent_partid': 62}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 80, 'amount_msat': 5783038msat, 'parent_partid': 72}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 90, 'amount_msat': 5783038msat, 'parent_partid': 80}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 99, 'amount_msat': 5783038msat, 'parent_partid': 90}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 110, 'amount_msat': 5783038msat, 'parent_partid': 99}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 117, 'amount_msat': 5783038msat, 'parent_partid': 110}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 128, 'amount_msat': 5783038msat, 'parent_partid': 117}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 138, 'amount_msat': 5783038msat, 'parent_partid': 128}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 150, 'amount_msat': 5783038msat, 'parent_partid': 138}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 156, 'amount_msat': 5783038msat, 'parent_partid': 150}, {'status': 'pending', 'partid': 170, 'amount_msat': 2902801msat, 'parent_partid': 156}, {'status': 'pending', 'partid': 171, 'amount_msat': 2880237msat, 'parent_partid': 156}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 5, 'amount_msat': 8942693msat, 'parent_partid': 1}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 8, 'amount_msat': 8942693msat, 'parent_partid': 5}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 16, 'amount_msat': 8942693msat, 'parent_partid': 8}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 20, 'amount_msat': 8942693msat, 'parent_partid': 16}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 27, 'amount_msat': 8942693msat, 'parent_partid': 20}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 32, 'amount_msat': 8942693msat, 'parent_partid': 27}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 35, 'amount_msat': 8942693msat, 'parent_partid': 32}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 42, 'amount_msat': 8942693msat, 'parent_partid': 35}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 45, 'amount_msat': 8942693msat, 'parent_partid': 42}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 50, 'amount_msat': 8942693msat, 'parent_partid': 45}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 58, 'amount_msat': 8942693msat, 'parent_partid': 50}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 64, 'amount_msat': 4159394msat, 'parent_partid': 58}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 78, 'amount_msat': 4159394msat, 'parent_partid': 64}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 84, 'amount_msat': 4159394msat, 'parent_partid': 78}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 98, 'amount_msat': 4159394msat, 'parent_partid': 84}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 108, 'amount_msat': 4159394msat, 'parent_partid': 98}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 116, 'amount_msat': 4159394msat, 'parent_partid': 108}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 123, 'amount_msat': 4159394msat, 'parent_partid': 116}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 135, 'amount_msat': 4159394msat, 'parent_partid': 123}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 144, 'amount_msat': 4159394msat, 'parent_partid': 135}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 155, 'amount_msat': 4159394msat, 'parent_partid': 144}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 163, 'amount_msat': 4159394msat, 'parent_partid': 155}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 65, 'amount_msat': 4783299msat, 'parent_partid': 58}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 73, 'amount_msat': 4783299msat, 'parent_partid': 65}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 83, 'amount_msat': 4783299msat, 'parent_partid': 73}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 93, 'amount_msat': 4783299msat, 'parent_partid': 83}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 104, 'amount_msat': 4783299msat, 'parent_partid': 93}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 119, 'amount_msat': 4783299msat, 'parent_partid': 104}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 127, 'amount_msat': 4783299msat, 'parent_partid': 119}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 140, 'amount_msat': 4783299msat, 'parent_partid': 127}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 151, 'amount_msat': 4783299msat, 'parent_partid': 140}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 160, 'amount_msat': 4783299msat, 'parent_partid': 151}, {'status': 'pending', 'partid': 172, 'amount_msat': 4783299msat, 'parent_partid': 160}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 6, 'amount_msat': 4106668msat, 'parent_partid': 1}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 11, 'amount_msat': 4106668msat, 'parent_partid': 6}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 14, 'amount_msat': 4106668msat, 'parent_partid': 11}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 22, 'amount_msat': 4106668msat, 'parent_partid': 14}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 26, 'amount_msat': 4106668msat, 'parent_partid': 22}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 31, 'amount_msat': 4106668msat, 'parent_partid': 26}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 37, 'amount_msat': 4106668msat, 'parent_partid': 31}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 41, 'amount_msat': 4106668msat, 'parent_partid': 37}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 46, 'amount_msat': 4106668msat, 'parent_partid': 41}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 52, 'amount_msat': 4106668msat, 'parent_partid': 46}, {'status': 'pending', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 55, 'amount_msat': 4106668msat, 'parent_partid': 52}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 66, 'amount_msat': 2165538msat, 'parent_partid': 55}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 75, 'amount_msat': 2165538msat, 'parent_partid': 66}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 85, 'amount_msat': 2165538msat, 'parent_partid': 75}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 95, 'amount_msat': 2165538msat, 'parent_partid': 85}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 107, 'amount_msat': 2165538msat, 'parent_partid': 95}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 113, 'amount_msat': 2165538msat, 'parent_partid': 107}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 130, 'amount_msat': 2165538msat, 'parent_partid': 113}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 137, 'amount_msat': 2165538msat, 'parent_partid': 130}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 152, 'amount_msat': 2165538msat, 'parent_partid': 137}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 159, 'amount_msat': 2165538msat, 'parent_partid': 152}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 167, 'amount_msat': 2165538msat, 'parent_partid': 159}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 67, 'amount_msat': 1941130msat, 'parent_partid': 55}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 74, 'amount_msat': 1941130msat, 'parent_partid': 67}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 88, 'amount_msat': 1941130msat, 'parent_partid': 74}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 97, 'amount_msat': 1941130msat, 'parent_partid': 88}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 106, 'amount_msat': 1941130msat, 'parent_partid': 97}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 118, 'amount_msat': 1941130msat, 'parent_partid': 106}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 129, 'amount_msat': 1941130msat, 'parent_partid': 118}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 133, 'amount_msat': 1941130msat, 'parent_partid': 129}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 145, 'amount_msat': 1941130msat, 'parent_partid': 133}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 153, 'amount_msat': 1941130msat, 'parent_partid': 145}, {'status': 'failed', 'failreason': 'failed: WIRE_EXPIRY_TOO_SOON (reply from remote)', 'partid': 166, 'amount_msat': 1941130msat, 'parent_partid': 153}]}
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-07-22 03:44:43 +02:00
# Make sure (esp in non-dev-mode) blockheights agree so we don't WIRE_EXPIRY_TOO_SOON...
sync_blockheight ( bitcoind , [ l1 , l2 , l3 ] )
2022-05-25 17:03:31 +02:00
inv = l3 . rpc . invoice ( 42 * 10 * * 6 , ' inv1 ' , ' desc ' ) [ ' bolt11 ' ]
2022-05-25 16:14:47 +02:00
l1 . rpc . pay ( inv )
2022-05-25 17:03:31 +02:00
# And now try the other way around: zeroconf channel first
# followed by a public one.
wait_for ( lambda : len ( l3 . rpc . listchannels ( ) [ ' channels ' ] ) == 4 )
pytest: fix flake in test_zeroconf_forward
Second pay can fail if first is not completely settled:
```
def test_zeroconf_forward(node_factory, bitcoind):
"""Ensure that we can use zeroconf channels in forwards.
...
# Make sure (esp in non-dev-mode) blockheights agree so we don't WIRE_EXPIRY_TOO_SOON...
sync_blockheight(bitcoind, [l1, l2, l3])
inv = l3.rpc.invoice(42 * 10**6, 'inv1', 'desc')['bolt11']
l1.rpc.pay(inv)
# And now try the other way around: zeroconf channel first
# followed by a public one.
wait_for(lambda: len(l3.rpc.listchannels()['channels']) == 4)
inv = l1.rpc.invoice(42, 'back1', 'desc')['bolt11']
> l3.rpc.pay(inv)
...
> raise RpcError(method, payload, resp['error'])
E pyln.client.lightning.RpcError: RPC call failed: method: pay, payload: {'bolt11': 'lnbcrt420p1p3junrssp588gtjzmlrr4pfj7ssmdlulzhhushrpq3rdqxjuz2m33scsvzdjlspp5fk5yhu6netc0d0sgp8es52vjk6akhd3uayr08u8max4d8rwzpjuqdq8v3jhxccxqyjw5qcqp99qyysgqcpyyugejv4nya97v6gw8fhtr0ru3vq87jjlltav99wlat436a95n0z8yzdp699p9md0zz9tmnsjpvfj622n9g9fh7r6ldhpgh9wmr4qpcru3rk'}, error: {'code': 210, 'message': 'Destination 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 is not reachable directly and all routehints were unusable.', 'attempts': [{'status': 'failed', 'failreason': 'Destination 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 is not reachable directly and all routehints were unusable.', 'partid': 0, 'amount_msat': 42msat}]}
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-09-24 06:27:09 +02:00
# Make sure all htlcs completely settled!
2023-01-12 02:25:55 +01:00
wait_for ( lambda : ( p [ ' htlcs ' ] == [ ] for p in l2 . rpc . listpeerchannels ( ) [ ' channels ' ] ) )
pytest: fix flake in test_zeroconf_forward
Second pay can fail if first is not completely settled:
```
def test_zeroconf_forward(node_factory, bitcoind):
"""Ensure that we can use zeroconf channels in forwards.
...
# Make sure (esp in non-dev-mode) blockheights agree so we don't WIRE_EXPIRY_TOO_SOON...
sync_blockheight(bitcoind, [l1, l2, l3])
inv = l3.rpc.invoice(42 * 10**6, 'inv1', 'desc')['bolt11']
l1.rpc.pay(inv)
# And now try the other way around: zeroconf channel first
# followed by a public one.
wait_for(lambda: len(l3.rpc.listchannels()['channels']) == 4)
inv = l1.rpc.invoice(42, 'back1', 'desc')['bolt11']
> l3.rpc.pay(inv)
...
> raise RpcError(method, payload, resp['error'])
E pyln.client.lightning.RpcError: RPC call failed: method: pay, payload: {'bolt11': 'lnbcrt420p1p3junrssp588gtjzmlrr4pfj7ssmdlulzhhushrpq3rdqxjuz2m33scsvzdjlspp5fk5yhu6netc0d0sgp8es52vjk6akhd3uayr08u8max4d8rwzpjuqdq8v3jhxccxqyjw5qcqp99qyysgqcpyyugejv4nya97v6gw8fhtr0ru3vq87jjlltav99wlat436a95n0z8yzdp699p9md0zz9tmnsjpvfj622n9g9fh7r6ldhpgh9wmr4qpcru3rk'}, error: {'code': 210, 'message': 'Destination 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 is not reachable directly and all routehints were unusable.', 'attempts': [{'status': 'failed', 'failreason': 'Destination 0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518 is not reachable directly and all routehints were unusable.', 'partid': 0, 'amount_msat': 42msat}]}
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-09-24 06:27:09 +02:00
2022-05-25 17:03:31 +02:00
inv = l1 . rpc . invoice ( 42 , ' back1 ' , ' desc ' ) [ ' bolt11 ' ]
l3 . rpc . pay ( inv )
2022-07-09 00:07:06 +02:00
@pytest.mark.openchannel ( ' v1 ' )
def test_buy_liquidity_ad_no_v2 ( node_factory , bitcoind ) :
""" Test that you can ' t actually request amt for a
node that doesn ' support v2 opens " " "
l1 , l2 , = node_factory . get_nodes ( 2 )
amount = 500000
feerate = 2000
l1 . fundwallet ( amount * 100 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
# l1 leases a channel from l2
with pytest . raises ( RpcError , match = r " Tried to buy a liquidity ad but we[(][?][)] don ' t have experimental-dual-fund enabled " ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = ' 029a002d000000004b2003e8 ' )
2022-08-08 04:42:52 +02:00
2022-09-13 18:02:53 +02:00
@pytest.mark.openchannel ( ' v2 ' )
def test_v2_replay_bookkeeping ( node_factory , bitcoind ) :
""" Test that your bookkeeping for a liquidity ad is good
even if we replay the opening and locking tx !
"""
opts = [ { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
2023-06-29 02:14:09 +02:00
' rescan ' : 10 , ' funding-confirms ' : 6 , ' may_reconnect ' : True ,
' experimental-anchors ' : None } ,
2022-09-13 18:02:53 +02:00
{ ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
2023-06-29 02:14:09 +02:00
' may_reconnect ' : True ,
' experimental-anchors ' : None } ]
2022-10-19 20:35:45 +02:00
2022-09-13 18:02:53 +02:00
l1 , l2 , = node_factory . get_nodes ( 2 , opts = opts )
amount = 500000
feerate = 2000
l1 . fundwallet ( amount * 100 )
l2 . fundwallet ( amount * 100 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
rates = l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount )
# l1 leases a channel from l2
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = rates [ ' compact_lease ' ] )
# add the funding transaction
bitcoind . generate_block ( 4 , wait_for_mempool = 1 )
l1 . restart ( )
bitcoind . generate_block ( 2 )
l1 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
chan_id = first_channel_id ( l1 , l2 )
ev_tags = [ e [ ' tag ' ] for e in l1 . rpc . bkpr_listaccountevents ( chan_id ) [ ' events ' ] ]
assert ' lease_fee ' in ev_tags
# This should work ok
l1 . rpc . bkpr_listbalances ( )
bitcoind . generate_block ( 2 )
sync_blockheight ( bitcoind , [ l1 ] )
l1 . restart ( )
chan_id = first_channel_id ( l1 , l2 )
ev_tags = [ e [ ' tag ' ] for e in l1 . rpc . bkpr_listaccountevents ( chan_id ) [ ' events ' ] ]
assert ' lease_fee ' in ev_tags
l1 . rpc . close ( l2 . info [ ' id ' ] , 1 )
bitcoind . generate_block ( 6 , wait_for_mempool = 1 )
l1 . daemon . wait_for_log ( ' to ONCHAIN ' )
l2 . daemon . wait_for_log ( ' to ONCHAIN ' )
# This should not crash
l1 . rpc . bkpr_listbalances ( )
2022-09-13 01:49:26 +02:00
@pytest.mark.openchannel ( ' v2 ' )
def test_buy_liquidity_ad_check_bookkeeping ( node_factory , bitcoind ) :
""" Test that your bookkeeping for a liquidity ad is good. """
opts = [ { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
' rescan ' : 10 , ' disable-plugin ' : ' bookkeeper ' ,
2023-06-29 02:14:09 +02:00
' funding-confirms ' : 6 , ' may_reconnect ' : True ,
' experimental-anchors ' : None } ,
2022-09-13 01:49:26 +02:00
{ ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
2022-09-13 16:53:46 +02:00
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
2023-06-29 02:14:09 +02:00
' may_reconnect ' : True ,
' experimental-anchors ' : None } ]
2022-10-19 20:35:45 +02:00
2022-09-13 01:49:26 +02:00
l1 , l2 , = node_factory . get_nodes ( 2 , opts = opts )
amount = 500000
feerate = 2000
l1 . fundwallet ( amount * 100 )
l2 . fundwallet ( amount * 100 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
rates = l1 . rpc . dev_queryrates ( l2 . info [ ' id ' ] , amount , amount )
# l1 leases a channel from l2
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = rates [ ' compact_lease ' ] )
# add the funding transaction
bitcoind . generate_block ( 4 , wait_for_mempool = 1 )
l1 . stop ( )
del l1 . daemon . opts [ ' disable-plugin ' ]
l1 . start ( )
2022-09-13 16:53:46 +02:00
bitcoind . generate_block ( 2 )
l1 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
2022-09-13 01:49:26 +02:00
chan_id = first_channel_id ( l1 , l2 )
ev_tags = [ e [ ' tag ' ] for e in l1 . rpc . bkpr_listaccountevents ( chan_id ) [ ' events ' ] ]
assert ' lease_fee ' in ev_tags
# This should work ok
l1 . rpc . bkpr_listbalances ( )
l1 . rpc . close ( l2 . info [ ' id ' ] , 1 )
bitcoind . generate_block ( 6 , wait_for_mempool = 1 )
l1 . daemon . wait_for_log ( ' to ONCHAIN ' )
l2 . daemon . wait_for_log ( ' to ONCHAIN ' )
2022-09-13 16:53:46 +02:00
# This should not crash
2022-09-13 01:49:26 +02:00
l1 . rpc . bkpr_listbalances ( )
2022-08-08 04:42:52 +02:00
def test_scid_alias_private ( node_factory , bitcoind ) :
""" Test that we don ' t allow use of real scid for scid_alias-type channels """
l1 , l2 , l3 = node_factory . line_graph ( 3 , fundchannel = False , opts = [ { } , { } ,
{ ' log-level ' : ' io ' } ] )
l2 . fundwallet ( 5000000 )
l2 . rpc . fundchannel ( l3 . info [ ' id ' ] , ' all ' , announce = False )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
2023-01-12 02:25:55 +01:00
wait_for ( lambda : only_one ( l2 . rpc . listpeerchannels ( l3 . info [ ' id ' ] ) [ ' channels ' ] ) [ ' state ' ] == ' CHANNELD_NORMAL ' )
2022-08-08 04:42:52 +02:00
2023-01-12 02:25:55 +01:00
chan = only_one ( l2 . rpc . listpeerchannels ( l3 . info [ ' id ' ] ) [ ' channels ' ] )
2022-08-08 04:42:52 +02:00
assert chan [ ' private ' ] is True
scid23 = chan [ ' short_channel_id ' ]
alias23 = chan [ ' alias ' ] [ ' local ' ]
# Create l1<->l2 channel, make sure l3 sees it so it will routehint via
# l2 (otherwise it sees it as a deadend!)
l1 . fundwallet ( 5000000 )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , ' all ' )
bitcoind . generate_block ( 6 , wait_for_mempool = 1 )
wait_for ( lambda : len ( l3 . rpc . listchannels ( source = l1 . info [ ' id ' ] ) [ ' channels ' ] ) == 1 )
2023-01-12 02:25:55 +01:00
chan = only_one ( l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] )
2022-08-08 04:42:52 +02:00
assert chan [ ' private ' ] is False
scid12 = chan [ ' short_channel_id ' ]
# Make sure it sees both sides of private channel in gossmap!
wait_for ( lambda : len ( l3 . rpc . listchannels ( ) [ ' channels ' ] ) == 4 )
# BOLT #2:
# - if `channel_type` has `option_scid_alias` set:
# - MUST NOT use the real `short_channel_id` in BOLT 11 `r` fields.
inv = l3 . rpc . invoice ( 10 , ' test_scid_alias_private ' , ' desc ' )
assert only_one ( only_one ( l1 . rpc . decode ( inv [ ' bolt11 ' ] ) [ ' routes ' ] ) ) [ ' short_channel_id ' ] == alias23
# BOLT #2:
# - if `channel_type` has `option_scid_alias` set:
# - MUST NOT allow incoming HTLCs to this channel using the real `short_channel_id`
route = [ { ' amount_msat ' : 11 ,
' id ' : l2 . info [ ' id ' ] ,
' delay ' : 12 ,
' channel ' : scid12 } ,
{ ' amount_msat ' : 10 ,
' id ' : l3 . info [ ' id ' ] ,
' delay ' : 6 ,
' channel ' : scid23 } ]
l1 . rpc . sendpay ( route , inv [ ' payment_hash ' ] , payment_secret = inv [ ' payment_secret ' ] )
with pytest . raises ( RpcError ) as err :
l1 . rpc . waitsendpay ( inv [ ' payment_hash ' ] )
# PERM|10
WIRE_UNKNOWN_NEXT_PEER = 0x4000 | 10
assert err . value . error [ ' data ' ] [ ' failcode ' ] == WIRE_UNKNOWN_NEXT_PEER
assert err . value . error [ ' data ' ] [ ' erring_node ' ] == l2 . info [ ' id ' ]
assert err . value . error [ ' data ' ] [ ' erring_channel ' ] == scid23
# BOLT #2
# - MUST always recognize the `alias` as a `short_channel_id` for incoming HTLCs to this channel.
route [ 1 ] [ ' channel ' ] = alias23
l1 . rpc . sendpay ( route , inv [ ' payment_hash ' ] , payment_secret = inv [ ' payment_secret ' ] )
l1 . rpc . waitsendpay ( inv [ ' payment_hash ' ] )
2022-08-16 15:32:09 +02:00
def test_zeroconf_multichan_forward ( node_factory ) :
""" The freedom to choose the forward channel bytes us when it is 0conf
Reported by Breez , we crashed when logging in ` forward_htlc ` when
the replacement channel was a zeroconf channel .
l2 - > l3 is a double channel with the zeroconf channel having a
higher spendable msat , which should cause it to be chosen instead .
"""
node_id = ' 022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59 '
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroconf-selective.py "
l1 , l2 , l3 = node_factory . line_graph ( 3 , opts = [
{ } ,
{ } ,
{
' plugin ' : str ( plugin_path ) ,
' zeroconf-allow ' : node_id ,
}
] , fundamount = 10 * * 6 , wait_for_announce = True )
# Just making sure the allowlisted node_id matches.
assert l2 . info [ ' id ' ] == node_id
# Now create a channel that is twice as large as the real channel,
# and don't announce it.
l2 . fundwallet ( 10 * * 7 )
2022-09-25 15:14:12 +02:00
zeroconf_cid = l2 . rpc . fundchannel ( l3 . info [ ' id ' ] , 2 * 10 * * 6 , mindepth = 0 ) [ ' channel_id ' ]
2022-08-16 15:32:09 +02:00
2022-09-10 04:10:31 +02:00
l2 . daemon . wait_for_log ( r ' peer_in WIRE_CHANNEL_READY ' )
l3 . daemon . wait_for_log ( r ' peer_in WIRE_CHANNEL_READY ' )
2022-08-16 15:32:09 +02:00
inv = l3 . rpc . invoice ( amount_msat = 10000 , label = ' lbl1 ' , description = ' desc ' ) [ ' bolt11 ' ]
l1 . rpc . pay ( inv )
2022-09-25 15:14:12 +02:00
2023-01-12 02:25:55 +01:00
for c in l2 . rpc . listpeerchannels ( l3 . info [ ' id ' ] ) [ ' channels ' ] :
2022-09-25 15:14:12 +02:00
if c [ ' channel_id ' ] == zeroconf_cid :
zeroconf_scid = c [ ' alias ' ] [ ' local ' ]
else :
normal_scid = c [ ' short_channel_id ' ]
assert l2 . daemon . is_in_log ( r ' Chose a better channel than {} : {} '
. format ( normal_scid , zeroconf_scid ) )
2022-06-09 17:07:03 +02:00
2023-06-01 08:42:43 +02:00
@pytest.mark.developer ( " dev-allowdustreserve " )
2022-06-09 17:07:03 +02:00
def test_zeroreserve ( node_factory , bitcoind ) :
""" Ensure we can set the reserves.
3 nodes :
- l1 enforces zeroreserve
- l2 enforces default reserve
- l3 enforces sub - dust reserves
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroreserve.py "
opts = [
{
' plugin ' : str ( plugin_path ) ,
' reserve ' : ' 0sat ' ,
2022-06-24 12:58:26 +02:00
' dev-allowdustreserve ' : True ,
} ,
{
' dev-allowdustreserve ' : True ,
2022-06-09 17:07:03 +02:00
} ,
{
' plugin ' : str ( plugin_path ) ,
2022-06-24 12:58:26 +02:00
' reserve ' : ' 123sat ' ,
' dev-allowdustreserve ' : True ,
2022-06-09 17:07:03 +02:00
}
]
l1 , l2 , l3 = node_factory . get_nodes ( 3 , opts = opts )
l1 . fundwallet ( 10 * * 7 )
l2 . fundwallet ( 10 * * 7 )
l3 . fundwallet ( 10 * * 7 )
l1 . connect ( l2 )
l2 . connect ( l3 )
l3 . connect ( l1 )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , 10 * * 6 , reserve = ' 0sat ' )
l2 . rpc . fundchannel ( l3 . info [ ' id ' ] , 10 * * 6 )
l3 . rpc . fundchannel ( l1 . info [ ' id ' ] , 10 * * 6 , reserve = ' 321sat ' )
bitcoind . generate_block ( 1 , wait_for_mempool = 3 )
wait_for ( lambda : l1 . channel_state ( l2 ) == ' CHANNELD_NORMAL ' )
wait_for ( lambda : l2 . channel_state ( l3 ) == ' CHANNELD_NORMAL ' )
wait_for ( lambda : l3 . channel_state ( l1 ) == ' CHANNELD_NORMAL ' )
# Now make sure we all agree on each others reserves
2023-01-12 02:25:55 +01:00
l1c1 = l1 . rpc . listpeerchannels ( l2 . info [ ' id ' ] ) [ ' channels ' ] [ 0 ]
l2c1 = l2 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] [ 0 ]
l2c2 = l2 . rpc . listpeerchannels ( l3 . info [ ' id ' ] ) [ ' channels ' ] [ 0 ]
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 ]
2022-06-09 17:07:03 +02:00
# l1 imposed a 0sat reserve on l2, while l2 imposed the default 1% reserve on l1
2022-06-24 12:58:26 +02:00
assert l1c1 [ ' their_reserve_msat ' ] == l2c1 [ ' our_reserve_msat ' ] == Millisatoshi ( ' 0sat ' )
assert l1c1 [ ' our_reserve_msat ' ] == l2c1 [ ' their_reserve_msat ' ] == Millisatoshi ( ' 10000sat ' )
2022-06-09 17:07:03 +02:00
# l2 imposed the default 1% on l3, while l3 imposed a custom 123sat fee on l2
2022-06-24 12:58:26 +02:00
assert l2c2 [ ' their_reserve_msat ' ] == l3c2 [ ' our_reserve_msat ' ] == Millisatoshi ( ' 10000sat ' )
assert l2c2 [ ' our_reserve_msat ' ] == l3c2 [ ' their_reserve_msat ' ] == Millisatoshi ( ' 123sat ' )
2022-06-09 17:07:03 +02:00
# l3 imposed a custom 321sat fee on l1, while l1 imposed a custom 0sat fee on l3
2022-06-24 12:58:26 +02:00
assert l3c3 [ ' their_reserve_msat ' ] == l1c3 [ ' our_reserve_msat ' ] == Millisatoshi ( ' 321sat ' )
assert l3c3 [ ' our_reserve_msat ' ] == l1c3 [ ' their_reserve_msat ' ] == Millisatoshi ( ' 0sat ' )
2022-06-09 17:07:03 +02:00
# Now do some drain tests on c1, as that should be drainable
# completely by l2 being the fundee
l1 . rpc . keysend ( l2 . info [ ' id ' ] , 10 * 7 ) # Something above dust for sure
l2 . drain ( l1 )
# Remember that this is the reserve l1 imposed on l2, so l2 can drain completely
2023-01-12 02:25:55 +01:00
l2c1 = l2 . rpc . listpeerchannels ( l1 . info [ ' id ' ] ) [ ' channels ' ] [ 0 ]
2022-06-09 17:07:03 +02:00
# And despite us briefly being above dust (with a to_us output),
# closing should result in the output being trimmed again since we
# dropped below dust again.
c = l2 . rpc . close ( l1 . info [ ' id ' ] )
decoded = bitcoind . rpc . decoderawtransaction ( c [ ' tx ' ] )
2022-06-24 12:42:45 +02:00
# Elements has a change output always
assert len ( decoded [ ' vout ' ] ) == 1 if TEST_NETWORK == ' regtest ' else 2
2023-06-01 08:42:43 +02:00
@pytest.mark.developer ( " dev-allowdustreserve " )
2022-06-24 12:42:45 +02:00
def test_zeroreserve_mixed ( node_factory , bitcoind ) :
""" l1 runs with zeroreserve, l2 and l3 without, should still work
Basically tests that l1 doesn ' t get upset when l2 allows us to
drop below dust .
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroreserve.py "
opts = [
{
' plugin ' : str ( plugin_path ) ,
' reserve ' : ' 0sat ' ,
' dev-allowdustreserve ' : True ,
} , {
' dev-allowdustreserve ' : False ,
} , {
' dev-allowdustreserve ' : False ,
}
]
l1 , l2 , l3 = node_factory . get_nodes ( 3 , opts = opts )
l1 . fundwallet ( 10 * * 7 )
l3 . fundwallet ( 10 * * 7 )
l1 . connect ( l2 )
l3 . connect ( l1 )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , 10 * * 6 , reserve = ' 0sat ' )
l3 . rpc . fundchannel ( l1 . info [ ' id ' ] , 10 * * 6 )
2022-08-16 17:55:34 +02:00
2023-06-01 08:42:43 +02:00
@pytest.mark.developer ( " dev-allowdustreserve " )
2022-08-16 17:55:34 +02:00
def test_zeroreserve_alldust ( node_factory ) :
""" If we allow dust reserves we need larger fundings
This is because we might have up to
allhtlcs = ( local . max_concurrent_htlcs + remote . max_concurrent_htlcs )
alldust = allhlcs * min ( local . dust , remote . dust )
allocated to HTLCs in flight , reducing both direct outputs to
dust . This could leave us with no outs on the commitment , is
therefore invalid .
Parameters are as follows :
- Regtest :
- max_concurrent_htlcs = 483
- dust = 546 sat
- minfunding = ( 483 * 2 + 2 ) * 546 sat = 528528 sat
- Mainnet :
- max_concurrent_htlcs = 30
- dust = 546 sat
- minfunding = ( 30 * 2 + 2 ) * 546 sat = 33852 s
"""
plugin_path = Path ( __file__ ) . parent / " plugins " / " zeroreserve.py "
l1 , l2 = node_factory . get_nodes ( 2 , opts = [ {
' plugin ' : plugin_path ,
' reserve ' : ' 0sat ' ,
' dev-allowdustreserve ' : True
} ] * 2 )
maxhtlc = 483
mindust = 546
minfunding = ( maxhtlc * 2 + 2 ) * mindust
l1 . fundwallet ( 10 * * 6 )
error = ( f ' channel funding { minfunding } sat too small for chosen parameters: '
f ' a total of { maxhtlc * 2 } HTLCs with dust value { mindust } sat would '
f ' result in a commitment_transaction without outputs ' )
# This is right on the edge, and should fail
with pytest . raises ( RpcError , match = error ) :
l1 . connect ( l2 )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , minfunding )
# Now try with just a bit more
l1 . connect ( l2 )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , minfunding + 1 )
2022-10-17 20:10:12 +02:00
def test_coinbase_unspendable ( node_factory , bitcoind ) :
""" A node should not be able to spend a coinbase output
before it ' s mature " " "
[ l1 ] = node_factory . get_nodes ( 1 )
addr = l1 . rpc . newaddr ( ) [ " bech32 " ]
bitcoind . rpc . generatetoaddress ( 1 , addr )
addr2 = l1 . rpc . newaddr ( ) [ " bech32 " ]
# Wait til money in wallet
wait_for ( lambda : len ( l1 . rpc . listfunds ( ) [ ' outputs ' ] ) == 1 )
2022-10-17 21:59:57 +02:00
out = only_one ( l1 . rpc . listfunds ( ) [ ' outputs ' ] )
assert out [ ' status ' ] == ' immature '
with pytest . raises ( RpcError , match = ' Could not afford all using all 0 available UTXOs ' ) :
l1 . rpc . withdraw ( addr2 , " all " )
2022-10-17 20:10:12 +02:00
# Nothing sent to the mempool!
assert len ( bitcoind . rpc . getrawmempool ( ) ) == 0
2022-10-17 21:59:57 +02:00
# Mine 98 blocks
bitcoind . rpc . generatetoaddress ( 98 , l1 . rpc . newaddr ( ) [ ' bech32 ' ] )
assert len ( [ out for out in l1 . rpc . listfunds ( ) [ ' outputs ' ] if out [ ' status ' ] == ' confirmed ' ] ) == 0
with pytest . raises ( RpcError , match = ' Could not afford all using all 0 available UTXOs ' ) :
l1 . rpc . withdraw ( addr2 , " all " )
# One more and the first coinbase unlocks
bitcoind . rpc . generatetoaddress ( 1 , l1 . rpc . newaddr ( ) [ ' bech32 ' ] )
wait_for ( lambda : len ( l1 . rpc . listfunds ( ) [ ' outputs ' ] ) == 100 )
assert len ( [ out for out in l1 . rpc . listfunds ( ) [ ' outputs ' ] if out [ ' status ' ] == ' confirmed ' ] ) == 1
l1 . rpc . withdraw ( addr2 , " all " )
# One tx in the mempool now!
assert len ( bitcoind . rpc . getrawmempool ( ) ) == 1
# Mine one block, assert one more is spendable
bitcoind . rpc . generatetoaddress ( 1 , l1 . rpc . newaddr ( ) [ ' bech32 ' ] )
assert len ( [ out for out in l1 . rpc . listfunds ( ) [ ' outputs ' ] if out [ ' status ' ] == ' confirmed ' ] ) == 1
2022-10-19 22:45:29 +02:00
2023-01-13 02:30:20 +01:00
@pytest.mark.openchannel ( ' v2 ' )
def test_openchannel_no_confirmed_inputs_opener ( node_factory , bitcoind ) :
""" If the opener flags ' require-confirmed-inputs ' for an open,
and accepter sends unconfirmed inputs check that the
accepter aborts the open """
l1_opts = { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
' may_reconnect ' : True , ' funder-lease-requests-only ' : False ,
' allow_warning ' : True }
l2_opts = l1_opts . copy ( )
l1_opts [ ' require-confirmed-inputs ' ] = True
l1 , l2 = node_factory . get_nodes ( 2 , opts = [ l1_opts , l2_opts ] )
2023-06-02 04:36:04 +02:00
assert l1 . rpc . listconfigs ( ) [ ' configs ' ] [ ' require-confirmed-inputs ' ] [ ' value_bool ' ] is True
2023-01-13 02:30:20 +01:00
amount = 500000
l1 . fundwallet ( 20000000 )
l2 . fundwallet ( 20000000 )
utxo_lookups = set ( )
def _no_utxo_response ( r ) :
utxo_lookups . add ( tuple ( r [ ' params ' ] ) )
return { ' id ' : r [ ' id ' ] , ' result ' : None }
# We mock l1 out such that it thinks no inputs are confirmed
l1 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , _no_utxo_response )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
# l1 should return an error + abort the open as it thinks it's
# sending unconfirmed inputs to a peer that's requested only
# confirmed inputs
with pytest . raises ( RpcError , match = r ' Input .* is not confirmed ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount )
assert l1 . daemon . is_in_log ( ' validating psbt for role: accepter ' )
# Verify that the looked up utxo is l2's
# Build a set of outpoints for node (l2)
outs = { ( out [ ' txid ' ] , out [ ' output ' ] ) for out in l2 . rpc . listfunds ( ) [ ' outputs ' ] }
# Confirm that seen utxo lookups are a subset of l2's outpoints
assert utxo_lookups < = outs
@pytest.mark.openchannel ( ' v2 ' )
def test_openchannel_no_unconfirmed_inputs_accepter ( node_factory , bitcoind ) :
""" If the accepter flags ' require-confirmed-inputs ' for an open,
and opener send unconfirmed inputs check that the
accepter aborts the open """
l1_opts = { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
' may_reconnect ' : True , ' funder-lease-requests-only ' : False ,
' allow_warning ' : True }
l2_opts = l1_opts . copy ( )
l2_opts [ ' require-confirmed-inputs ' ] = True
l1 , l2 = node_factory . get_nodes ( 2 , opts = [ l1_opts , l2_opts ] )
2023-06-02 04:36:04 +02:00
assert l2 . rpc . listconfigs ( ) [ ' configs ' ] [ ' require-confirmed-inputs ' ] [ ' value_bool ' ] is True
2023-01-13 02:30:20 +01:00
amount = 500000
l1 . fundwallet ( 20000000 )
l1 . fundwallet ( 20000000 )
l2 . fundwallet ( 20000000 )
utxo_lookups = set ( )
def _verify_utxos ( n , lookedup ) :
# Build a set of outpoints for node (l2)
outs = { ( out [ ' txid ' ] , out [ ' output ' ] ) for out in n . rpc . listfunds ( ) [ ' outputs ' ] }
# Confirm that seen utxo lookups are a subset of l2's outpoints
assert lookedup < = outs
lookedup . clear ( )
def _no_utxo_response ( r ) :
utxo_lookups . add ( tuple ( r [ ' params ' ] ) )
# Check that the utxo belongs to l2
return { ' id ' : r [ ' id ' ] , ' result ' : None }
l1 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , _no_utxo_response )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
# l1 should return an error + abort the open as it thinks it's
# sending unconfirmed inputs to a peer that's requested only
# confirmed inputs
with pytest . raises ( RpcError , match = r ' Input .* is not confirmed ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount )
_verify_utxos ( l1 , utxo_lookups )
l1 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , None )
l2 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , _no_utxo_response )
# l2 should return an error + abort the open
with pytest . raises ( RpcError , match = r ' Input .* is not confirmed ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount )
_verify_utxos ( l1 , utxo_lookups )
# Let's negotiate the open, remove option from l2, and then RBF
# Turn the txout unconfirmed off, so we can open a channel
l2 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , None )
res = l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount )
l1 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
l2 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
# Remove option from l2
l2 . stop ( )
del l2 . daemon . opts [ ' require-confirmed-inputs ' ]
l2 . start ( )
2023-06-02 04:36:04 +02:00
assert l2 . rpc . listconfigs ( ) [ ' configs ' ] [ ' require-confirmed-inputs ' ] [ ' value_bool ' ] is False
2023-01-13 02:30:20 +01:00
# Turn the mock back on so we pretend everything l1 sends is unconf
l2 . daemon . rpcproxy . mock_rpc ( ' gettxout ' , _no_utxo_response )
# Prep for RBF
startweight = 42 + 172 # base weight, funding output
next_feerate = find_next_feerate ( l1 , l2 )
psbt = l1 . rpc . fundpsbt ( amount , next_feerate , startweight ,
min_witness_weight = 110 ,
excess_as_change = True ) [ ' psbt ' ]
# Attempt bump, fail. L2 should remember required-confirmed-inputs
# from original channel negotiation, despite node-wide setting
# being flagged off
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
bump = l1 . rpc . openchannel_bump ( res [ ' channel_id ' ] , amount , psbt )
with pytest . raises ( RpcError , match = r ' Input .* is not confirmed ' ) :
l1 . rpc . openchannel_update ( res [ ' channel_id ' ] , bump [ ' psbt ' ] )
_verify_utxos ( l1 , utxo_lookups )
2023-05-22 02:51:44 +02:00
@unittest.skip ( " anchors not available " )
2023-06-29 02:14:09 +02:00
@pytest.mark.developer ( " dev-queryrates required " )
2022-10-19 22:45:29 +02:00
@pytest.mark.openchannel ( ' v2 ' )
def test_no_anchor_liquidity_ads ( node_factory , bitcoind ) :
""" Liquidity ads requires anchors, which are no longer a
requirement for dual - funded channels . """
2023-06-29 02:14:09 +02:00
l2_opts = { ' funder-policy ' : ' match ' , ' funder-policy-mod ' : 100 ,
2022-10-19 22:45:29 +02:00
' lease-fee-base-sat ' : ' 100sat ' , ' lease-fee-basis ' : 100 ,
' may_reconnect ' : True , ' funder-lease-requests-only ' : False }
2023-06-29 02:14:09 +02:00
l1_opts = l2_opts . copy ( )
l1_opts [ ' experimental-anchors ' ] = None
2022-10-19 22:45:29 +02:00
l1 , l2 = node_factory . get_nodes ( 2 , opts = [ l1_opts , l2_opts ] )
feerate = 2000
amount = 10 * * 6
l1 . fundwallet ( 10 * * 8 )
l2 . fundwallet ( 10 * * 8 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
with pytest . raises ( RpcError , match = r ' liquidity ads not supported, no anchors. ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount , request_amt = amount ,
feerate = ' {} perkw ' . format ( feerate ) ,
compact_lease = ' 029a002d000000004b2003e8 ' )
# But you can make it work without the liquidity ad request
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , amount ,
feerate = ' {} perkw ' . format ( feerate ) )
# Confirm that we used the DUAL_FUND flow
chan = only_one ( only_one ( l1 . rpc . listpeers ( ) [ ' peers ' ] ) [ ' channels ' ] )
assert chan [ ' state ' ] == ' DUALOPEND_AWAITING_LOCKIN '
assert chan [ ' funding ' ] [ ' local_funds_msat ' ] == chan [ ' funding ' ] [ ' remote_funds_msat ' ]
assert ' option_anchor_outputs ' not in chan [ ' features ' ]
2023-06-26 01:03:21 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd has different feerates ' )
2023-06-29 02:14:09 +02:00
@pytest.mark.parametrize ( " anchors " , [ False , True ] )
def test_commitment_feerate ( bitcoind , node_factory , anchors ) :
opts = { }
if anchors :
opts [ ' experimental-anchors ' ] = None
l1 , l2 = node_factory . get_nodes ( 2 , opts = opts )
2023-06-26 01:03:21 +02:00
2023-06-26 01:11:21 +02:00
opening_feerate = 2000
2023-06-29 02:14:09 +02:00
if anchors :
2023-06-26 01:11:21 +02:00
# anchors use lowball fees
commitment_feerate = 3750
else :
commitment_feerate = opening_feerate
2023-06-26 01:03:21 +02:00
l1 . fundwallet ( 10 * * 8 )
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , 10 * * 6 ,
feerate = f ' { opening_feerate } perkw ' )
wait_for ( lambda : bitcoind . rpc . getrawmempool ( ) != [ ] )
tx = only_one ( [ t for t in bitcoind . rpc . getrawmempool ( True ) . values ( ) ] )
feerate_perkw = int ( tx [ ' fees ' ] [ ' base ' ] * 100_000_000 ) / ( tx [ ' weight ' ] / 1000 )
assert opening_feerate - 10 < feerate_perkw < opening_feerate + 10
bitcoind . generate_block ( 1 )
l2 . stop ( )
l1 . rpc . close ( l2 . info [ ' id ' ] , unilateraltimeout = 1 )
# feerate for this will be the same
wait_for ( lambda : bitcoind . rpc . getrawmempool ( ) != [ ] )
tx = only_one ( [ t for t in bitcoind . rpc . getrawmempool ( True ) . values ( ) ] )
fee = int ( tx [ ' fees ' ] [ ' base ' ] * 100_000_000 )
# Weight is idealized worst case, and we don't meet it!
2023-06-29 02:14:09 +02:00
if anchors :
2023-06-26 01:03:21 +02:00
# 200 is the approximate cost estimate used for anchor outputs.
assert tx [ ' weight ' ] < 1124 - 200
else :
assert tx [ ' weight ' ] < 724
2023-06-29 02:14:09 +02:00
if anchors :
2023-06-26 01:03:21 +02:00
# We pay for two anchors, but only produce one.
fee - = 330
weight = 1124
else :
weight = 724
feerate_perkw = fee / ( weight / 1000 )
assert commitment_feerate - 10 < feerate_perkw < commitment_feerate + 10
2023-06-29 02:14:10 +02:00
@unittest.skipIf ( TEST_NETWORK != ' regtest ' , ' elementsd has different tx costs ' )
def test_anchor_min_emergency ( bitcoind , node_factory ) :
l1 , l2 = node_factory . line_graph ( 2 , opts = { ' experimental-anchors ' : None } ,
fundchannel = False )
addr = l1 . rpc . newaddr ( ) [ ' bech32 ' ]
bitcoind . rpc . sendtoaddress ( addr , 5000000 / 10 * * 8 )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
wait_for ( lambda : l1 . rpc . listfunds ( ) [ ' outputs ' ] != [ ] )
# Cost of tx itself is 3637.
with pytest . raises ( RpcError , match = r ' We would not have enough left for min-emergency-msat 25000sat ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , f ' { 5000000 - 3637 } sat ' )
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , ' all ' )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
# Wait for l1 to see that spend.
wait_for ( lambda : len ( l1 . rpc . listfunds ( ) [ ' outputs ' ] ) == 1 )
# Default is 25000 sats.
assert only_one ( l1 . rpc . listfunds ( ) [ ' outputs ' ] ) [ ' amount_msat ' ] == Millisatoshi ( ' 25000sat ' )
# And we can't spend it, either!
addr2 = l2 . rpc . newaddr ( ) [ ' bech32 ' ]
with pytest . raises ( RpcError , match = r ' We would not have enough left for min-emergency-msat 25000sat ' ) :
l1 . rpc . withdraw ( addr2 , ' 500sat ' )
with pytest . raises ( RpcError , match = r ' We would not have enough left for min-emergency-msat 25000sat ' ) :
l1 . rpc . withdraw ( addr2 , ' all ' )
2023-07-20 23:46:48 +02:00
# Make sure channeld tells gossipd about channel before we close, otherwise
# we get spurious "bad gossip" complaints if l2 sends channel_updates.
l1 . daemon . wait_for_log ( " received private channel announcement from channeld " )
2023-06-29 02:14:10 +02:00
# Even with onchain anchor channel, it still keeps reserve (just in case!).
l1 . rpc . close ( l2 . info [ ' id ' ] )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
sync_blockheight ( bitcoind , [ l1 ] )
# This workse, but will leave the emergency funds as change.
l1 . rpc . withdraw ( addr2 , ' all ' )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
sync_blockheight ( bitcoind , [ l1 ] )
wait_for ( lambda : [ ( o [ ' amount_msat ' ] , o [ ' status ' ] ) for o in l1 . rpc . listfunds ( ) [ ' outputs ' ] ] == [ ( Millisatoshi ( ' 25000sat ' ) , ' confirmed ' ) ] )
# Can't spend it!
with pytest . raises ( RpcError , match = r ' We would not have enough left for min-emergency-msat 25000sat ' ) :
l1 . rpc . withdraw ( addr2 , ' all ' )
# Once it's totally forgotten, we can spend that!
bitcoind . generate_block ( 99 )
wait_for ( lambda : l1 . rpc . listpeerchannels ( ) [ ' channels ' ] == [ ] )
# And it's *all* gone!
l1 . rpc . withdraw ( addr2 , ' all ' )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
wait_for ( lambda : l1 . rpc . listfunds ( ) [ ' outputs ' ] == [ ] )
2023-09-13 02:25:38 +02:00
def test_fundchannel_utxo_too_small ( bitcoind , node_factory ) :
l1 , l2 = node_factory . get_nodes ( 2 )
# Add 1 600 sat UTXO to a fresh node
bitcoind . rpc . sendtoaddress ( l1 . rpc . newaddr ( ) [ ' bech32 ' ] , 0.00000600 )
bitcoind . generate_block ( 1 , wait_for_mempool = 1 )
wait_for ( lambda : len ( l1 . rpc . listfunds ( ) [ ' outputs ' ] ) == 1 )
# a higher fee rate, making the 600 sat UTXO uneconomical:
l1 . rpc . connect ( l2 . info [ ' id ' ] , ' localhost ' , l2 . port )
with pytest . raises ( RpcError , match = r ' Could not afford 100000sat using all 0 available UTXOs ' ) :
l1 . rpc . fundchannel ( l2 . info [ ' id ' ] , 100000 , 10000 )