@@ -5114,16 +5114,50 @@ def send_open_channel(lconn, chain_hash, temp_chan_id, funding_sat, push_msat,
51145114 lconn .send_message (msg )
51155115
51165116
5117- def read_channel_reply (lconn ):
5118- """Read past gossip chatter to openingd's answer to our open_channel."""
5117+ def read_channel_reply_payload (lconn ):
5118+ """
5119+ Read past gossip chatter to openingd's answer to our open_channel
5120+ Returns (mtype, payload), so callers can inspect accept_channel's fields
5121+ """
51195122 for _ in range (20 ):
51205123 msg = lconn .read_message ()
51215124 mtype = int .from_bytes (msg [0 :2 ], 'big' )
51225125 if mtype in (WIRE_ACCEPT_CHANNEL , WIRE_WARNING , WIRE_ERROR ):
5123- return mtype
5126+ return mtype , msg [ 2 :]
51245127 raise AssertionError ("no reply to open_channel" )
51255128
51265129
5130+ def read_channel_reply (lconn ):
5131+ mtype , _ = read_channel_reply_payload (lconn )
5132+ return mtype
5133+
5134+
5135+ def send_open_channel_reserve (lconn , chain_hash , temp_chan_id , funding_sat , push_msat ,
5136+ dust_limit , channel_reserve , feerate_per_kw , channel_type ):
5137+ """Like send_open_channel, but with a caller-chosen dust_limit / channel_reserve"""
5138+ keys = [wire .PrivateKey (bytes ([i + 1 ] * 32 )).public_key ().serializeCompressed ()
5139+ for i in range (6 )]
5140+
5141+ msg = struct .pack ('>H' , WIRE_OPEN_CHANNEL )
5142+ msg += chain_hash
5143+ msg += temp_chan_id
5144+ msg += struct .pack ('>Q' , funding_sat ) # funding_satoshis
5145+ msg += struct .pack ('>Q' , push_msat ) # push_msat
5146+ msg += struct .pack ('>Q' , dust_limit ) # dust_limit_satoshis
5147+ msg += struct .pack ('>Q' , 0xFFFFFFFFFFFF ) # max_htlc_value_in_flight_msat
5148+ msg += struct .pack ('>Q' , channel_reserve ) # channel_reserve_satoshis
5149+ msg += struct .pack ('>Q' , 0 ) # htlc_minimum_msat
5150+ msg += struct .pack ('>I' , feerate_per_kw ) # feerate_per_kw
5151+ msg += struct .pack ('>H' , 144 ) # to_self_delay
5152+ msg += struct .pack ('>H' , 483 ) # max_accepted_htlcs
5153+ for k in keys :
5154+ msg += k
5155+ msg += struct .pack ('>B' , 0 ) # channel_flags
5156+ msg += bytes ([1 , len (channel_type )]) + channel_type # TLV 1: channel_type
5157+
5158+ lconn .send_message (msg )
5159+
5160+
51275161def send_funding_created (lconn , temp_chan_id ):
51285162 """Drive the open to the point where we build the commitment transaction.
51295163
@@ -5266,3 +5300,37 @@ def serve():
52665300 + bytes ([len (hostname )])
52675301 + hostname .encode ('ascii' )
52685302 + (1234 ).to_bytes (2 , 'big' ))
5303+
5304+
5305+ @pytest .mark .openchannel ('v1' )
5306+ def test_accept_channel_reserve_below_our_dust_limit (node_factory , bitcoind ):
5307+ """Our accept_channel must not set channel_reserve below our dust limit.
5308+
5309+ As fundee, openingd used to floor the 1% channel reserve at the peer's
5310+ dust_limit_satoshis only, never at our own. A peer opening a small
5311+ channel with a low dust limit could get an accept_channel whose
5312+ dust_limit_satoshis (546, hardcoded) exceeded its own
5313+ channel_reserve_satoshis, violating BOLT #2.
5314+ """
5315+ l1 = node_factory .get_node ()
5316+
5317+ chain_hash = bytes .fromhex (bitcoind .rpc .getblockhash (0 ))[::- 1 ]
5318+ feerate = l1 .rpc .feerates ('perkw' )['perkw' ]['opening' ]
5319+
5320+ lconn , channel_type = raw_peer_connect (l1 )
5321+ # 1% of 30000 is 300sat, floored to our peer's 354sat dust limit -- still
5322+ # under our own hardcoded 546sat dust limit. channel_reserve_satoshis=546
5323+ # keeps us clear of the separate issue #9439, and funding_sat is well
5324+ # clear of lightningd's default 10,000sat minimum effective capacity.
5325+ send_open_channel_reserve (lconn , chain_hash , os .urandom (32 ),
5326+ funding_sat = 30000 , push_msat = 0 ,
5327+ dust_limit = 354 , channel_reserve = 546 ,
5328+ feerate_per_kw = feerate , channel_type = channel_type )
5329+ mtype , payload = read_channel_reply_payload (lconn )
5330+ assert mtype == WIRE_ACCEPT_CHANNEL
5331+
5332+ dust_limit = struct .unpack ('>Q' , payload [32 :40 ])[0 ]
5333+ reserve = struct .unpack ('>Q' , payload [48 :56 ])[0 ]
5334+ assert dust_limit <= reserve , \
5335+ "accept_channel dust_limit_satoshis {} exceeds its channel_reserve_satoshis {}" .format (
5336+ dust_limit , reserve )
0 commit comments