Skip to content

Commit 212b848

Browse files
authored
Merge pull request #3933 from tankyleo/2025-07-nondust-htlc-accounting
Correct non-dust HTLC accounting in `next_remote_commit_tx_fee_msat`
2 parents 1d507f2 + c9b827c commit 212b848

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
@@ -5191,14 +5191,14 @@ where
51915191
// committed outbound HTLCs, see below.
51925192
let mut included_htlcs = 0;
51935193
for ref htlc in context.pending_inbound_htlcs.iter() {
5194-
if htlc.amount_msat / 1000 <= real_dust_limit_timeout_sat {
5194+
if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat {
51955195
continue
51965196
}
51975197
included_htlcs += 1;
51985198
}
51995199

52005200
for ref htlc in context.pending_outbound_htlcs.iter() {
5201-
if htlc.amount_msat / 1000 <= real_dust_limit_success_sat {
5201+
if htlc.amount_msat / 1000 < real_dust_limit_success_sat {
52025202
continue
52035203
}
52045204
// 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)