-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attributable errors 2025 #3611
base: main
Are you sure you want to change the base?
Attributable errors 2025 #3611
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ use crate::ln::interactivetxs::{ | |
TX_COMMON_FIELDS_WEIGHT, | ||
}; | ||
use crate::ln::msgs; | ||
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; | ||
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError, OnionErrorPacket}; | ||
use crate::ln::script::{self, ShutdownScript}; | ||
use crate::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo, InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails, OutboundHTLCStateDetails}; | ||
use crate::ln::channelmanager::{self, OpenChannelMessage, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, PaymentClaimDetails, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT}; | ||
|
@@ -49,7 +49,7 @@ use crate::ln::chan_utils::{ | |
ClosingTransaction, commit_tx_fee_sat, | ||
}; | ||
use crate::ln::chan_utils; | ||
use crate::ln::onion_utils::HTLCFailReason; | ||
use crate::ln::onion_utils::{HTLCFailReason, ATTRIBUTION_DATA_LEN}; | ||
use crate::chain::BestBlock; | ||
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight}; | ||
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS}; | ||
|
@@ -4722,7 +4722,7 @@ trait FailHTLCContents { | |
impl FailHTLCContents for msgs::OnionErrorPacket { | ||
type Message = msgs::UpdateFailHTLC; | ||
fn to_message(self, htlc_id: u64, channel_id: ChannelId) -> Self::Message { | ||
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self } | ||
msgs::UpdateFailHTLC { htlc_id, channel_id, reason: self.data, attribution_data: self.attribution_data } | ||
} | ||
fn to_inbound_htlc_state(self) -> InboundHTLCState { | ||
InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(self)) | ||
|
@@ -6041,7 +6041,7 @@ impl<SP: Deref> FundedChannel<SP> where | |
require_commitment = true; | ||
match fail_msg { | ||
HTLCFailureMsg::Relay(msg) => { | ||
htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay(msg.reason.clone())); | ||
htlc.state = InboundHTLCState::LocalRemoved(InboundHTLCRemovalReason::FailRelay((&msg).into())); | ||
update_fail_htlcs.push(msg) | ||
}, | ||
HTLCFailureMsg::Malformed(msg) => { | ||
|
@@ -6749,7 +6749,8 @@ impl<SP: Deref> FundedChannel<SP> where | |
update_fail_htlcs.push(msgs::UpdateFailHTLC { | ||
channel_id: self.context.channel_id(), | ||
htlc_id: htlc.htlc_id, | ||
reason: err_packet.clone() | ||
reason: err_packet.data.clone(), | ||
attribution_data: err_packet.attribution_data, | ||
}); | ||
}, | ||
&InboundHTLCRemovalReason::FailMalformed((ref sha256_of_onion, ref failure_code)) => { | ||
|
@@ -9903,11 +9904,6 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) | |
const SERIALIZATION_VERSION: u8 = 4; | ||
const MIN_SERIALIZATION_VERSION: u8 = 4; | ||
|
||
impl_writeable_tlv_based_enum_legacy!(InboundHTLCRemovalReason,; | ||
(0, FailRelay), | ||
(1, FailMalformed), | ||
(2, Fulfill), | ||
); | ||
|
||
impl Writeable for ChannelUpdateStatus { | ||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> { | ||
|
@@ -10013,6 +10009,7 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
dropped_inbound_htlcs += 1; | ||
} | ||
} | ||
let mut removed_htlc_failure_attribution_data: Vec<&Option<[u8; ATTRIBUTION_DATA_LEN]>> = Vec::new(); | ||
(self.context.pending_inbound_htlcs.len() as u64 - dropped_inbound_htlcs).write(writer)?; | ||
for htlc in self.context.pending_inbound_htlcs.iter() { | ||
if let &InboundHTLCState::RemoteAnnounced(_) = &htlc.state { | ||
|
@@ -10037,7 +10034,21 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
}, | ||
&InboundHTLCState::LocalRemoved(ref removal_reason) => { | ||
4u8.write(writer)?; | ||
removal_reason.write(writer)?; | ||
match removal_reason { | ||
InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { data, attribution_data }) => { | ||
0u8.write(writer)?; | ||
data.write(writer)?; | ||
removed_htlc_failure_attribution_data.push(&attribution_data); | ||
}, | ||
InboundHTLCRemovalReason::FailMalformed((hash, code)) => { | ||
1u8.write(writer)?; | ||
(hash, code).write(writer)?; | ||
}, | ||
InboundHTLCRemovalReason::Fulfill(preimage) => { | ||
2u8.write(writer)?; | ||
preimage.write(writer)?; | ||
}, | ||
} | ||
}, | ||
} | ||
} | ||
|
@@ -10089,10 +10100,11 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
|
||
let mut holding_cell_skimmed_fees: Vec<Option<u64>> = Vec::new(); | ||
let mut holding_cell_blinding_points: Vec<Option<PublicKey>> = Vec::new(); | ||
let mut holding_cell_failure_attribution_data: Vec<(u32, [u8; ATTRIBUTION_DATA_LEN])> = Vec::new(); | ||
// Vec of (htlc_id, failure_code, sha256_of_onion) | ||
let mut malformed_htlcs: Vec<(u64, u16, [u8; 32])> = Vec::new(); | ||
(self.context.holding_cell_htlc_updates.len() as u64).write(writer)?; | ||
for update in self.context.holding_cell_htlc_updates.iter() { | ||
for (i, update) in self.context.holding_cell_htlc_updates.iter().enumerate() { | ||
match update { | ||
&HTLCUpdateAwaitingACK::AddHTLC { | ||
ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, | ||
|
@@ -10116,7 +10128,14 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
&HTLCUpdateAwaitingACK::FailHTLC { ref htlc_id, ref err_packet } => { | ||
2u8.write(writer)?; | ||
htlc_id.write(writer)?; | ||
err_packet.write(writer)?; | ||
err_packet.data.write(writer)?; | ||
|
||
// Store the attribution data for later writing. Include the holding cell htlc update index because | ||
// FailMalformedHTLC is stored with the same type 2 and we wouldn't be able to distinguish the two | ||
// when reading back in. | ||
if let Some(attribution_data ) = err_packet.attribution_data { | ||
holding_cell_failure_attribution_data.push((i as u32, attribution_data)); | ||
} | ||
} | ||
&HTLCUpdateAwaitingACK::FailMalformedHTLC { | ||
htlc_id, failure_code, sha256_of_onion | ||
|
@@ -10125,10 +10144,9 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
// `::FailHTLC` variant and write the real malformed error as an optional TLV. | ||
malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion)); | ||
|
||
let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() }; | ||
2u8.write(writer)?; | ||
htlc_id.write(writer)?; | ||
dummy_err_packet.write(writer)?; | ||
Vec::<u8>::new().write(writer)?; | ||
} | ||
} | ||
} | ||
|
@@ -10301,6 +10319,8 @@ impl<SP: Deref> Writeable for FundedChannel<SP> where SP::Target: SignerProvider | |
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122 | ||
(51, is_manual_broadcast, option), // Added in 0.0.124 | ||
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124 | ||
(55, removed_htlc_failure_attribution_data, optional_vec), // Added in 0.2 | ||
(57, holding_cell_failure_attribution_data, optional_vec), // Added in 0.2 | ||
}); | ||
|
||
Ok(()) | ||
|
@@ -10374,7 +10394,18 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel | |
InboundHTLCState::AwaitingAnnouncedRemoteRevoke(resolution) | ||
}, | ||
3 => InboundHTLCState::Committed, | ||
4 => InboundHTLCState::LocalRemoved(Readable::read(reader)?), | ||
4 => { | ||
let reason = match <u8 as Readable>::read(reader)? { | ||
0 => InboundHTLCRemovalReason::FailRelay(msgs::OnionErrorPacket { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update in holding cell, still waiting for next round of commitment dance. Should be tests that test holding cell persistence. Strategy: break main, see if tests fail. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expect_pending_htlcs_forwardable!(nodes[1]); - in here let node 0 do something else like add another htlc, so that the failure gets into the holding cell |
||
data: Readable::read(reader)?, | ||
attribution_data: None, | ||
}), | ||
1 => InboundHTLCRemovalReason::FailMalformed(Readable::read(reader)?), | ||
2 => InboundHTLCRemovalReason::Fulfill(Readable::read(reader)?), | ||
_ => return Err(DecodeError::InvalidValue), | ||
}; | ||
InboundHTLCState::LocalRemoved(reason) | ||
}, | ||
_ => return Err(DecodeError::InvalidValue), | ||
}, | ||
}); | ||
|
@@ -10430,7 +10461,10 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel | |
}, | ||
2 => HTLCUpdateAwaitingACK::FailHTLC { | ||
htlc_id: Readable::read(reader)?, | ||
err_packet: Readable::read(reader)?, | ||
err_packet: OnionErrorPacket { | ||
data: Readable::read(reader)?, | ||
attribution_data: None, | ||
}, | ||
}, | ||
_ => return Err(DecodeError::InvalidValue), | ||
}); | ||
|
@@ -10573,6 +10607,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel | |
let mut pending_outbound_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None; | ||
let mut holding_cell_blinding_points_opt: Option<Vec<Option<PublicKey>>> = None; | ||
|
||
let mut removed_htlc_failure_attribution_data: Option<Vec<Option<[u8; ATTRIBUTION_DATA_LEN]>>> = None; | ||
let mut holding_cell_failure_attribution_data: Option<Vec<(u32, [u8; ATTRIBUTION_DATA_LEN])>> = None; | ||
|
||
let mut malformed_htlcs: Option<Vec<(u64, u16, [u8; 32])>> = None; | ||
let mut monitor_pending_update_adds: Option<Vec<msgs::UpdateAddHTLC>> = None; | ||
|
||
|
@@ -10615,6 +10652,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel | |
(49, local_initiated_shutdown, option), | ||
(51, is_manual_broadcast, option), | ||
(53, funding_tx_broadcast_safe_event_emitted, option), | ||
(55, removed_htlc_failure_attribution_data, optional_vec), | ||
(57, holding_cell_failure_attribution_data, optional_vec), | ||
}); | ||
|
||
let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); | ||
|
@@ -10696,6 +10735,38 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, &'c Channel | |
if iter.next().is_some() { return Err(DecodeError::InvalidValue) } | ||
} | ||
|
||
if let Some(attribution_datas) = removed_htlc_failure_attribution_data { | ||
let mut removed_htlc_relay_failures = | ||
pending_inbound_htlcs.iter_mut().filter_map(|status| | ||
if let InboundHTLCState::LocalRemoved(ref mut reason) = &mut status.state { | ||
if let InboundHTLCRemovalReason::FailRelay(ref mut packet) = reason { | ||
Some(&mut packet.attribution_data) | ||
} else { | ||
None | ||
} | ||
} else { | ||
None | ||
} | ||
); | ||
|
||
for attribution_data in attribution_datas { | ||
*removed_htlc_relay_failures.next().ok_or(DecodeError::InvalidValue)? = attribution_data; | ||
} | ||
if removed_htlc_relay_failures.next().is_some() { return Err(DecodeError::InvalidValue); } | ||
} | ||
|
||
if let Some(attribution_datas) = holding_cell_failure_attribution_data { | ||
for (i, attribution_data) in attribution_datas { | ||
let update = holding_cell_htlc_updates.get_mut(i as usize).ok_or(DecodeError::InvalidValue)?; | ||
|
||
if let HTLCUpdateAwaitingACK::FailHTLC { htlc_id: _, ref mut err_packet } = update { | ||
err_packet.attribution_data = Some(attribution_data); | ||
} else { | ||
return Err(DecodeError::InvalidValue); | ||
} | ||
} | ||
} | ||
|
||
if let Some(malformed_htlcs) = malformed_htlcs { | ||
for (malformed_htlc_id, failure_code, sha256_of_onion) in malformed_htlcs { | ||
let htlc_idx = holding_cell_htlc_updates.iter().position(|htlc| { | ||
|
@@ -10893,7 +10964,7 @@ mod tests { | |
use bitcoin::transaction::{Transaction, TxOut, Version}; | ||
use bitcoin::opcodes; | ||
use bitcoin::network::Network; | ||
use crate::ln::onion_utils::INVALID_ONION_BLINDING; | ||
use crate::ln::onion_utils::{ATTRIBUTION_DATA_LEN, INVALID_ONION_BLINDING}; | ||
use crate::types::payment::{PaymentHash, PaymentPreimage}; | ||
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint}; | ||
use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; | ||
|
@@ -11529,7 +11600,7 @@ mod tests { | |
htlc_id: 0, | ||
}; | ||
let dummy_holding_cell_failed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailHTLC { | ||
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] } | ||
htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42], attribution_data: Some([1; ATTRIBUTION_DATA_LEN]) } | ||
}; | ||
let dummy_holding_cell_malformed_htlc = |htlc_id| HTLCUpdateAwaitingACK::FailMalformedHTLC { | ||
htlc_id, failure_code: INVALID_ONION_BLINDING, sha256_of_onion: [0; 32], | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pending htlc initiated the removal (send update fail back upstream) but no response yet from peer to lock into their commit tx. See
InboundHTLCState
docs.This code needs to work because of resends. Fetch fail message but don't deliver. Then check if after restart message is identical. Other node just sends chan reestablish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage added in #3645