Skip to content

Commit 89ef026

Browse files
committed
Test the fee spike buffer multiple affordability check
Make sure it correctly does not trim HTLCs at the spiked feerate, see the previous commit for further details.
1 parent 555d6ed commit 89ef026

1 file changed

Lines changed: 233 additions & 35 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
}
@@ -3640,3 +3642,199 @@ fn test_0reserve_outbound_vs_available_capacity_outbound_htlc_limit() {
36403642
assert_eq!(node_1_details.outbound_capacity_msat, 0);
36413643
assert_eq!(nodes[1].node.list_channels()[0].next_outbound_htlc_limit_msat, 0);
36423644
}
3645+
3646+
/// Make sure that we do not account for HTLCs going from non-dust to dust at the spiked feerate
3647+
/// when checking the fee spike buffer in `can_accept_incoming_htlc`. This is required to make sure
3648+
/// that we can afford *any* increase in the feerate between 1x to 2x, instead of checking whether
3649+
/// we can afford only the 2x increase in the feerate.
3650+
#[xtest(feature = "_externalize_tests")]
3651+
fn test_fail_cannot_afford_dust_htlcs_at_spike_multiple_if_nondust_at_base_feerate() {
3652+
let mut config = test_default_channel_config();
3653+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = false;
3654+
config.channel_handshake_config.negotiate_anchor_zero_fee_commitments = false;
3655+
3656+
let chanmon_cfgs = create_chanmon_cfgs(2);
3657+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
3658+
config.channel_handshake_config.announced_channel_max_inbound_htlc_value_in_flight_percentage =
3659+
100;
3660+
3661+
let channel_type = ChannelTypeFeatures::only_static_remote_key();
3662+
3663+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
3664+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3665+
3666+
let node_a_id = nodes[0].node.get_our_node_id();
3667+
let _node_b_id = nodes[1].node.get_our_node_id();
3668+
3669+
const FEERATE: u32 = 253;
3670+
const MULTIPLE: u32 = FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32;
3671+
const SPIKED_FEERATE: u32 = FEERATE * MULTIPLE;
3672+
const DUST_LIMIT_MSAT: u64 = 354 * 1000;
3673+
const CHANNEL_VALUE_MSAT: u64 = 10_000 * 1000;
3674+
const NODE_0_VALUE_TO_SELF_MSAT: u64 = 5_000 * 1000;
3675+
const NODE_1_VALUE_TO_SELF_MSAT: u64 = 5_000 * 1000;
3676+
const CHANNEL_RESERVE_MSAT: u64 = 1_000 * 1_000;
3677+
3678+
let channel_id = create_announced_chan_between_nodes_with_value(
3679+
&nodes,
3680+
0,
3681+
1,
3682+
CHANNEL_VALUE_MSAT / 1000,
3683+
NODE_1_VALUE_TO_SELF_MSAT,
3684+
)
3685+
.2;
3686+
assert_eq!(nodes[0].node.list_channels()[0].channel_type.as_ref().unwrap(), &channel_type);
3687+
3688+
// Find the HTLC amount that will be non-dust at the current feerate,
3689+
// but dust at the spiked feerate.
3690+
const SPIKED_DUST_HTLC_MSAT: u64 = 688 * 1000;
3691+
const HTLC_SPIKE_DUST_LIMIT_MSAT: u64 = 689 * 1000;
3692+
// When checking the fee spike buffer in `can_accept_incoming_htlc`, we check the remote
3693+
// commitment, hence inbound HTLCs will be offered HTLCs, and use the timeout dust limit.
3694+
let htlc_timeout_spike_tx_fee_msat =
3695+
second_stage_tx_fees_sat(&channel_type, SPIKED_FEERATE).1 * 1000;
3696+
assert_eq!(HTLC_SPIKE_DUST_LIMIT_MSAT, DUST_LIMIT_MSAT + htlc_timeout_spike_tx_fee_msat);
3697+
3698+
// Calculate here the dust limit at the current feerate so we know when node 0 cannot send
3699+
// any further non-dust HTLCs at the current feerate.
3700+
let htlc_timeout_tx_fee_msat = second_stage_tx_fees_sat(&channel_type, FEERATE).1 * 1000;
3701+
let htlc_dust_limit_msat = DUST_LIMIT_MSAT + htlc_timeout_tx_fee_msat;
3702+
// Make sure the HTLC will be non-dust at the current feerate
3703+
assert!(SPIKED_DUST_HTLC_MSAT > htlc_dust_limit_msat);
3704+
3705+
// Place a few non-dust HTLCs on the commitment, these HTLCs would get trimmed upon a 2x
3706+
// increase in the feerate.
3707+
let mut sent_htlcs_count: usize = 0;
3708+
let mut payment_hashes = Vec::new();
3709+
while nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat >= htlc_dust_limit_msat {
3710+
let (_preimage, hash, _secret, _id) =
3711+
route_payment(&nodes[0], &[&nodes[1]], SPIKED_DUST_HTLC_MSAT);
3712+
payment_hashes.push(hash);
3713+
sent_htlcs_count += 1;
3714+
}
3715+
assert_eq!(sent_htlcs_count, 4);
3716+
3717+
// Check the outbound and available capacities
3718+
let node_0_outbound_capacity_msat = NODE_0_VALUE_TO_SELF_MSAT
3719+
- sent_htlcs_count as u64 * SPIKED_DUST_HTLC_MSAT
3720+
- CHANNEL_RESERVE_MSAT;
3721+
let node_0_details = &nodes[0].node.list_channels()[0];
3722+
assert_eq!(node_0_details.outbound_capacity_msat, node_0_outbound_capacity_msat);
3723+
// Node 0 can now only send dust HTLCs, so we reserve the fees for a single additional
3724+
// inbound non-dust HTLC.
3725+
let min_reserved_fee_msat =
3726+
commit_tx_fee_sat(SPIKED_FEERATE, sent_htlcs_count + 1, &channel_type) * 1000;
3727+
let node_0_available_capacity_msat = node_0_outbound_capacity_msat - min_reserved_fee_msat;
3728+
assert_eq!(node_0_details.next_outbound_htlc_limit_msat, node_0_available_capacity_msat);
3729+
3730+
// Then send an identical, 5th non-dust HTLC, bypass the validation from the holder, and
3731+
// check that the counterparty fails it due to a fee spike buffer violation.
3732+
3733+
// First check the maths
3734+
3735+
// Node 0 can afford an exact 2x increase in the feerate
3736+
let spiked_commit_tx_fee_msat = commit_tx_fee_sat(SPIKED_FEERATE, 0, &channel_type) * 1000;
3737+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3738+
.checked_sub(spiked_commit_tx_fee_msat)
3739+
.is_some());
3740+
// Node 0 can afford a 5th non-dust HTLC at the current feerate, so `update_add_htlc`
3741+
// validation will pass.
3742+
let real_commit_tx_fee_msat = commit_tx_fee_sat(FEERATE, 5, &channel_type) * 1000;
3743+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3744+
.checked_sub(real_commit_tx_fee_msat)
3745+
.is_some());
3746+
// But we don't account for the HTLC trimming effect of the spike multiple feerate increase,
3747+
// so the 5th HTLC should be rejected at `can_accept_incoming_htlc`!
3748+
let expected_commit_tx_fee_msat = commit_tx_fee_sat(SPIKED_FEERATE, 5, &channel_type) * 1000;
3749+
assert!((node_0_outbound_capacity_msat - SPIKED_DUST_HTLC_MSAT)
3750+
.checked_sub(expected_commit_tx_fee_msat)
3751+
.is_none());
3752+
3753+
// Then run the experiment
3754+
3755+
let sender_amount_msat = node_0_available_capacity_msat;
3756+
let receiver_amount_msat = SPIKED_DUST_HTLC_MSAT;
3757+
let (route, payment_hash, _, payment_secret) =
3758+
get_route_and_payment_hash!(nodes[0], nodes[1], sender_amount_msat);
3759+
let secp_ctx = Secp256k1::new();
3760+
let session_priv = SecretKey::from_slice(&[42; 32]).unwrap();
3761+
let cur_height = nodes[0].node.best_block.read().unwrap().height + 1;
3762+
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv);
3763+
let recipient_onion_fields =
3764+
RecipientOnionFields::secret_only(payment_secret, sender_amount_msat);
3765+
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::test_build_onion_payloads(
3766+
&route.paths[0],
3767+
&recipient_onion_fields,
3768+
cur_height,
3769+
&None,
3770+
None,
3771+
None,
3772+
)
3773+
.unwrap();
3774+
assert_eq!(htlc_msat, sender_amount_msat);
3775+
let onion_packet =
3776+
onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash)
3777+
.unwrap();
3778+
let msg = msgs::UpdateAddHTLC {
3779+
channel_id,
3780+
htlc_id: sent_htlcs_count as u64,
3781+
amount_msat: receiver_amount_msat,
3782+
payment_hash,
3783+
cltv_expiry: htlc_cltv,
3784+
onion_routing_packet: onion_packet,
3785+
skimmed_fee_msat: None,
3786+
blinding_point: None,
3787+
hold_htlc: None,
3788+
accountable: None,
3789+
};
3790+
3791+
nodes[1].node.handle_update_add_htlc(node_a_id, &msg);
3792+
3793+
let htlcs_in_tx = vec![
3794+
HTLCOutputInCommitment {
3795+
offered: false,
3796+
cltv_expiry: 81,
3797+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x75).unwrap().clone(),
3798+
amount_msat: 688_000,
3799+
transaction_output_index: Some(0),
3800+
},
3801+
HTLCOutputInCommitment {
3802+
offered: false,
3803+
cltv_expiry: 81,
3804+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x64).unwrap().clone(),
3805+
amount_msat: 688_000,
3806+
transaction_output_index: Some(1),
3807+
},
3808+
HTLCOutputInCommitment {
3809+
offered: false,
3810+
cltv_expiry: 81,
3811+
payment_hash,
3812+
amount_msat: 688_000,
3813+
transaction_output_index: Some(2),
3814+
},
3815+
HTLCOutputInCommitment {
3816+
offered: false,
3817+
cltv_expiry: 81,
3818+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x72).unwrap().clone(),
3819+
amount_msat: 688_000,
3820+
transaction_output_index: Some(3),
3821+
},
3822+
HTLCOutputInCommitment {
3823+
offered: false,
3824+
cltv_expiry: 81,
3825+
payment_hash: payment_hashes.iter().find(|hash| hash.0[0] == 0x66).unwrap().clone(),
3826+
amount_msat: 688_000,
3827+
transaction_output_index: Some(4),
3828+
},
3829+
];
3830+
3831+
manually_trigger_update_fail_htlc(
3832+
&nodes,
3833+
channel_id,
3834+
NODE_0_VALUE_TO_SELF_MSAT,
3835+
DUST_LIMIT_MSAT / 1000,
3836+
payment_hash,
3837+
htlcs_in_tx,
3838+
true,
3839+
);
3840+
}

0 commit comments

Comments
 (0)