use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
-use crate::onion_message::{Destination, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
+use crate::onion_message::{Destination, MessageRouter, OffersMessage, OffersMessageHandler, PendingOnionMessage, new_pending_onion_message};
use crate::sign::{EntropySource, KeysManager, NodeSigner, Recipient, SignerProvider};
use crate::sign::ecdsa::WriteableEcdsaChannelSigner;
use crate::util::config::{UserConfig, ChannelConfig, ChannelConfigUpdate};
/// Information about where a received HTLC('s onion) has indicated the HTLC should go.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
+#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum PendingHTLCRouting {
/// An HTLC which should be forwarded on to another node.
Forward {
/// [`Event::PaymentClaimable::onion_fields`] as
/// [`RecipientOnionFields::custom_tlvs`].
custom_tlvs: Vec<(u64, Vec<u8>)>,
+ /// Set if this HTLC is the final hop in a multi-hop blinded path.
+ requires_blinded_error: bool,
},
/// The onion indicates that this is for payment to us but which contains the preimage for
/// claiming included, and is unrelated to any invoice we'd previously generated (aka a
}
/// Information used to forward or fail this HTLC that is being forwarded within a blinded path.
-#[derive(Clone, Copy, Hash, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct BlindedForward {
/// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound
/// onion payload if we're the introduction node. Useful for calculating the next hop's
impl PendingHTLCRouting {
// Used to override the onion failure code and data if the HTLC is blinded.
fn blinded_failure(&self) -> Option<BlindedFailure> {
- // TODO: needs update when we support receiving to multi-hop blinded paths
- if let Self::Forward { blinded: Some(_), .. } = self {
- Some(BlindedFailure::FromIntroductionNode)
- } else {
- None
+ // TODO: needs update when we support forwarding blinded HTLCs as non-intro node
+ match self {
+ Self::Forward { blinded: Some(_), .. } => Some(BlindedFailure::FromIntroductionNode),
+ Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode),
+ _ => None,
}
}
}
/// Information about an incoming HTLC, including the [`PendingHTLCRouting`] describing where it
/// should go next.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
+#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct PendingHTLCInfo {
/// Further routing details based on whether the HTLC is being forwarded or received.
pub routing: PendingHTLCRouting,
Fail(HTLCFailureMsg),
}
+#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
pub(super) struct PendingAddHTLCInfo {
pub(super) forward_info: PendingHTLCInfo,
prev_user_channel_id: u128,
}
+#[cfg_attr(test, derive(Clone, Debug, PartialEq))]
pub(super) enum HTLCForwardInfo {
AddHTLC(PendingAddHTLCInfo),
FailHTLC {
htlc_id: u64,
err_packet: msgs::OnionErrorPacket,
},
+ FailMalformedHTLC {
+ htlc_id: u64,
+ failure_code: u16,
+ sha256_of_onion: [u8; 32],
+ },
}
// Used for failing blinded HTLCs backwards correctly.
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum BlindedFailure {
FromIntroductionNode,
- // Another variant will be added here for non-intro nodes.
+ FromBlindedNode,
}
/// Tracks the inbound corresponding to an outbound HTLC
msg, &self.node_signer, &self.logger, &self.secp_ctx
)?;
- let is_blinded = match next_hop {
+ let is_intro_node_forward = match next_hop {
onion_utils::Hop::Forward {
+ // TODO: update this when we support blinded forwarding as non-intro node
next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, ..
} => true,
- _ => false, // TODO: update this when we support receiving to multi-hop blinded paths
+ _ => false,
};
macro_rules! return_err {
WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id)),
"Failed to accept/forward incoming HTLC: {}", $msg
);
- let (err_code, err_data) = if is_blinded {
+ // If `msg.blinding_point` is set, we must always fail with malformed.
+ if msg.blinding_point.is_some() {
+ return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC {
+ channel_id: msg.channel_id,
+ htlc_id: msg.htlc_id,
+ sha256_of_onion: [0; 32],
+ failure_code: INVALID_ONION_BLINDING,
+ }));
+ }
+
+ let (err_code, err_data) = if is_intro_node_forward {
(INVALID_ONION_BLINDING, &[0; 32][..])
} else { ($err_code, $data) };
return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
{
let logger = WithContext::from(&self.logger, Some(*counterparty_node_id), Some(msg.channel_id));
log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg);
+ if msg.blinding_point.is_some() {
+ return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(
+ msgs::UpdateFailMalformedHTLC {
+ channel_id: msg.channel_id,
+ htlc_id: msg.htlc_id,
+ sha256_of_onion: [0; 32],
+ failure_code: INVALID_ONION_BLINDING,
+ }
+ ))
+ }
return PendingHTLCStatus::Fail(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC {
channel_id: msg.channel_id,
htlc_id: msg.htlc_id,
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
let next_hop = match onion_utils::decode_next_payment_hop(
phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
- payment_hash, &self.node_signer
+ payment_hash, None, &self.node_signer
) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
}
},
- HTLCForwardInfo::FailHTLC { .. } => {
+ HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => {
// Channel went away before we could fail it. This implies
// the channel is now on chain and our counterparty is
// trying to broadcast the HTLC-Timeout, but that's their
continue;
}
},
+ HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
+ log_trace!(self.logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
+ if let Err(e) = chan.queue_fail_malformed_htlc(htlc_id, failure_code, sha256_of_onion, &self.logger) {
+ if let ChannelError::Ignore(msg) = e {
+ log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
+ } else {
+ panic!("Stated return value requirements in queue_fail_malformed_htlc() were not met");
+ }
+ // fail-backs are best-effort, we probably already have one
+ // pending, and if not that's OK, if not, the channel is on
+ // the chain and sending the HTLC-Timeout is their problem.
+ continue;
+ }
+ },
}
}
} else {
}) => {
let blinded_failure = routing.blinded_failure();
let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing {
- PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => {
+ PendingHTLCRouting::Receive {
+ payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret,
+ custom_tlvs, requires_blinded_error: _
+ } => {
let _legacy_hop_data = Some(payment_data.clone());
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
payment_metadata, custom_tlvs };
htlc_id: $htlc.prev_hop.htlc_id,
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
phantom_shared_secret,
- blinded_failure: None,
+ blinded_failure,
}), payment_hash,
HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data),
HTLCDestination::FailedPayment { payment_hash: $payment_hash },
},
};
},
- HTLCForwardInfo::FailHTLC { .. } => {
+ HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => {
panic!("Got pending fail of our own HTLC");
}
}
"Failing {}HTLC with payment_hash {} backwards from us: {:?}",
if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error
);
- let err_packet = match blinded_failure {
+ let failure = match blinded_failure {
Some(BlindedFailure::FromIntroductionNode) => {
let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]);
- blinded_onion_error.get_encrypted_failure_packet(
+ let err_packet = blinded_onion_error.get_encrypted_failure_packet(
incoming_packet_shared_secret, phantom_shared_secret
- )
+ );
+ HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }
+ },
+ Some(BlindedFailure::FromBlindedNode) => {
+ HTLCForwardInfo::FailMalformedHTLC {
+ htlc_id: *htlc_id,
+ failure_code: INVALID_ONION_BLINDING,
+ sha256_of_onion: [0; 32]
+ }
},
None => {
- onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret)
+ let err_packet = onion_error.get_encrypted_failure_packet(
+ incoming_packet_shared_secret, phantom_shared_secret
+ );
+ HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }
}
};
}
match forward_htlcs.entry(*short_channel_id) {
hash_map::Entry::Occupied(mut entry) => {
- entry.get_mut().push(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet });
+ entry.get_mut().push(failure);
},
hash_map::Entry::Vacant(entry) => {
- entry.insert(vec!(HTLCForwardInfo::FailHTLC { htlc_id: *htlc_id, err_packet }));
+ entry.insert(vec!(failure));
}
}
mem::drop(forward_htlcs);
Err(e) => PendingHTLCStatus::Fail(e)
};
let create_pending_htlc_status = |chan: &Channel<SP>, pending_forward_info: PendingHTLCStatus, error_code: u16| {
+ if msg.blinding_point.is_some() {
+ return PendingHTLCStatus::Fail(HTLCFailureMsg::Malformed(
+ msgs::UpdateFailMalformedHTLC {
+ channel_id: msg.channel_id,
+ htlc_id: msg.htlc_id,
+ sha256_of_onion: [0; 32],
+ failure_code: INVALID_ONION_BLINDING,
+ }
+ ))
+ }
// If the update_add is completely bogus, the call will Err and we will close,
// but if we've sent a shutdown and they haven't acknowledged it yet, we just
// want to reject the new HTLC and fail it backwards instead of forwarding.
/// attempted in every channel, or in the specifically provided channel.
///
/// [`ChannelSigner`]: crate::sign::ChannelSigner
- #[cfg(test)] // This is only implemented for one signer method, and should be private until we
- // actually finish implementing it fully.
+ #[cfg(async_signing)]
pub fn signer_unblocked(&self, channel_opt: Option<(PublicKey, ChannelId)>) {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
///
/// # Privacy
///
- /// Uses a one-hop [`BlindedPath`] for the offer with [`ChannelManager::get_our_node_id`] as the
- /// introduction node and a derived signing pubkey for recipient privacy. As such, currently,
- /// the node must be announced. Otherwise, there is no way to find a path to the introduction
- /// node in order to send the [`InvoiceRequest`].
+ /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
+ /// However, if one is not found, uses a one-hop [`BlindedPath`] with
+ /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
+ /// the node must be announced, otherwise, there is no way to find a path to the introduction in
+ /// order to send the [`InvoiceRequest`].
+ ///
+ /// Also, uses a derived signing pubkey in the offer for recipient privacy.
///
/// # Limitations
///
/// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s
/// reply path.
///
+ /// # Errors
+ ///
+ /// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer.
+ ///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Offer`]: crate::offers::offer::Offer
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub fn create_offer_builder(
&self, description: String
- ) -> OfferBuilder<DerivedMetadata, secp256k1::All> {
+ ) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> {
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
- let path = self.create_one_hop_blinded_path();
- OfferBuilder::deriving_signing_pubkey(description, node_id, expanded_key, entropy, secp_ctx)
+ let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let builder = OfferBuilder::deriving_signing_pubkey(
+ description, node_id, expanded_key, entropy, secp_ctx
+ )
.chain_hash(self.chain_hash)
- .path(path)
+ .path(path);
+
+ Ok(builder)
}
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
///
/// # Privacy
///
- /// Uses a one-hop [`BlindedPath`] for the refund with [`ChannelManager::get_our_node_id`] as
- /// the introduction node and a derived payer id for payer privacy. As such, currently, the
- /// node must be announced. Otherwise, there is no way to find a path to the introduction node
- /// in order to send the [`Bolt12Invoice`].
+ /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
+ /// However, if one is not found, uses a one-hop [`BlindedPath`] with
+ /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
+ /// the node must be announced, otherwise, there is no way to find a path to the introduction in
+ /// order to send the [`Bolt12Invoice`].
+ ///
+ /// Also, uses a derived payer id in the refund for payer privacy.
///
/// # Limitations
///
///
/// # Errors
///
- /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
- /// or if `amount_msats` is invalid.
+ /// Errors if:
+ /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
+ /// - `amount_msats` is invalid, or
+ /// - the parameterized [`Router`] is unable to create a blinded path for the refund.
///
/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
///
/// [`Refund`]: crate::offers::refund::Refund
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
/// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
+ /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
pub fn create_refund_builder(
&self, description: String, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
- let path = self.create_one_hop_blinded_path();
+ let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let builder = RefundBuilder::deriving_payer_id(
description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
)?
///
/// # Errors
///
- /// Errors if a duplicate `payment_id` is provided given the caveats in the aforementioned link
- /// or if the provided parameters are invalid for the offer.
+ /// Errors if:
+ /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
+ /// - the provided parameters are invalid for the offer,
+ /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
+ /// request.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
None => builder,
Some(payer_note) => builder.payer_note(payer_note),
};
-
let invoice_request = builder.build_and_sign()?;
- let reply_path = self.create_one_hop_blinded_path();
+ let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
let expiration = StaleExpiration::TimerTicks(1);
self.pending_outbound_payments
payment_paths, payment_hash, created_at, expanded_key, entropy
)?;
let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
- let reply_path = self.create_one_hop_blinded_path();
+ let reply_path = self.create_blinded_path()
+ .map_err(|_| Bolt12SemanticError::MissingPaths)?;
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if refund.paths().is_empty() {
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
}
- /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
- /// node.
- fn create_one_hop_blinded_path(&self) -> BlindedPath {
+ /// Creates a blinded path by delegating to [`MessageRouter::create_blinded_paths`].
+ ///
+ /// Errors if the `MessageRouter` errors or returns an empty `Vec`.
+ fn create_blinded_path(&self) -> Result<BlindedPath, ()> {
+ let recipient = self.get_our_node_id();
let entropy_source = self.entropy_source.deref();
let secp_ctx = &self.secp_ctx;
- BlindedPath::one_hop_for_message(self.get_our_node_id(), entropy_source, secp_ctx).unwrap()
+
+ let peers = self.per_peer_state.read().unwrap()
+ .iter()
+ .filter(|(_, peer)| peer.lock().unwrap().latest_features.supports_onion_messages())
+ .map(|(node_id, _)| *node_id)
+ .collect::<Vec<_>>();
+
+ self.router
+ .create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
+ .and_then(|paths| paths.into_iter().next().ok_or(()))
}
- /// Creates a one-hop blinded path with [`ChannelManager::get_our_node_id`] as the introduction
- /// node.
+ /// Creates a one-hop blinded payment path with [`ChannelManager::get_our_node_id`] as the
+ /// introduction node.
fn create_one_hop_blinded_payment_path(
&self, payment_secret: PaymentSecret
) -> (BlindedPayInfo, BlindedPath) {
let secp_ctx = &self.secp_ctx;
let payee_node_id = self.get_our_node_id();
- let max_cltv_expiry = self.best_block.read().unwrap().height() + LATENCY_GRACE_PERIOD_BLOCKS;
+ let max_cltv_expiry = self.best_block.read().unwrap().height() + CLTV_FAR_FAR_AWAY
+ + LATENCY_GRACE_PERIOD_BLOCKS;
let payee_tlvs = ReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints {
(2, incoming_cltv_expiry, required),
(3, payment_metadata, option),
(5, custom_tlvs, optional_vec),
+ (7, requires_blinded_error, (default_value, false)),
},
(2, ReceiveKeysend) => {
(0, payment_preimage, required),
);
impl_writeable_tlv_based_enum!(BlindedFailure,
- (0, FromIntroductionNode) => {}, ;
+ (0, FromIntroductionNode) => {},
+ (2, FromBlindedNode) => {}, ;
);
impl_writeable_tlv_based!(HTLCPreviousHopData, {
(6, prev_funding_outpoint, required),
});
-impl_writeable_tlv_based_enum!(HTLCForwardInfo,
- (1, FailHTLC) => {
- (0, htlc_id, required),
- (2, err_packet, required),
- };
- (0, AddHTLC)
-);
+impl Writeable for HTLCForwardInfo {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ const FAIL_HTLC_VARIANT_ID: u8 = 1;
+ match self {
+ Self::AddHTLC(info) => {
+ 0u8.write(w)?;
+ info.write(w)?;
+ },
+ Self::FailHTLC { htlc_id, err_packet } => {
+ FAIL_HTLC_VARIANT_ID.write(w)?;
+ write_tlv_fields!(w, {
+ (0, htlc_id, required),
+ (2, err_packet, required),
+ });
+ },
+ Self::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
+ // Since this variant was added in 0.0.119, write this as `::FailHTLC` with an empty error
+ // packet so older versions have something to fail back with, but serialize the real data as
+ // optional TLVs for the benefit of newer versions.
+ FAIL_HTLC_VARIANT_ID.write(w)?;
+ let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
+ write_tlv_fields!(w, {
+ (0, htlc_id, required),
+ (1, failure_code, required),
+ (2, dummy_err_packet, required),
+ (3, sha256_of_onion, required),
+ });
+ },
+ }
+ Ok(())
+ }
+}
+
+impl Readable for HTLCForwardInfo {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let id: u8 = Readable::read(r)?;
+ Ok(match id {
+ 0 => Self::AddHTLC(Readable::read(r)?),
+ 1 => {
+ _init_and_read_len_prefixed_tlv_fields!(r, {
+ (0, htlc_id, required),
+ (1, malformed_htlc_failure_code, option),
+ (2, err_packet, required),
+ (3, sha256_of_onion, option),
+ });
+ if let Some(failure_code) = malformed_htlc_failure_code {
+ Self::FailMalformedHTLC {
+ htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
+ failure_code,
+ sha256_of_onion: sha256_of_onion.ok_or(DecodeError::InvalidValue)?,
+ }
+ } else {
+ Self::FailHTLC {
+ htlc_id: _init_tlv_based_struct_field!(htlc_id, required),
+ err_packet: _init_tlv_based_struct_field!(err_packet, required),
+ }
+ }
+ },
+ _ => return Err(DecodeError::InvalidValue),
+ })
+ }
+}
impl_writeable_tlv_based!(PendingInboundPayment, {
(0, payment_secret, required),
use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use crate::ln::ChannelId;
- use crate::ln::channelmanager::{create_recv_pending_htlc_info, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
+ use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId};
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::{self, ErrorAction};
use crate::ln::msgs::ChannelMessageHandler;
+ use crate::prelude::*;
use crate::routing::router::{PaymentParameters, RouteParameters, find_route};
use crate::util::errors::APIError;
+ use crate::util::ser::Writeable;
use crate::util::test_utils;
use crate::util::config::{ChannelConfig, ChannelConfigUpdate};
use crate::sign::EntropySource;
check_spends!(txn[0], funding_tx);
}
}
+
+ #[test]
+ fn test_malformed_forward_htlcs_ser() {
+ // Ensure that `HTLCForwardInfo::FailMalformedHTLC`s are (de)serialized properly.
+ let chanmon_cfg = create_chanmon_cfgs(1);
+ let node_cfg = create_node_cfgs(1, &chanmon_cfg);
+ let persister;
+ let chain_monitor;
+ let chanmgrs = create_node_chanmgrs(1, &node_cfg, &[None]);
+ let deserialized_chanmgr;
+ let mut nodes = create_network(1, &node_cfg, &chanmgrs);
+
+ let dummy_failed_htlc = |htlc_id| {
+ HTLCForwardInfo::FailHTLC { htlc_id, err_packet: msgs::OnionErrorPacket { data: vec![42] }, }
+ };
+ let dummy_malformed_htlc = |htlc_id| {
+ HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code: 0x4000, sha256_of_onion: [0; 32] }
+ };
+
+ let dummy_htlcs_1: Vec<HTLCForwardInfo> = (1..10).map(|htlc_id| {
+ if htlc_id % 2 == 0 {
+ dummy_failed_htlc(htlc_id)
+ } else {
+ dummy_malformed_htlc(htlc_id)
+ }
+ }).collect();
+
+ let dummy_htlcs_2: Vec<HTLCForwardInfo> = (1..10).map(|htlc_id| {
+ if htlc_id % 2 == 1 {
+ dummy_failed_htlc(htlc_id)
+ } else {
+ dummy_malformed_htlc(htlc_id)
+ }
+ }).collect();
+
+
+ let (scid_1, scid_2) = (42, 43);
+ let mut forward_htlcs = HashMap::new();
+ forward_htlcs.insert(scid_1, dummy_htlcs_1.clone());
+ forward_htlcs.insert(scid_2, dummy_htlcs_2.clone());
+
+ let mut chanmgr_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
+ *chanmgr_fwd_htlcs = forward_htlcs.clone();
+ core::mem::drop(chanmgr_fwd_htlcs);
+
+ reload_node!(nodes[0], nodes[0].node.encode(), &[], persister, chain_monitor, deserialized_chanmgr);
+
+ let mut deserialized_fwd_htlcs = nodes[0].node.forward_htlcs.lock().unwrap();
+ for scid in [scid_1, scid_2].iter() {
+ let deserialized_htlcs = deserialized_fwd_htlcs.remove(scid).unwrap();
+ assert_eq!(forward_htlcs.remove(scid).unwrap(), deserialized_htlcs);
+ }
+ assert!(deserialized_fwd_htlcs.is_empty());
+ core::mem::drop(deserialized_fwd_htlcs);
+
+ expect_pending_htlcs_forwardable!(nodes[0]);
+ }
}
#[cfg(ldk_bench)]