Skip to content

Commit deb51ae

Browse files
committed
Don't trim HTLCs when calculating the fee spike commit tx fee
We previously accounted for HTLC trims at the spiked feerate when calculating the commitment transaction fee including the fee spike multiple. This only ensured that the funder of the channel could afford the commitment transaction fee for an exact 2x increase in the feerate. Now, we check that the funder can cover any increase in the feerate between 1x to 2x.
1 parent e90d524 commit deb51ae

2 files changed

Lines changed: 244 additions & 37 deletions

File tree

lightning/src/ln/htlc_reserve_unit_tests.rs

Lines changed: 233 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use crate::events::{ClosureReason, Event, HTLCHandlingFailureType, PaymentPurpose};
44
use crate::ln::chan_utils::{
55
self, commit_tx_fee_sat, commitment_tx_base_weight, second_stage_tx_fees_sat,
6-
shared_anchor_script_pubkey, CommitmentTransaction, COMMITMENT_TX_WEIGHT_PER_HTLC,
7-
TRUC_CHILD_MAX_WEIGHT,
6+
shared_anchor_script_pubkey, CommitmentTransaction, HTLCOutputInCommitment,
7+
COMMITMENT_TX_WEIGHT_PER_HTLC, TRUC_CHILD_MAX_WEIGHT,
88
};
99
use crate::ln::channel::{
1010
get_holder_selected_channel_reserve_satoshis, Channel, ANCHOR_OUTPUT_VALUE_SATOSHI,
@@ -888,7 +888,7 @@ pub fn do_test_fee_spike_buffer(cfg: Option<UserConfig>, htlc_fails: bool) {
888888

889889
// Build the remote commitment transaction so we can sign it, and then later use the
890890
// signature for the commitment_signed message.
891-
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
891+
let accepted_htlc_info = HTLCOutputInCommitment {
892892
offered: false,
893893
amount_msat: payment_amt_msat,
894894
cltv_expiry: htlc_cltv,
@@ -2143,7 +2143,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) {
21432143
let (_payment_preimage, payment_hash, ..) =
21442144
route_payment(&nodes[0], &[&nodes[1]], HTLC_AMT_SAT * 1000);
21452145
// Grab a snapshot of these HTLCs to manually build the commitment transaction later...
2146-
let accepted_htlc = chan_utils::HTLCOutputInCommitment {
2146+
let accepted_htlc = HTLCOutputInCommitment {
21472147
offered: false,
21482148
amount_msat: HTLC_AMT_SAT * 1000,
21492149
// Hard-coded to match the expected value
@@ -2257,7 +2257,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) {
22572257
&channel_type,
22582258
);
22592259

2260-
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
2260+
let accepted_htlc_info = HTLCOutputInCommitment {
22612261
offered: false,
22622262
amount_msat: HTLC_AMT_SAT * 1000,
22632263
cltv_expiry,
@@ -2838,21 +2838,30 @@ fn do_test_0reserve_no_outputs_legacy(no_outputs_case: LegacyChannelsNoOutputs)
28382838
return;
28392839
}
28402840

2841+
let htlcs_in_commitment = vec![HTLCOutputInCommitment {
2842+
offered: false,
2843+
amount_msat: receiver_amount_msat,
2844+
cltv_expiry: htlc_cltv,
2845+
payment_hash,
2846+
transaction_output_index: Some(1),
2847+
}];
2848+
28412849
manually_trigger_update_fail_htlc(
28422850
&nodes,
28432851
channel_id,
2844-
channel_value_sat,
2852+
channel_value_sat * 1000,
28452853
dust_limit_satoshis,
2846-
receiver_amount_msat,
2847-
htlc_cltv,
28482854
payment_hash,
2855+
htlcs_in_commitment,
2856+
false,
28492857
);
28502858
}
28512859
}
28522860

28532861
fn manually_trigger_update_fail_htlc<'a, 'b, 'c, 'd>(
2854-
nodes: &'a Vec<Node<'b, 'c, 'd>>, channel_id: ChannelId, channel_value_sat: u64,
2855-
dust_limit_satoshis: u64, receiver_amount_msat: u64, htlc_cltv: u32, payment_hash: PaymentHash,
2862+
nodes: &'a Vec<Node<'b, 'c, 'd>>, channel_id: ChannelId, value_to_self_msat: u64,
2863+
dust_limit_satoshis: u64, payment_hash: PaymentHash,
2864+
htlcs_in_commitment: Vec<HTLCOutputInCommitment>, can_afford_but_reserve_is_breached: bool,
28562865
) {
28572866
let node_a_id = nodes[0].node.get_our_node_id();
28582867
let node_b_id = nodes[1].node.get_our_node_id();
@@ -2864,45 +2873,36 @@ fn manually_trigger_update_fail_htlc<'a, 'b, 'c, 'd>(
28642873

28652874
let feerate_per_kw = get_feerate!(nodes[0], nodes[1], channel_id);
28662875

2867-
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
2868-
28692876
let (local_secret, next_local_point) = {
28702877
let per_peer_state = nodes[0].node.per_peer_state.read().unwrap();
28712878
let chan_lock = per_peer_state.get(&node_b_id).unwrap().lock().unwrap();
28722879
let local_chan =
28732880
chan_lock.channel_by_id.get(&channel_id).and_then(Channel::as_funded).unwrap();
28742881
let chan_signer = local_chan.get_signer();
28752882
// Make the signer believe we validated another commitment, so we can release the secret
2883+
let commit_number = chan_signer.get_enforcement_state().last_holder_commitment;
28762884
chan_signer.get_enforcement_state().last_holder_commitment -= 1;
28772885

28782886
(
2879-
chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER).unwrap(),
2880-
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx).unwrap(),
2887+
chan_signer.release_commitment_secret(commit_number).unwrap(),
2888+
chan_signer.get_per_commitment_point(commit_number - 2, &secp_ctx).unwrap(),
28812889
)
28822890
};
2883-
let remote_point = {
2891+
let (remote_commit_number, remote_point) = {
28842892
let per_peer_lock;
28852893
let mut peer_state_lock;
28862894

28872895
let channel =
28882896
get_channel_ref!(nodes[1], nodes[0], per_peer_lock, peer_state_lock, channel_id);
28892897
let chan_signer = channel.as_funded().unwrap().get_signer();
2890-
chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap()
2898+
let commit_number = chan_signer.get_enforcement_state().last_holder_commitment;
2899+
let remote_point =
2900+
chan_signer.get_per_commitment_point(commit_number - 1, &secp_ctx).unwrap();
2901+
(commit_number - 1, remote_point)
28912902
};
28922903

28932904
// Build the remote commitment transaction so we can sign it, and then later use the
28942905
// signature for the commitment_signed message.
2895-
let accepted_htlc_info = chan_utils::HTLCOutputInCommitment {
2896-
offered: false,
2897-
amount_msat: receiver_amount_msat,
2898-
cltv_expiry: htlc_cltv,
2899-
payment_hash,
2900-
transaction_output_index: Some(1),
2901-
};
2902-
2903-
let local_chan_balance_msat = channel_value_sat * 1000;
2904-
let commitment_number = INITIAL_COMMITMENT_NUMBER - 1;
2905-
29062906
let res = {
29072907
let per_peer_lock;
29082908
let mut peer_state_lock;
@@ -2913,12 +2913,12 @@ fn manually_trigger_update_fail_htlc<'a, 'b, 'c, 'd>(
29132913

29142914
let (commitment_tx, _stats) = SpecTxBuilder {}.build_commitment_transaction(
29152915
false,
2916-
commitment_number,
2916+
remote_commit_number,
29172917
&remote_point,
29182918
&channel.funding().channel_transaction_parameters,
29192919
&secp_ctx,
2920-
local_chan_balance_msat,
2921-
vec![accepted_htlc_info],
2920+
value_to_self_msat,
2921+
htlcs_in_commitment,
29222922
feerate_per_kw,
29232923
dust_limit_satoshis,
29242924
&nodes[0].logger,
@@ -2969,11 +2969,13 @@ fn manually_trigger_update_fail_htlc<'a, 'b, 'c, 'd>(
29692969
},
29702970
_ => panic!("Unexpected event"),
29712971
};
2972-
nodes[1].logger.assert_log(
2973-
"lightning::ln::channel",
2974-
"Attempting to fail HTLC due to balance exhausted on remote commitment".to_string(),
2975-
1,
2976-
);
2972+
let log_string =
2973+
if can_afford_but_reserve_is_breached {
2974+
String::from("Attempting to fail HTLC due to fee spike buffer violation. Rebalancing is required.")
2975+
} else {
2976+
String::from("Attempting to fail HTLC due to balance exhausted on remote commitment")
2977+
};
2978+
nodes[1].logger.assert_log("lightning::ln::channel", log_string, 1);
29772979

29782980
check_added_monitors(&nodes[1], 3);
29792981
}
@@ -3549,3 +3551,199 @@ fn test_outbound_vs_available_capacity_outbound_htlc_limit_spiked_feerate() {
35493551
);
35503552
}
35513553
}
3554+
3555+
/// Make sure that we do not account for HTLCs going from non-dust to dust at the spiked feerate
3556+
/// when checking the fee spike buffer in `can_accept_incoming_htlc`. This is required to make sure
3557+
/// that we can afford *any* increase in the feerate between 1x to 2x, instead of checking whether
3558+
/// we can afford only the 2x increase in the feerate.
3559+
#[xtest(feature = "_externalize_tests")]
3560+
fn test_fail_cannot_afford_dust_htlcs_at_spike_multiple_if_nondust_at_base_feerate() {
3561+
let mut config = test_default_channel_config();
3562+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
3563+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
3564+
3565+
let chanmon_cfgs = create_chanmon_cfgs(2);
3566+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
3567+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
3568+
100;
3569+
3570+
let channel_type = ChannelTypeFeatures::only_static_remote_key();
3571+
3572+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
3573+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3574+
3575+
let node_a_id = nodes[0].node.get_our_node_id();
3576+
let _node_b_id = nodes[1].node.get_our_node_id();
3577+
3578+
const FEERATE: u32 = 253;
3579+
const MULTIPLE: u32 = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
3580+
const SPIKED_FEERATE: u32 = FEERATE * MULTIPLE;
3581+
const DUST_LIMIT_MSAT: u64 = 354 * 1000;
3582+
const CHANNEL_VALUE_MSAT: u64 = 10_000 * 1000;
3583+
const NODE_0_VALUE_TO_SELF_MSAT: u64 = 5_000 * 1000;
3584+
const NODE_1_VALUE_TO_SELF_MSAT: u64 = 5_000 * 1000;
3585+
const CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000;
3586+
3587+
let channel_id = create_announced_chan_between_nodes_with_value(
3588+
&nodes,
3589+
0,
3590+
1,
3591+
CHANNEL_VALUE_MSAT / 1000,
3592+
NODE_1_VALUE_TO_SELF_MSAT,
3593+
)
3594+
.2;
3595+
assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type);
3596+
3597+
// Find the HTLC amount that will be non-dust at the current feerate,
3598+
// but dust at the spiked feerate.
3599+
const SPIKED_DUST_HTLC_MSAT: u64 = 688 * 1000;
3600+
const HTLC_SPIKE_DUST_LIMIT_MSAT: u64 = 689 * 1000;
3601+
// When checking the fee spike buffer in `can_accept_incoming_htlc`, we check the remote
3602+
// commitment, hence inbound HTLCs will be offered HTLCs, and use the timeout dust limit.
3603+
let htlc_timeout_spike_tx_fee_msat =
3604+
second_stage_tx_fees_sat(&channel_type, SPIKED_FEERATE).1 * 1000;
3605+
assert_eq!(HTLC_SPIKE_DUST_LIMIT_MSAT, DUST_LIMIT_MSAT + htlc_timeout_spike_tx_fee_msat);
3606+
3607+
// Calculate here the dust limit at the current feerate so we know when node 0 cannot send
3608+
// any further non-dust HTLCs at the current feerate.
3609+
let htlc_timeout_tx_fee_msat = second_stage_tx_fees_sat(&channel_type, FEERATE).1 * 1000;
3610+
let htlc_dust_limit_msat = DUST_LIMIT_MSAT + htlc_timeout_tx_fee_msat;
3611+
// Make sure the HTLC will be non-dust at the current feerate
3612+
assert!(SPIKED_DUST_HTLC_MSAT > htlc_dust_limit_msat);
3613+
3614+
// Place a few non-dust HTLCs on the commitment, these HTLCs would get trimmed upon a 2x
3615+
// increase in the feerate.
3616+
let mut sent_htlcs_count: usize = 0;
3617+
let mut payment_hashes = Vec::new();
3618+
while nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat >= htlc_dust_limit_msat {
3619+
let (_preimage, hash, _secret, _id) =
3620+
route_payment(&nodes[0], &[&nodes[1]], SPIKED_DUST_HTLC_MSAT);
3621+
payment_hashes.push(hash);
3622+
sent_htlcs_count += 1;
3623+
}
3624+
assert_eq!(sent_htlcs_count, 4);
3625+
3626+
// Check the outbound and available capacities
3627+
let node_0_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT
3628+
- sent_htlcs_count as u64 * SPIKED_DUST_HTLC_MSAT
3629+
- CHANNEL_RESERVE_MSAT;
3630+
let node_0_details = &nodes[0].node.list_channels()[0];
3631+
assert_eq!(node_0_details.outbound_capacity_msat, node_0_outbound_capacity_msat);
3632+
// Node 0 can now only send dust HTLCs, so we reserve the fees for a single additional
3633+
// inbound non-dust HTLC.
3634+
let min_reserved_fee_msat =
3635+
commit_tx_fee_sat(SPIKED_FEERATE, sent_htlcs_count + 1, &channel_type) * 1000;
3636+
let node_0_available_capacity_msat = node_0_outbound_capacity_msat - min_reserved_fee_msat;
3637+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, node_0_available_capacity_msat);
3638+
3639+
// Then send an identical, 5th non-dust HTLC, bypass the validation from the holder, and
3640+
// check that the counterparty fails it due to a fee spike buffer violation.
3641+
3642+
// First check the maths
3643+
3644+
// Node 0 can afford an exact 2x increase in the feerate
3645+
let spiked_commit_tx_fee_msat = commit_tx_fee_sat(SPIKED_FEERATE, 0, &channel_type) * 1000;
3646+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3647+
.checked_sub(spiked_commit_tx_fee_msat)
3648+
.is_some());
3649+
// Node 0 can afford a 5th non-dust HTLC at the current feerate, so `update_add_htlc`
3650+
// validation will pass.
3651+
let real_commit_tx_fee_msat = commit_tx_fee_sat(FEERATE, 5, &channel_type) * 1000;
3652+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3653+
.checked_sub(real_commit_tx_fee_msat)
3654+
.is_some());
3655+
// But we don't account for the HTLC trimming effect of the spike multiple feerate increase,
3656+
// so the 5th HTLC should be rejected at `can_accept_incoming_htlc`!
3657+
let expected_commit_tx_fee_msat = commit_tx_fee_sat(SPIKED_FEERATE, 5, &channel_type) * 1000;
3658+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3659+
.checked_sub(expected_commit_tx_fee_msat)
3660+
.is_none());
3661+
3662+
// Then run the experiment
3663+
3664+
let sender_amount_msat = node_0_available_capacity_msat;
3665+
let receiver_amount_msat = SPIKED_DUST_HTLC_MSAT;
3666+
let (route, payment_hash, _, payment_secret) =
3667+
get_route_and_payment_hash!(nodes[0], nodes[1], sender_amount_msat);
3668+
let secp_ctx = Secp256k1::new();
3669+
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
3670+
let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
3671+
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv);
3672+
let recipient_onion_fields =
3673+
RecipientOnionFields::secret_only(payment_secret, sender_amount_msat);
3674+
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::test_build_onion_payloads(
3675+
&route.paths[0],
3676+
&recipient_onion_fields,
3677+
cur_height,
3678+
&None,
3679+
None,
3680+
None,
3681+
)
3682+
.unwrap();
3683+
assert_eq!(htlc_msat, sender_amount_msat);
3684+
let onion_packet =
3685+
onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash)
3686+
.unwrap();
3687+
let msg = msgs::UpdateAddHTLC {
3688+
channel_id,
3689+
htlc_id: sent_htlcs_count as u64,
3690+
amount_msat: receiver_amount_msat,
3691+
payment_hash,
3692+
cltv_expiry: htlc_cltv,
3693+
onion_routing_packet: onion_packet,
3694+
skimmed_fee_msat: None,
3695+
blinding_point: None,
3696+
hold_htlc: None,
3697+
accountable: None,
3698+
};
3699+
3700+
nodes[1].node.handle_update_add_htlc(node_a_id, &msg);
3701+
3702+
let htlcs_in_tx = vec![
3703+
HTLCOutputInCommitment {
3704+
offered: false,
3705+
cltv_expiry: 81,
3706+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x75).unwrap().clone(),
3707+
amount_msat: 688_000,
3708+
transaction_output_index: Some(0),
3709+
},
3710+
HTLCOutputInCommitment {
3711+
offered: false,
3712+
cltv_expiry: 81,
3713+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x64).unwrap().clone(),
3714+
amount_msat: 688_000,
3715+
transaction_output_index: Some(1),
3716+
},
3717+
HTLCOutputInCommitment {
3718+
offered: false,
3719+
cltv_expiry: 81,
3720+
payment_hash,
3721+
amount_msat: 688_000,
3722+
transaction_output_index: Some(2),
3723+
},
3724+
HTLCOutputInCommitment {
3725+
offered: false,
3726+
cltv_expiry: 81,
3727+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x72).unwrap().clone(),
3728+
amount_msat: 688_000,
3729+
transaction_output_index: Some(3),
3730+
},
3731+
HTLCOutputInCommitment {
3732+
offered: false,
3733+
cltv_expiry: 81,
3734+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x66).unwrap().clone(),
3735+
amount_msat: 688_000,
3736+
transaction_output_index: Some(4),
3737+
},
3738+
];
3739+
3740+
manually_trigger_update_fail_htlc(
3741+
&nodes,
3742+
channel_id,
3743+
NODE_0_VALUE_TO_SELF_MSAT,
3744+
DUST_LIMIT_MSAT / 1000,
3745+
payment_hash,
3746+
htlcs_in_tx,
3747+
true,
3748+
);
3749+
}

lightning/src/sign/tx_builder.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,18 @@ fn get_next_commitment_stats(
300300
// 2) Now including any additional non-dust HTLCs (usually the fee spike buffer HTLC), does the funder cover
301301
// this bigger transaction fee ? The funder can dip below their dust limit to cover this case, as the
302302
// commitment will have at least one output: the non-dust fee spike buffer HTLC offered by the counterparty.
303+
let nondust_htlc_count = next_commitment_htlcs
304+
.iter()
305+
.filter(|htlc| {
306+
!htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
307+
})
308+
.count();
309+
// Note here we use the htlc count at the current feerate together with the spiked feerate;
310+
// this makes sure that the holder can afford any fee bump between 1x to 2x from the current
311+
// feerate if the fee spike multiple is included.
303312
let commit_tx_fee_sat = commit_tx_fee_sat(
304313
spiked_feerate,
305-
spiked_nondust_htlc_count + addl_nondust_htlc_count,
314+
nondust_htlc_count + addl_nondust_htlc_count,
306315
channel_type,
307316
);
308317
let (holder_balance_msat, counterparty_balance_msat) = checked_sub_from_funder(
@@ -317,7 +326,7 @@ fn get_next_commitment_stats(
317326
counterparty_balance_msat,
318327
dust_exposure_msat,
319328
#[cfg(any(test, fuzzing))]
320-
nondust_htlc_count: spiked_nondust_htlc_count + addl_nondust_htlc_count,
329+
nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count,
321330
#[cfg(any(test, fuzzing))]
322331
commit_tx_fee_sat,
323332
})

0 commit comments

Comments
 (0)