Skip to content

Commit 382e71b

Browse files
tankyleoTheBlueMatt
authored andcommitted
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. Conflicts resolved in: * lightning/src/ln/htlc_reserve_unit_tests.rs which is a new file upstream. The new test was instead moved to lightning/src/ln/functional_tests.rs and rewritten where the upstream API has changed (in some cases nontrivially).
1 parent a9597aa commit 382e71b

File tree

2 files changed

+293
-3
lines changed

2 files changed

+293
-3
lines changed

lightning/src/ln/channel.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,7 +3216,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
32163216
/// Creates a set of keys for build_commitment_transaction to generate a transaction which we
32173217
/// will sign and send to our counterparty.
32183218
/// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
3219-
fn build_remote_transaction_keys(&self) -> TxCreationKeys {
3219+
pub fn build_remote_transaction_keys(&self) -> TxCreationKeys {
32203220
let revocation_basepoint = &self.get_holder_pubkeys().revocation_basepoint;
32213221
let htlc_basepoint = &self.get_holder_pubkeys().htlc_basepoint;
32223222
let counterparty_pubkeys = self.get_counterparty_pubkeys();
@@ -3774,14 +3774,14 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
37743774
// committed outbound HTLCs, see below.
37753775
let mut included_htlcs = 0;
37763776
for ref htlc in context.pending_inbound_htlcs.iter() {
3777-
if htlc.amount_msat / 1000 <= real_dust_limit_timeout_sat {
3777+
if htlc.amount_msat / 1000 < real_dust_limit_timeout_sat {
37783778
continue
37793779
}
37803780
included_htlcs += 1;
37813781
}
37823782

37833783
for ref htlc in context.pending_outbound_htlcs.iter() {
3784-
if htlc.amount_msat / 1000 <= real_dust_limit_success_sat {
3784+
if htlc.amount_msat / 1000 < real_dust_limit_success_sat {
37853785
continue
37863786
}
37873787
// We only include outbound HTLCs if it will not be included in their next commitment_signed,

lightning/src/ln/functional_tests.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11684,3 +11684,293 @@ fn test_funding_signed_event() {
1168411684
nodes[1].node.get_and_clear_pending_msg_events();
1168511685
}
1168611686

11687+
#[test]
11688+
pub fn test_dust_limit_fee_accounting() {
11689+
do_test_dust_limit_fee_accounting(false);
11690+
do_test_dust_limit_fee_accounting(true);
11691+
}
11692+
11693+
pub fn do_test_dust_limit_fee_accounting(can_afford: bool) {
11694+
// Test that when a channel funder sends HTLCs exactly on the dust limit
11695+
// of the funder, the fundee correctly accounts for the additional fee on the
11696+
// funder's commitment transaction due to those additional non-dust HTLCs when
11697+
// checking for any infrigements on the funder's reserve.
11698+
11699+
let channel_type = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies();
11700+
11701+
let chanmon_cfgs = create_chanmon_cfgs(2);
11702+
11703+
let mut default_config = test_default_channel_config();
11704+
default_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
11705+
default_config.manually_accept_inbound_channels = true;
11706+
11707+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
11708+
let node_chanmgrs =
11709+
create_node_chanmgrs(2, &node_cfgs, &[Some(default_config.clone()), Some(default_config)]);
11710+
11711+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
11712+
11713+
let node_a_id = nodes[0].node.get_our_node_id();
11714+
11715+
// Set a HTLC amount that is equal to the dust limit of the funder
11716+
const HTLC_AMT_SAT: u64 = 354;
11717+
11718+
const CHANNEL_VALUE_SAT: u64 = 100_000;
11719+
11720+
const FEERATE_PER_KW: u32 = 253;
11721+
11722+
let commit_tx_fee_sat =
11723+
chan_utils::commit_tx_fee_sat(FEERATE_PER_KW, MIN_AFFORDABLE_HTLC_COUNT, &channel_type);
11724+
11725+
// By default the reserve is set to 1% or 1000sat, whichever is higher
11726+
let channel_reserve_satoshis = 1_000;
11727+
11728+
// Set node 0's balance to pay for exactly MIN_AFFORDABLE_HTLC_COUNT non-dust HTLCs on the channel, minus some offset
11729+
let node_0_balance_sat = commit_tx_fee_sat
11730+
+ channel_reserve_satoshis
11731+
+ 2 * crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI
11732+
+ MIN_AFFORDABLE_HTLC_COUNT as u64 * HTLC_AMT_SAT
11733+
- if can_afford { 0 } else { 1 };
11734+
let mut node_1_balance_sat = CHANNEL_VALUE_SAT - node_0_balance_sat;
11735+
11736+
let chan_id = create_chan_between_nodes_with_value(
11737+
&nodes[0],
11738+
&nodes[1],
11739+
CHANNEL_VALUE_SAT,
11740+
node_1_balance_sat * 1000,
11741+
)
11742+
.3;
11743+
11744+
{
11745+
// Double check the reserve that node 0 has to maintain here
11746+
let per_peer_state_lock;
11747+
let mut peer_state_lock;
11748+
let chan =
11749+
get_channel_ref!(nodes[1], nodes[0], per_peer_state_lock, peer_state_lock, chan_id);
11750+
assert_eq!(
11751+
chan.context().holder_selected_channel_reserve_satoshis,
11752+
channel_reserve_satoshis
11753+
);
11754+
}
11755+
{
11756+
// Double check the dust limit on node 0's commitment transactions; when node 0
11757+
// adds a HTLC, node 1 will check that the fee on node 0's commitment transaction
11758+
// does not dip under the node 1 selected reserve.
11759+
let per_peer_state_lock;
11760+
let mut peer_state_lock;
11761+
let chan =
11762+
get_channel_ref!(nodes[0], nodes[1], per_peer_state_lock, peer_state_lock, chan_id);
11763+
assert_eq!(chan.context().holder_dust_limit_satoshis, HTLC_AMT_SAT);
11764+
}
11765+
11766+
// Precompute the route to skip any router complaints when sending the last HTLC
11767+
let (route_0_1, payment_hash_0_1, _, payment_secret_0_1) =
11768+
get_route_and_payment_hash!(nodes[0], nodes[1], HTLC_AMT_SAT * 1000);
11769+
11770+
let mut htlcs = Vec::new();
11771+
for _ in 0..MIN_AFFORDABLE_HTLC_COUNT - 1 {
11772+
let (_payment_preimage, payment_hash, ..) =
11773+
route_payment(&nodes[0], &[&nodes[1]], HTLC_AMT_SAT * 1000);
11774+
// Grab a snapshot of these HTLCs to manually build the commitment transaction later...
11775+
let accepted_htlc = chan_utils::HTLCOutputInCommitment {
11776+
offered: false,
11777+
amount_msat: HTLC_AMT_SAT * 1000,
11778+
// Hard-coded to match the expected value
11779+
cltv_expiry: 81,
11780+
payment_hash,
11781+
transaction_output_index: None,
11782+
};
11783+
htlcs.push((accepted_htlc, ()));
11784+
}
11785+
11786+
// Need to manually create the update_add_htlc message to go around the channel reserve check in send_htlc()
11787+
let secp_ctx = Secp256k1::new();
11788+
let session_priv = SecretKey::from_slice(&[42; 32]).expect("RNG is bad!");
11789+
11790+
let cur_height = nodes[1].node.best_block.read().unwrap().height + 1;
11791+
11792+
let onion_keys =
11793+
onion_utils::construct_onion_keys(&secp_ctx, &route_0_1.paths[0], &session_priv).unwrap();
11794+
let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret_0_1);
11795+
let (onion_payloads, amount_msat, cltv_expiry) = onion_utils::build_onion_payloads(
11796+
&route_0_1.paths[0],
11797+
HTLC_AMT_SAT * 1000,
11798+
&recipient_onion_fields,
11799+
cur_height,
11800+
&None,
11801+
None,
11802+
)
11803+
.unwrap();
11804+
let onion_routing_packet =
11805+
onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash_0_1)
11806+
.unwrap();
11807+
// Double check the hard-coded value
11808+
assert_eq!(cltv_expiry, 81);
11809+
let msg = msgs::UpdateAddHTLC {
11810+
channel_id: chan_id,
11811+
htlc_id: MIN_AFFORDABLE_HTLC_COUNT as u64 - 1,
11812+
amount_msat,
11813+
payment_hash: payment_hash_0_1,
11814+
cltv_expiry,
11815+
onion_routing_packet,
11816+
skimmed_fee_msat: None,
11817+
blinding_point: None,
11818+
};
11819+
11820+
nodes[1].node.handle_update_add_htlc(node_a_id, &msg);
11821+
11822+
if !can_afford {
11823+
let err = "Remote HTLC add would put them under remote reserve value".to_string();
11824+
nodes[1].logger.assert_log_contains("lightning::ln::channelmanager", &err, 3);
11825+
let events = nodes[1].node.get_and_clear_pending_msg_events();
11826+
assert_eq!(events.len(), 2);
11827+
let reason = ClosureReason::ProcessingError { err };
11828+
check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], CHANNEL_VALUE_SAT);
11829+
check_added_monitors(&nodes[1], 1);
11830+
} else {
11831+
// Now manually create the commitment_signed message corresponding to the update_add
11832+
// nodes[0] just sent. In the code for construction of this message, "local" refers
11833+
// to the sender of the message, and "remote" refers to the receiver.
11834+
11835+
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
11836+
11837+
let (local_secret, next_local_point) = {
11838+
let per_peer_lock;
11839+
let mut peer_state_lock;
11840+
11841+
let channel =
11842+
get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id);
11843+
let local_chan = if let ChannelPhase::Funded(chan) = &*channel {
11844+
chan
11845+
} else {
11846+
panic!();
11847+
};
11848+
let chan_signer = local_chan.get_signer();
11849+
// Make the signer believe we validated another commitment, so we can release the secret
11850+
chan_signer.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1;
11851+
11852+
(
11853+
chan_signer
11854+
.as_ref()
11855+
.release_commitment_secret(
11856+
INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64 + 1,
11857+
)
11858+
.unwrap(),
11859+
chan_signer
11860+
.as_ref()
11861+
.get_per_commitment_point(
11862+
INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64,
11863+
&secp_ctx,
11864+
)
11865+
.unwrap(),
11866+
)
11867+
};
11868+
11869+
// Build the remote commitment transaction so we can sign it, and then later use the
11870+
// signature for the commitment_signed message.
11871+
let local_chan_balance = node_0_balance_sat
11872+
- HTLC_AMT_SAT * MIN_AFFORDABLE_HTLC_COUNT as u64
11873+
- 2 * crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI
11874+
- chan_utils::commit_tx_fee_sat(
11875+
FEERATE_PER_KW,
11876+
MIN_AFFORDABLE_HTLC_COUNT,
11877+
&channel_type,
11878+
);
11879+
11880+
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
11881+
offered: false,
11882+
amount_msat: HTLC_AMT_SAT * 1000,
11883+
cltv_expiry,
11884+
payment_hash: payment_hash_0_1,
11885+
transaction_output_index: None,
11886+
};
11887+
htlcs.push((accepted_htlc_info, ()));
11888+
11889+
let commitment_number = INITIAL_COMMITMENT_NUMBER - MIN_AFFORDABLE_HTLC_COUNT as u64;
11890+
11891+
let res = {
11892+
let per_peer_lock;
11893+
let mut peer_state_lock;
11894+
11895+
let channel =
11896+
get_channel_ref!(nodes[0], nodes[1], per_peer_lock, peer_state_lock, chan_id);
11897+
let chan_signer = if let ChannelPhase::Funded(chan) = &*channel {
11898+
chan.get_signer()
11899+
} else {
11900+
panic!();
11901+
};
11902+
11903+
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
11904+
commitment_number,
11905+
node_1_balance_sat,
11906+
local_chan_balance,
11907+
channel.context().channel_transaction_parameters.counterparty_parameters.as_ref().unwrap().pubkeys.funding_pubkey,
11908+
channel.context().channel_transaction_parameters.holder_pubkeys.funding_pubkey,
11909+
channel.context().build_remote_transaction_keys(),
11910+
FEERATE_PER_KW,
11911+
&mut htlcs,
11912+
&channel.context().channel_transaction_parameters.as_counterparty_broadcastable(),
11913+
);
11914+
chan_signer
11915+
.as_ecdsa()
11916+
.unwrap()
11917+
.sign_counterparty_commitment(
11918+
&commitment_tx,
11919+
Vec::new(),
11920+
Vec::new(),
11921+
&secp_ctx,
11922+
)
11923+
.unwrap()
11924+
};
11925+
11926+
let commit_signed_msg = msgs::CommitmentSigned {
11927+
channel_id: chan_id,
11928+
signature: res.0,
11929+
htlc_signatures: res.1,
11930+
batch: None,
11931+
#[cfg(taproot)]
11932+
partial_signature_with_nonce: None,
11933+
};
11934+
11935+
// Send the commitment_signed message to the nodes[1].
11936+
nodes[1].node.handle_commitment_signed(node_a_id, &commit_signed_msg);
11937+
let _ = nodes[1].node.get_and_clear_pending_msg_events();
11938+
11939+
// Send the RAA to nodes[1].
11940+
let raa_msg = msgs::RevokeAndACK {
11941+
channel_id: chan_id,
11942+
per_commitment_secret: local_secret,
11943+
next_per_commitment_point: next_local_point,
11944+
#[cfg(taproot)]
11945+
next_local_nonce: None,
11946+
};
11947+
nodes[1].node.handle_revoke_and_ack(node_a_id, &raa_msg);
11948+
11949+
// The HTLC actually fails here in `fn validate_commitment_signed` due to a fee spike buffer
11950+
// violation. It nonetheless passed all checks in `fn validate_update_add_htlc`.
11951+
11952+
//expect_pending_htlcs_forwardable!(nodes[1]);
11953+
expect_htlc_handling_failed_destinations!(
11954+
nodes[1].node.get_and_clear_pending_events(),
11955+
&[HTLCDestination::FailedPayment { payment_hash: payment_hash_0_1 }]
11956+
);
11957+
11958+
let events = nodes[1].node.get_and_clear_pending_msg_events();
11959+
assert_eq!(events.len(), 1);
11960+
// Make sure the HTLC failed in the way we expect.
11961+
match events[0] {
11962+
MessageSendEvent::UpdateHTLCs {
11963+
updates: msgs::CommitmentUpdate { ref update_fail_htlcs, .. },
11964+
..
11965+
} => {
11966+
assert_eq!(update_fail_htlcs.len(), 1);
11967+
update_fail_htlcs[0].clone()
11968+
},
11969+
_ => panic!("Unexpected event"),
11970+
};
11971+
nodes[1].logger.assert_log("lightning::ln::channel",
11972+
format!("Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", raa_msg.channel_id), 1);
11973+
11974+
check_added_monitors(&nodes[1], 2);
11975+
}
11976+
}

0 commit comments

Comments
 (0)