Skip to content

Commit c80b255

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 2646184 commit c80b255

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
@@ -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+
53275361
def 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

Comments
 (0)