From 235d5eca529e082846d2d20f16b6dc5b8feeeb63 Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 23 Oct 2025 10:15:14 -0400 Subject: [PATCH 1/5] ln: add experimental accountable signal to update_add_htlc --- lightning/src/ln/blinded_payment_tests.rs | 1 + lightning/src/ln/channel.rs | 1 + lightning/src/ln/functional_tests.rs | 1 + lightning/src/ln/htlc_reserve_unit_tests.rs | 5 +++++ lightning/src/ln/msgs.rs | 9 +++++++++ lightning/src/ln/onion_payment.rs | 1 + lightning/src/ln/payment_tests.rs | 1 + 7 files changed, 19 insertions(+) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 25fa5e71e5d..f7fe233dd49 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1523,6 +1523,7 @@ fn update_add_msg( skimmed_fee_msat: None, blinding_point, hold_htlc: None, + accountable: None, } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 8f74dc24e71..e3339cf0b0c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -9639,6 +9639,7 @@ where skimmed_fee_msat: htlc.skimmed_fee_msat, blinding_point: htlc.blinding_point, hold_htlc: htlc.hold_htlc, + accountable: None, }); } } diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index d79b3074cc7..2dd0691f729 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -2269,6 +2269,7 @@ pub fn fail_backward_pending_htlc_upon_channel_failure() { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; nodes[0].node.handle_update_add_htlc(node_b_id, &update_add_htlc); } diff --git a/lightning/src/ln/htlc_reserve_unit_tests.rs b/lightning/src/ln/htlc_reserve_unit_tests.rs index dc5d07c180e..7952039253f 100644 --- a/lightning/src/ln/htlc_reserve_unit_tests.rs +++ b/lightning/src/ln/htlc_reserve_unit_tests.rs @@ -836,6 +836,7 @@ pub fn do_test_fee_spike_buffer(cfg: Option, htlc_fails: bool) { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; nodes[1].node.handle_update_add_htlc(node_a_id, &msg); @@ -1075,6 +1076,7 @@ pub fn test_chan_reserve_violation_inbound_htlc_outbound_channel() { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; nodes[0].node.handle_update_add_htlc(node_b_id, &msg); @@ -1259,6 +1261,7 @@ pub fn test_chan_reserve_violation_inbound_htlc_inbound_chan() { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; nodes[1].node.handle_update_add_htlc(node_a_id, &msg); @@ -1642,6 +1645,7 @@ pub fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; for i in 0..50 { @@ -2248,6 +2252,7 @@ pub fn do_test_dust_limit_fee_accounting(can_afford: bool) { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; nodes[1].node.handle_update_add_htlc(node_a_id, &msg); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 354273f7170..cdeb9bb0412 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -771,6 +771,13 @@ pub struct UpdateAddHTLC { /// /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc pub hold_htlc: Option<()>, + /// An experimental field indicating whether the receiving node's reputation would be held + /// accountable for the timely resolution of the HTLC. + /// + /// Note that this field is [`experimental`] so should not be used for forwarding decisions. + /// + /// [`experimental`]: https://github.com/lightning/blips/blob/master/blip-0004.md + pub accountable: Option, } /// An [`onion message`] to be sent to or received from a peer. @@ -3368,6 +3375,7 @@ impl_writeable_msg!(UpdateAddHTLC, { // TODO: currently we may fail to read the `ChannelManager` if we write a new even TLV in this message // and then downgrade. Once this is fixed, update the type here to match BOLTs PR 989. (75537, hold_htlc, option), + (106823, accountable, option), }); impl LengthReadable for OnionMessage { @@ -5865,6 +5873,7 @@ mod tests { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; let encoded_value = update_add_htlc.encode(); let target_value = >::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap(); diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 84b1338b1a7..20c4c3a9cb8 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -755,6 +755,7 @@ mod tests { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, } } diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 9eb85173a83..0177799306e 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -5078,6 +5078,7 @@ fn peel_payment_onion_custom_tlvs() { onion_routing_packet, blinding_point: None, hold_htlc: None, + accountable: None, }; let peeled_onion = crate::ln::onion_payment::peel_payment_onion( &update_add, From 8fb06818745f2f47ad32241e748847d2202b52fd Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 23 Oct 2025 14:08:03 -0400 Subject: [PATCH 2/5] ln: add incoming_accountable to PendingHTLCInfo --- lightning/src/ln/channelmanager.rs | 14 ++++++++++---- lightning/src/ln/onion_payment.rs | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 68c5e810c1d..b24fea2b385 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -426,6 +426,9 @@ pub struct PendingHTLCInfo { /// This is used to allow LSPs to take fees as a part of payments, without the sender having to /// shoulder them. pub skimmed_fee_msat: Option, + /// An experimental field indicating whether our node's reputation would be held accountable + /// for the timely resolution of the received HTLC. + pub incoming_accountable: Option, } #[derive(Clone)] // See FundedChannel::revoke_and_ack for why, tl;dr: Rust bug @@ -5048,7 +5051,7 @@ where let current_height: u32 = self.best_block.read().unwrap().height; create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, - current_height) + msg.accountable, current_height) }, onion_utils::Hop::Forward { .. } | onion_utils::Hop::BlindedForward { .. } => { create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) @@ -7150,6 +7153,7 @@ where payment_hash, outgoing_amt_msat, outgoing_cltv_value, + incoming_accountable, .. }, } = payment; @@ -7248,6 +7252,7 @@ where Some(phantom_shared_secret), false, None, + incoming_accountable, current_height, ); match create_res { @@ -15764,6 +15769,7 @@ impl_writeable_tlv_based!(PendingHTLCInfo, { (8, outgoing_cltv_value, required), (9, incoming_amt_msat, option), (10, skimmed_fee_msat, option), + (11, incoming_accountable, option), }); impl Writeable for HTLCFailureMsg { @@ -19198,7 +19204,7 @@ mod tests { if let Err(crate::ln::channelmanager::InboundHTLCErr { reason, .. }) = create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat - 1, 42, None, true, Some(extra_fee_msat), - current_height) + None, current_height) { assert_eq!(reason, LocalHTLCFailureReason::FinalIncorrectHTLCAmount); } else { panic!(); } @@ -19221,7 +19227,7 @@ mod tests { let current_height: u32 = node[0].node.best_block.read().unwrap().height; assert!(create_recv_pending_htlc_info(hop_data, [0; 32], PaymentHash([0; 32]), sender_intended_amt_msat - extra_fee_msat, 42, None, true, Some(extra_fee_msat), - current_height).is_ok()); + None, current_height).is_ok()); } #[test] @@ -19246,7 +19252,7 @@ mod tests { custom_tlvs: Vec::new(), }, shared_secret: SharedSecret::from_bytes([0; 32]), - }, [0; 32], PaymentHash([0; 32]), 100, TEST_FINAL_CLTV + 1, None, true, None, current_height); + }, [0; 32], PaymentHash([0; 32]), 100, TEST_FINAL_CLTV + 1, None, true, None, None, current_height); // Should not return an error as this condition: // https://github.com/lightning/bolts/blob/4dcc377209509b13cf89a4b91fde7d478f5b46d8/04-onion-routing.md?plain=1#L334 diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 20c4c3a9cb8..e0568e2f684 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -241,6 +241,7 @@ pub(super) fn create_fwd_pending_htlc_info( outgoing_amt_msat: amt_to_forward, outgoing_cltv_value, skimmed_fee_msat: None, + incoming_accountable: msg.accountable, }) } @@ -248,7 +249,7 @@ pub(super) fn create_fwd_pending_htlc_info( pub(super) fn create_recv_pending_htlc_info( hop_data: onion_utils::Hop, shared_secret: [u8; 32], payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>, allow_underpay: bool, - counterparty_skimmed_fee_msat: Option, current_height: u32 + counterparty_skimmed_fee_msat: Option, incoming_accountable: Option, current_height: u32 ) -> Result { let ( payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry, @@ -416,6 +417,7 @@ pub(super) fn create_recv_pending_htlc_info( outgoing_amt_msat: onion_amt_msat, outgoing_cltv_value: onion_cltv_expiry, skimmed_fee_msat: counterparty_skimmed_fee_msat, + incoming_accountable, }) } @@ -480,7 +482,7 @@ where let shared_secret = hop.shared_secret().secret_bytes(); create_recv_pending_htlc_info( hop, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, - None, allow_skimmed_fees, msg.skimmed_fee_msat, cur_height, + None, allow_skimmed_fees, msg.skimmed_fee_msat, msg.accountable, cur_height, )? } }) From ef58f947e1bae3f9254771ea96cf122feaaa81db Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 23 Oct 2025 14:15:06 -0400 Subject: [PATCH 3/5] [approach1]: Set outgoing_accountable in PendingHTLCInfo Downside: - We create this struct early on, so have to set this value to none and then mutate it. Upside: - We coherently store the accountable signal with the other forwarding information that we'll be using later on. --- lightning/src/ln/channelmanager.rs | 13 ++++++++++++- lightning/src/ln/onion_payment.rs | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b24fea2b385..13a94ef2cc1 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -429,6 +429,9 @@ pub struct PendingHTLCInfo { /// An experimental field indicating whether our node's reputation would be held accountable /// for the timely resolution of the received HTLC. pub incoming_accountable: Option, + /// An experimental field indicating whether the outgoing node's reputation would be held + /// accountable for the timely resolution of the offered HTLC. + pub outgoing_accountable: Option, } #[derive(Clone)] // See FundedChannel::revoke_and_ack for why, tl;dr: Rust bug @@ -11226,7 +11229,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ Some(prev_channel_id), Some(payment_hash), ); - let pending_add = PendingAddHTLCInfo { + let mut pending_add = PendingAddHTLCInfo { prev_outbound_scid_alias, prev_counterparty_node_id, prev_funding_outpoint, @@ -11320,6 +11323,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ }, } } else { + // Set the value for our outgoing accountable signal to copy the received + // incoming value (or just set zero if not present). This point is where we + // could introduce an interceptor that provides us with custom accountable + // values if desired. + pending_add.forward_info.outgoing_accountable = + pending_add.forward_info.incoming_accountable.or(Some(0)); + match self.forward_htlcs.lock().unwrap().entry(scid) { hash_map::Entry::Occupied(mut entry) => { entry.get_mut().push(HTLCForwardInfo::AddHTLC(pending_add)); @@ -15770,6 +15780,7 @@ impl_writeable_tlv_based!(PendingHTLCInfo, { (9, incoming_amt_msat, option), (10, skimmed_fee_msat, option), (11, incoming_accountable, option), + (13, outgoing_accountable, option), }); impl Writeable for HTLCFailureMsg { diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index e0568e2f684..65fd4c155d9 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -242,6 +242,7 @@ pub(super) fn create_fwd_pending_htlc_info( outgoing_cltv_value, skimmed_fee_msat: None, incoming_accountable: msg.accountable, + outgoing_accountable: None, }) } @@ -418,6 +419,7 @@ pub(super) fn create_recv_pending_htlc_info( outgoing_cltv_value: onion_cltv_expiry, skimmed_fee_msat: counterparty_skimmed_fee_msat, incoming_accountable, + outgoing_accountable: None, }) } From 1f00685ca30fb23a16aae018839ec195dbd93df4 Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 23 Oct 2025 10:48:24 -0400 Subject: [PATCH 4/5] ln: add accountable signal to HTLCUpdateAwaitingACK::AddHTLC --- lightning/src/ln/channel.rs | 37 ++++++++++++++++++++++++++---- lightning/src/ln/channelmanager.rs | 3 +++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e3339cf0b0c..77e5af1efdc 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -445,6 +445,7 @@ enum HTLCUpdateAwaitingACK { skimmed_fee_msat: Option, blinding_point: Option, hold_htlc: Option<()>, + accountable: Option, }, ClaimHTLC { payment_preimage: PaymentPreimage, @@ -8314,7 +8315,7 @@ where skimmed_fee_msat, blinding_point, hold_htlc, - .. + accountable, } => { match self.send_htlc( amount_msat, @@ -8326,6 +8327,7 @@ where skimmed_fee_msat, blinding_point, hold_htlc.is_some(), + accountable, fee_estimator, logger, ) { @@ -12429,7 +12431,8 @@ where pub fn queue_add_htlc( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option, - blinding_point: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + blinding_point: Option, accountable: Option, + fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Result<(), (LocalHTLCFailureReason, String)> where F::Target: FeeEstimator, @@ -12446,6 +12449,7 @@ where blinding_point, // This method is only called for forwarded HTLCs, which are never held at the next hop false, + accountable, fee_estimator, logger, ) @@ -12477,7 +12481,7 @@ where &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool, skimmed_fee_msat: Option, blinding_point: Option, hold_htlc: bool, - fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + accountable: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, ) -> Result where F::Target: FeeEstimator, @@ -12559,6 +12563,7 @@ where skimmed_fee_msat, blinding_point, hold_htlc: hold_htlc.then(|| ()), + accountable, }); return Ok(false); } @@ -12807,7 +12812,8 @@ where pub fn send_htlc_and_commit( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option, - hold_htlc: bool, fee_estimator: &LowerBoundedFeeEstimator, logger: &L, + hold_htlc: bool, accountable: Option, fee_estimator: &LowerBoundedFeeEstimator, + logger: &L, ) -> Result, ChannelError> where F::Target: FeeEstimator, @@ -12823,6 +12829,7 @@ where skimmed_fee_msat, None, hold_htlc, + accountable, fee_estimator, logger, ); @@ -14481,6 +14488,8 @@ where Vec::with_capacity(holding_cell_htlc_update_count); let mut holding_cell_held_htlc_flags: Vec> = Vec::with_capacity(holding_cell_htlc_update_count); + let mut holding_cell_accountable_flags: Vec> = + Vec::with_capacity(holding_cell_htlc_update_count); // Vec of (htlc_id, failure_code, sha256_of_onion) let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new(); (holding_cell_htlc_update_count as u64).write(writer)?; @@ -14495,6 +14504,7 @@ where blinding_point, skimmed_fee_msat, hold_htlc, + accountable, } => { 0u8.write(writer)?; amount_msat.write(writer)?; @@ -14506,6 +14516,7 @@ where holding_cell_skimmed_fees.push(skimmed_fee_msat); holding_cell_blinding_points.push(blinding_point); holding_cell_held_htlc_flags.push(hold_htlc); + holding_cell_accountable_flags.push(accountable); }, &HTLCUpdateAwaitingACK::ClaimHTLC { ref payment_preimage, @@ -14760,6 +14771,7 @@ where (65, self.quiescent_action, option), // Added in 0.2 (67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2 + (71, holding_cell_accountable_flags, optional_vec), // Added in 0.3 }); Ok(()) @@ -14928,6 +14940,7 @@ where skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }, 1 => HTLCUpdateAwaitingACK::ClaimHTLC { payment_preimage: Readable::read(reader)?, @@ -15127,6 +15140,7 @@ where let mut pending_outbound_held_htlc_flags_opt: Option>> = None; let mut holding_cell_held_htlc_flags_opt: Option>> = None; + let mut holding_cell_accountable_opt: Option>> = None; read_tlv_fields!(reader, { (0, announcement_sigs, option), @@ -15174,6 +15188,7 @@ where (65, quiescent_action, upgradable_option), // Added in 0.2 (67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2 + (71, holding_cell_accountable_opt, optional_vec), // Added in 0.3 }); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); @@ -15298,6 +15313,19 @@ where } } + if let Some(accountable_htlcs) = holding_cell_accountable_opt { + let mut iter = accountable_htlcs.into_iter(); + for htlc in holding_cell_htlc_updates.iter_mut() { + if let HTLCUpdateAwaitingACK::AddHTLC { ref mut accountable, .. } = htlc { + *accountable = iter.next().ok_or(DecodeError::InvalidValue)?; + } + } + // We expect all accountable HTLC signals to be consumed above + if iter.next().is_some() { + return Err(DecodeError::InvalidValue); + } + } + if let Some(attribution_data_list) = removed_htlc_attribution_data { let mut removed_htlcs = pending_inbound_htlcs.iter_mut().filter_map(|status| { if let InboundHTLCState::LocalRemoved(reason) = &mut status.state { @@ -16360,6 +16388,7 @@ mod tests { skimmed_fee_msat: None, blinding_point: None, hold_htlc: None, + accountable: None, }; let dummy_holding_cell_claim_htlc = |attribution_data| HTLCUpdateAwaitingACK::ClaimHTLC { payment_preimage: PaymentPreimage([42; 32]), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 13a94ef2cc1..a298a1b1e34 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5274,6 +5274,7 @@ where onion_packet, None, hold_htlc_at_next_hop, + Some(0), &self.fee_estimator, &&logger, ); @@ -7365,6 +7366,7 @@ where outgoing_cltv_value, routing, skimmed_fee_msat, + outgoing_accountable, .. }, .. @@ -7460,6 +7462,7 @@ where onion_packet.clone(), *skimmed_fee_msat, next_blinding_point, + *outgoing_accountable, &self.fee_estimator, &&logger, ) { From c62eb0f9de5decddde2a5b73aa69d0228b701740 Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 23 Oct 2025 13:41:43 -0400 Subject: [PATCH 5/5] ln: add accountable signal to OutboundHTLCOutput --- lightning/src/ln/channel.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 77e5af1efdc..87aa77850e5 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -428,6 +428,7 @@ struct OutboundHTLCOutput { skimmed_fee_msat: Option, send_timestamp: Option, hold_htlc: Option<()>, + accountable: Option, } /// See AwaitingRemoteRevoke ChannelState for more info @@ -9641,7 +9642,7 @@ where skimmed_fee_msat: htlc.skimmed_fee_msat, blinding_point: htlc.blinding_point, hold_htlc: htlc.hold_htlc, - accountable: None, + accountable: htlc.accountable, }); } } @@ -12586,6 +12587,7 @@ where skimmed_fee_msat, send_timestamp, hold_htlc: hold_htlc.then(|| ()), + accountable, }); self.context.next_holder_htlc_id += 1; @@ -14434,6 +14436,7 @@ where let mut pending_outbound_skimmed_fees: Vec> = Vec::new(); let mut pending_outbound_blinding_points: Vec> = Vec::new(); let mut pending_outbound_held_htlc_flags: Vec> = Vec::new(); + let mut pending_outbound_accountable: Vec> = Vec::new(); (self.context.pending_outbound_htlcs.len() as u64).write(writer)?; for htlc in self.context.pending_outbound_htlcs.iter() { @@ -14477,6 +14480,7 @@ where pending_outbound_skimmed_fees.push(htlc.skimmed_fee_msat); pending_outbound_blinding_points.push(htlc.blinding_point); pending_outbound_held_htlc_flags.push(htlc.hold_htlc); + pending_outbound_accountable.push(htlc.accountable); } let holding_cell_htlc_update_count = self.context.holding_cell_htlc_updates.len(); @@ -14772,6 +14776,7 @@ where (67, pending_outbound_held_htlc_flags, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags, optional_vec), // Added in 0.2 (71, holding_cell_accountable_flags, optional_vec), // Added in 0.3 + (73, pending_outbound_accountable, optional_vec), // Added in 0.3 }); Ok(()) @@ -14921,6 +14926,7 @@ where blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }); } @@ -15140,6 +15146,7 @@ where let mut pending_outbound_held_htlc_flags_opt: Option>> = None; let mut holding_cell_held_htlc_flags_opt: Option>> = None; + let mut pending_outbound_accountable_opt: Option>> = None; let mut holding_cell_accountable_opt: Option>> = None; read_tlv_fields!(reader, { @@ -15189,6 +15196,7 @@ where (67, pending_outbound_held_htlc_flags_opt, optional_vec), // Added in 0.2 (69, holding_cell_held_htlc_flags_opt, optional_vec), // Added in 0.2 (71, holding_cell_accountable_opt, optional_vec), // Added in 0.3 + (73, pending_outbound_accountable_opt, optional_vec), // Added in 0.3 }); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); @@ -15325,7 +15333,16 @@ where return Err(DecodeError::InvalidValue); } } - + if let Some(held_htlcs) = pending_outbound_accountable_opt { + let mut iter = held_htlcs.into_iter(); + for htlc in pending_outbound_htlcs.iter_mut() { + htlc.accountable = iter.next().ok_or(DecodeError::InvalidValue)?; + } + // We expect all accountable HTLC signals to be consumed above + if iter.next().is_some() { + return Err(DecodeError::InvalidValue); + } + } if let Some(attribution_data_list) = removed_htlc_attribution_data { let mut removed_htlcs = pending_inbound_htlcs.iter_mut().filter_map(|status| { if let InboundHTLCState::LocalRemoved(reason) = &mut status.state { @@ -15907,6 +15924,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }); // Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass @@ -16362,6 +16380,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }; let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10]; for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() { @@ -16714,6 +16733,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array(); out @@ -16730,6 +16750,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array(); out @@ -17144,6 +17165,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array(); out @@ -17160,6 +17182,7 @@ mod tests { blinding_point: None, send_timestamp: None, hold_htlc: None, + accountable: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array(); out