@@ -5314,16 +5314,50 @@ def send_open_channel(lconn, chain_hash, temp_chan_id, funding_sat, push_msat,
53145314 lconn .send_message (msg )
53155315
53165316
5317- def read_channel_reply (lconn ):
5318- """Read past gossip chatter to openingd's answer to our open_channel."""
5317+ def read_channel_reply_payload (lconn ):
5318+ """
5319+ Read past gossip chatter to openingd's answer to our open_channel
5320+ Returns (mtype, payload), so callers can inspect accept_channel's fields
5321+ """
53195322 for _ in range (20 ):
53205323 msg = lconn .read_message ()
53215324 mtype = int .from_bytes (msg [0 :2 ], 'big' )
53225325 if mtype in (WIRE_ACCEPT_CHANNEL , WIRE_WARNING , WIRE_ERROR ):
5323- return mtype
5326+ return mtype , msg [ 2 :]
53245327 raise AssertionError ("no reply to open_channel" )
53255328
53265329
5330+ def read_channel_reply (lconn ):
5331+ mtype , _ = read_channel_reply_payload (lconn )
5332+ return mtype
5333+
5334+
5335+ def send_open_channel_reserve (lconn , chain_hash , temp_chan_id , funding_sat , push_msat ,
5336+ dust_limit , channel_reserve , feerate_per_kw , channel_type ):
5337+ """Like send_open_channel, but with a caller-chosen dust_limit / channel_reserve"""
5338+ keys = [wire .PrivateKey (bytes ([i + 1 ] * 32 )).public_key ().serializeCompressed ()
5339+ for i in range (6 )]
5340+
5341+ msg = struct .pack ('>H' , WIRE_OPEN_CHANNEL )
5342+ msg += chain_hash
5343+ msg += temp_chan_id
5344+ msg += struct .pack ('>Q' , funding_sat ) # funding_satoshis
5345+ msg += struct .pack ('>Q' , push_msat ) # push_msat
5346+ msg += struct .pack ('>Q' , dust_limit ) # dust_limit_satoshis
5347+ msg += struct .pack ('>Q' , 0xFFFFFFFFFFFF ) # max_htlc_value_in_flight_msat
5348+ msg += struct .pack ('>Q' , channel_reserve ) # channel_reserve_satoshis
5349+ msg += struct .pack ('>Q' , 0 ) # htlc_minimum_msat
5350+ msg += struct .pack ('>I' , feerate_per_kw ) # feerate_per_kw
5351+ msg += struct .pack ('>H' , 144 ) # to_self_delay
5352+ msg += struct .pack ('>H' , 483 ) # max_accepted_htlcs
5353+ for k in keys :
5354+ msg += k
5355+ msg += struct .pack ('>B' , 0 ) # channel_flags
5356+ msg += bytes ([1 , len (channel_type )]) + channel_type # TLV 1: channel_type
5357+
5358+ lconn .send_message (msg )
5359+
5360+
53275361def send_funding_created (lconn , temp_chan_id ):
53285362 """Drive the open to the point where we build the commitment transaction.
53295363
@@ -5826,3 +5860,37 @@ def test_open_channel2_tx_add_output_sum_above_max_supply(node_factory, bitcoind
58265860
58275861 # node is still up
58285862 assert l1 .rpc .getinfo ()["id" ] == l1 .info ["id" ]
5863+
5864+
5865+ @pytest .mark .openchannel ('v1' )
5866+ def test_accept_channel_reserve_below_our_dust_limit (node_factory , bitcoind ):
5867+ """Our accept_channel must not set channel_reserve below our dust limit.
5868+
5869+ As fundee, openingd used to floor the 1% channel reserve at the peer's
5870+ dust_limit_satoshis only, never at our own. A peer opening a small
5871+ channel with a low dust limit could get an accept_channel whose
5872+ dust_limit_satoshis (546, hardcoded) exceeded its own
5873+ channel_reserve_satoshis, violating BOLT #2.
5874+ """
5875+ l1 = node_factory .get_node ()
5876+
5877+ chain_hash = bytes .fromhex (bitcoind .rpc .getblockhash (0 ))[::- 1 ]
5878+ feerate = l1 .rpc .feerates ('perkw' )['perkw' ]['opening' ]
5879+
5880+ lconn , channel_type = raw_peer_connect (l1 )
5881+ # 1% of 30000 is 300sat, floored to our peer's 354sat dust limit -- still
5882+ # under our own hardcoded 546sat dust limit. channel_reserve_satoshis=546
5883+ # keeps us clear of the separate issue #9439, and funding_sat is well
5884+ # clear of lightningd's default 10,000sat minimum effective capacity.
5885+ send_open_channel_reserve (lconn , chain_hash , os .urandom (32 ),
5886+ funding_sat = 30000 , push_msat = 0 ,
5887+ dust_limit = 354 , channel_reserve = 546 ,
5888+ feerate_per_kw = feerate , channel_type = channel_type )
5889+ mtype , payload = read_channel_reply_payload (lconn )
5890+ assert mtype == WIRE_ACCEPT_CHANNEL
5891+
5892+ dust_limit = struct .unpack ('>Q' , payload [32 :40 ])[0 ]
5893+ reserve = struct .unpack ('>Q' , payload [48 :56 ])[0 ]
5894+ assert dust_limit <= reserve , \
5895+ "accept_channel dust_limit_satoshis {} exceeds its channel_reserve_satoshis {}" .format (
5896+ dust_limit , reserve )
0 commit comments