Skip to content

Commit c9b827c

Browse files
committed
Correct non-dust HTLC accounting in next_remote_commit_tx_fee_msat
`next_remote_commit_tx_fee_msat` previously mistakenly classified HTLCs with values equal to the dust limit as dust. This did not cause any force closes because the code that builds commitment transactions for signing correctly trims dust HTLCs. Nonetheless, this can cause `next_remote_commit_tx_fee_msat` to predict a weight for the next remote commitment transaction that is significantly lower than the eventual weight. This allows a malicious channel funder to create an unbroadcastable commitment for the channel fundee by adding HTLCs with values equal to the dust limit to the commitment transaction; according to the fundee, the funder has not exhausted their reserve because all the added HTLCs are dust, while in reality all the HTLCs are non-dust, and the funder does not have the funds to pay the minimum feerate to enter the mempool.
1 parent ee4211e commit c9b827c

File tree

2 files changed

+301
-2
lines changed

2 files changed

+301
-2
lines changed

lightning/src/ln/channel.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5201,14 +5201,14 @@ where
52015201
// committed outbound HTLCs, see below.
52025202
let mut included_htlcs = 0;
52035203
for ref htlc in context.pending_inbound_htlcs.iter() {
5204-
if htlc.amount_msat / 1000 <= real_dust_limit_timeout_sat {
5204+
if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat {
52055205
continue
52065206
}
52075207
included_htlcs += 1;
52085208
}
52095209

52105210
for ref htlc in context.pending_outbound_htlcs.iter() {
5211-
if htlc.amount_msat / 1000 <= real_dust_limit_success_sat {
5211+
if htlc.amount_msat / 1000 < real_dust_limit_success_sat {
52125212
continue
52135213
}
52145214
// We only include outbound HTLCs if it will not be included in their next commitment_signed,

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,3 +2078,302 @@ pub fn test_update_fulfill_htlc_bolt2_missing_badonion_bit_for_malformed_htlc_me
20782078
let reason = ClosureReason::ProcessingError { err: err_msg.data };
20792079
check_closed_event!(nodes[0], 1, reason, [node_b_id], 1000000);
20802080
}
2081+
2082+
#[xtest(feature = "_externalize_tests")]
2083+
pub fn test_dust_limit_fee_accounting() {
2084+
do_test_dust_limit_fee_accounting(false);
2085+
do_test_dust_limit_fee_accounting(true);
2086+
}
2087+
2088+
pub fn do_test_dust_limit_fee_accounting(can_afford: bool) {
2089+
// Test that when a channel funder sends HTLCs exactly on the dust limit
2090+
// of the funder, the fundee correctly accounts for the additional fee on the
2091+
// funder's commitment transaction due to those additional non-dust HTLCs when
2092+
// checking for any infrigements on the funder's reserve.
2093+
2094+
let channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
2095+
2096+
let chanmon_cfgs = create_chanmon_cfgs(2);
2097+
2098+
let mut default_config = test_default_channel_config();
2099+
default_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
2100+
default_config.manually_accept_inbound_channels = true;
2101+
2102+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
2103+
let node_chanmgrs =
2104+
create_node_chanmgrs(2, &node_cfgs, &[Some(default_config.clone()), Some(default_config)]);
2105+
2106+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
2107+
2108+
let node_a_id = nodes[0].node.get_our_node_id();
2109+
let node_b_id = nodes[1].node.get_our_node_id();
2110+
2111+
// Set a HTLC amount that is equal to the dust limit of the funder
2112+
const HTLC_AMT_SAT: u64 = 354;
2113+
2114+
const CHANNEL_VALUE_SAT: u64 = 100_000;
2115+
2116+
const FEERATE_PER_KW: u32 = 253;
2117+
2118+
let commit_tx_fee_sat =
2119+
chan_utils::commit_tx_fee_sat(FEERATE_PER_KW, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
2120+
2121+
// By default the reserve is set to 1% or 1000sat, whichever is higher
2122+
let channel_reserve_satoshis = 1_000;
2123+
2124+
// Set node 0's balance to pay for exactly MIN_AFFORDABLE_HTLC_COUNT non-dust HTLCs on the channel, minus some offset
2125+
let node_0_balance_sat = commit_tx_fee_sat
2126+
+ channel_reserve_satoshis
2127+
+ 2 * crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI
2128+
+ MIN_AFFORDABLE_HTLC_COUNT as u64 * HTLC_AMT_SAT
2129+
- if can_afford { 0 } else { 1 };
2130+
let mut node_1_balance_sat = CHANNEL_VALUE_SAT - node_0_balance_sat;
2131+
2132+
let chan_id = create_chan_between_nodes_with_value(
2133+
&nodes[0],
2134+
&nodes[1],
2135+
CHANNEL_VALUE_SAT,
2136+
node_1_balance_sat * 1000,
2137+
)
2138+
.3;
2139+
2140+
{
2141+
// Double check the reserve that node 0 has to maintain here
2142+
let per_peer_state_lock;
2143+
let mut peer_state_lock;
2144+
let chan =
2145+
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id);
2146+
assert_eq!(
2147+
chan.funding().holder_selected_channel_reserve_satoshis,
2148+
channel_reserve_satoshis
2149+
);
2150+
}
2151+
{
2152+
// Double check the dust limit on node 0's commitment transactions; when node 0
2153+
// adds a HTLC, node 1 will check that the fee on node 0's commitment transaction
2154+
// does not dip under the node 1 selected reserve.
2155+
let per_peer_state_lock;
2156+
let mut peer_state_lock;
2157+
let chan =
2158+
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, chan_id);
2159+
assert_eq!(chan.context().holder_dust_limit_satoshis, HTLC_AMT_SAT);
2160+
}
2161+
2162+
// Precompute the route to skip any router complaints when sending the last HTLC
2163+
let (route_0_1, payment_hash_0_1, _, payment_secret_0_1) =
2164+
get_route_and_payment_hash!(nodes[0], nodes[1], HTLC_AMT_SAT * 1000);
2165+
2166+
let mut htlcs = Vec::new();
2167+
for _ in 0..MIN_AFFORDABLE_HTLC_COUNT - 1 {
2168+
let (_payment_preimage, payment_hash, ..) =
2169+
route_payment(&nodes[0], &[&nodes[1]], HTLC_AMT_SAT * 1000);
2170+
// Grab a snapshot of these HTLCs to manually build the commitment transaction later...
2171+
let accepted_htlc = chan_utils::HTLCOutputInCommitment {
2172+
offered: false,
2173+
amount_msat: HTLC_AMT_SAT * 1000,
2174+
// Hard-coded to match the expected value
2175+
cltv_expiry: 81,
2176+
payment_hash,
2177+
transaction_output_index: None,
2178+
};
2179+
htlcs.push(accepted_htlc);
2180+
}
2181+
2182+
// Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc()
2183+
let secp_ctx = Secp256k1::new();
2184+
let session_priv = SecretKey::from_slice(&[42; 32]).expect("RNG is bad!");
2185+
2186+
let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
2187+
2188+
let onion_keys =
2189+
onion_utils::construct_onion_keys(&secp_ctx, &route_0_1.paths[0], &session_priv);
2190+
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret_0_1);
2191+
let (onion_payloads, amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(
2192+
&route_0_1.paths[0],
2193+
HTLC_AMT_SAT * 1000,
2194+
&recipient_onion_fields,
2195+
cur_height,
2196+
&None,
2197+
None,
2198+
None,
2199+
)
2200+
.unwrap();
2201+
let onion_routing_packet =
2202+
onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash_0_1)
2203+
.unwrap();
2204+
// Double check the hard-coded value
2205+
assert_eq!(cltv_expiry, 81);
2206+
let msg = msgs::UpdateAddHTLC {
2207+
channel_id: chan_id,
2208+
htlc_id: MIN_AFFORDABLE_HTLC_COUNT as u64 - 1,
2209+
amount_msat,
2210+
payment_hash: payment_hash_0_1,
2211+
cltv_expiry,
2212+
onion_routing_packet,
2213+
skimmed_fee_msat: None,
2214+
blinding_point: None,
2215+
};
2216+
2217+
nodes[1].node.handle_update_add_htlc(node_a_id, &msg);
2218+
2219+
if !can_afford {
2220+
let err = "Remote HTLC add would put them under remote reserve value".to_string();
2221+
nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", &err, 3);
2222+
let events = nodes[1].node.get_and_clear_pending_msg_events();
2223+
assert_eq!(events.len(), 2);
2224+
let reason = ClosureReason::ProcessingError { err };
2225+
check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], CHANNEL_VALUE_SAT);
2226+
check_added_monitors(&nodes[1], 1);
2227+
} else {
2228+
// Now manually create the commitment_signed message corresponding to the update_add
2229+
// nodes[0] just sent. In the code for construction of this message, "local" refers
2230+
// to the sender of the message, and "remote" refers to the receiver.
2231+
2232+
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
2233+
2234+
let (local_secret, next_local_point) = {
2235+
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
2236+
let chan_lock = per_peer_state.get(&node_b_id).unwrap().lock().unwrap();
2237+
let local_chan =
2238+
chan_lock.channel_by_id.get(&chan_id).and_then(Channel::as_funded).unwrap();
2239+
let chan_signer = local_chan.get_signer();
2240+
// Make the signer believe we validated another commitment, so we can release the secret
2241+
chan_signer.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
2242+
2243+
(
2244+
chan_signer
2245+
.as_ref()
2246+
.release_commitment_secret(
2247+
INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64 + 1,
2248+
)
2249+
.unwrap(),
2250+
chan_signer
2251+
.as_ref()
2252+
.get_per_commitment_point(
2253+
INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64,
2254+
&secp_ctx,
2255+
)
2256+
.unwrap(),
2257+
)
2258+
};
2259+
let remote_point = {
2260+
let per_peer_lock;
2261+
let mut peer_state_lock;
2262+
2263+
let channel =
2264+
get_channel_ref!(nodes[1], nodes[0], per_peer_lock, peer_state_lock, chan_id);
2265+
let chan_signer = channel.as_funded().unwrap().get_signer();
2266+
chan_signer
2267+
.as_ref()
2268+
.get_per_commitment_point(
2269+
INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64,
2270+
&secp_ctx,
2271+
)
2272+
.unwrap()
2273+
};
2274+
2275+
// Build the remote commitment transaction so we can sign it, and then later use the
2276+
// signature for the commitment_signed message.
2277+
let local_chan_balance = node_0_balance_sat
2278+
- HTLC_AMT_SAT * MIN_AFFORDABLE_HTLC_COUNT as u64
2279+
- 2 * crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI
2280+
- chan_utils::commit_tx_fee_sat(
2281+
FEERATE_PER_KW,
2282+
MIN_AFFORDABLE_HTLC_COUNT,
2283+
&channel_type,
2284+
);
2285+
2286+
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
2287+
offered: false,
2288+
amount_msat: HTLC_AMT_SAT * 1000,
2289+
cltv_expiry,
2290+
payment_hash: payment_hash_0_1,
2291+
transaction_output_index: None,
2292+
};
2293+
htlcs.push(accepted_htlc_info);
2294+
2295+
let commitment_number = INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64;
2296+
2297+
let res = {
2298+
let per_peer_lock;
2299+
let mut peer_state_lock;
2300+
2301+
let channel =
2302+
get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id);
2303+
let chan_signer = channel.as_funded().unwrap().get_signer();
2304+
2305+
let commitment_tx = CommitmentTransaction::new(
2306+
commitment_number,
2307+
&remote_point,
2308+
node_1_balance_sat,
2309+
local_chan_balance,
2310+
FEERATE_PER_KW,
2311+
htlcs,
2312+
&channel.funding().channel_transaction_parameters.as_counterparty_broadcastable(),
2313+
&secp_ctx,
2314+
);
2315+
let params = &channel.funding().channel_transaction_parameters;
2316+
chan_signer
2317+
.as_ecdsa()
2318+
.unwrap()
2319+
.sign_counterparty_commitment(
2320+
params,
2321+
&commitment_tx,
2322+
Vec::new(),
2323+
Vec::new(),
2324+
&secp_ctx,
2325+
)
2326+
.unwrap()
2327+
};
2328+
2329+
let commit_signed_msg = msgs::CommitmentSigned {
2330+
channel_id: chan_id,
2331+
signature: res.0,
2332+
htlc_signatures: res.1,
2333+
funding_txid: None,
2334+
#[cfg(taproot)]
2335+
partial_signature_with_nonce: None,
2336+
};
2337+
2338+
// Send the commitment_signed message to the nodes[1].
2339+
nodes[1].node.handle_commitment_signed(node_a_id, &commit_signed_msg);
2340+
let _ = nodes[1].node.get_and_clear_pending_msg_events();
2341+
2342+
// Send the RAA to nodes[1].
2343+
let raa_msg = msgs::RevokeAndACK {
2344+
channel_id: chan_id,
2345+
per_commitment_secret: local_secret,
2346+
next_per_commitment_point: next_local_point,
2347+
#[cfg(taproot)]
2348+
next_local_nonce: None,
2349+
};
2350+
nodes[1].node.handle_revoke_and_ack(node_a_id, &raa_msg);
2351+
2352+
// The HTLC actually fails here in `fn validate_commitment_signed` due to a fee spike buffer
2353+
// violation. It nonetheless passed all checks in `fn validate_update_add_htlc`.
2354+
2355+
expect_pending_htlcs_forwardable!(nodes[1]);
2356+
expect_htlc_handling_failed_destinations!(
2357+
nodes[1].node.get_and_clear_pending_events(),
2358+
&[HTLCHandlingFailureType::Receive { payment_hash: payment_hash_0_1 }]
2359+
);
2360+
2361+
let events = nodes[1].node.get_and_clear_pending_msg_events();
2362+
assert_eq!(events.len(), 1);
2363+
// Make sure the HTLC failed in the way we expect.
2364+
match events[0] {
2365+
MessageSendEvent::UpdateHTLCs {
2366+
updates: msgs::CommitmentUpdate { ref update_fail_htlcs, .. },
2367+
..
2368+
} => {
2369+
assert_eq!(update_fail_htlcs.len(), 1);
2370+
update_fail_htlcs[0].clone()
2371+
},
2372+
_ => panic!("Unexpected event"),
2373+
};
2374+
nodes[1].logger.assert_log("lightning::ln::channel",
2375+
format!("Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", raa_msg.channel_id), 1);
2376+
2377+
check_added_monitors(&nodes[1], 3);
2378+
}
2379+
}

0 commit comments

Comments
 (0)