Skip to content

Commit 9fa6f1b

Browse files
committed
openingd: floor accept_channel reserve at our own dust limit too
Changelog-Fixed: 'openingd' no longer sends an 'accept_channel' whose 'channel_reserve_satoshis' is below its own 'dust_limit_satoshis'
1 parent 8c8fdf4 commit 9fa6f1b

2 files changed

Lines changed: 86 additions & 7 deletions

File tree

‎openingd/openingd.c‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ static void NORETURN negotiation_failed(struct state *state,
145145

146146
static void set_reserve_absolute(struct state * state, const struct amount_sat dust_limit, struct amount_sat reserve_sat)
147147
{
148+
struct amount_sat floor;
149+
148150
status_debug("Setting their reserve to %s",
149151
fmt_amount_sat(tmpctx, reserve_sat));
150152
if (state->allowdustreserve) {
@@ -156,13 +158,22 @@ static void set_reserve_absolute(struct state * state, const struct amount_sat d
156158
* ...
157159
* - MUST set `channel_reserve_satoshis` greater than or equal
158160
* to `dust_limit_satoshis` from the `open_channel` message.
161+
*
162+
* Our own dust_limit_satoshis must also never exceed our
163+
* channel_reserve_satoshis, so floor against whichever dust
164+
* limit (ours or the caller-supplied one) is larger
159165
*/
160-
if (amount_sat_greater(dust_limit, reserve_sat)) {
161-
status_debug("Their reserve is too small, bumping to "
166+
if (amount_sat_greater(state->localconf.dust_limit, dust_limit))
167+
floor = state->localconf.dust_limit;
168+
else
169+
floor = dust_limit;
170+
171+
if (amount_sat_greater(floor, reserve_sat)) {
172+
status_debug("Reserve is too small, bumping to "
162173
"dust_limit: %s < %s",
163174
fmt_amount_sat(tmpctx, reserve_sat),
164-
fmt_amount_sat(tmpctx, dust_limit));
165-
state->localconf.channel_reserve = dust_limit;
175+
fmt_amount_sat(tmpctx, floor));
176+
state->localconf.channel_reserve = floor;
166177
} else {
167178
state->localconf.channel_reserve = reserve_sat;
168179
}

‎tests/test_connection.py‎

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
51275161
def 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

Comments
 (0)