next_outbound_htlc_limit_msat: capacity.saturating_mul(1000),
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
+ config: None,
});
}
Some(&first_hops_vec[..])
bitcoin_hashes = { version = "0.10", default-features = false }
hashbrown = { version = "0.11", optional = true }
core2 = { version = "0.3.0", default-features = false, optional = true }
+serde = { version = "1.0.118", optional = true }
[dev-dependencies]
lightning = { version = "0.0.108", path = "../lightning", default-features = false, features = ["_test_utils"] }
hex = "0.4"
+serde_json = { version = "1"}
extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate core;
+#[cfg(feature = "serde")]
+extern crate serde;
#[cfg(feature = "std")]
use std::time::SystemTime;
use core::time::Duration;
use core::str;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Deserializer,Serialize, Serializer, de::Error};
+
mod de;
mod ser;
mod tb;
}
}
+#[cfg(feature = "serde")]
+impl Serialize for Invoice {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
+ serializer.serialize_str(self.to_string().as_str())
+ }
+}
+#[cfg(feature = "serde")]
+impl<'de> Deserialize<'de> for Invoice {
+ fn deserialize<D>(deserializer: D) -> Result<Invoice, D::Error> where D: Deserializer<'de> {
+ let bolt11 = String::deserialize(deserializer)?
+ .parse::<Invoice>()
+ .map_err(|e| D::Error::custom(format!("{:?}", e)))?;
+
+ Ok(bolt11)
+ }
+}
+
#[cfg(test)]
mod test {
use bitcoin_hashes::hex::FromHex;
assert!(invoice.would_expire(Duration::from_secs(1234567 + DEFAULT_EXPIRY_TIME + 1)));
}
+
+ #[cfg(feature = "serde")]
+ #[test]
+ fn test_serde() {
+ let invoice_str = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
+ h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
+ 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
+ h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
+ j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
+ ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
+ guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
+ ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
+ p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
+ 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
+ j5r6drg6k6zcqj0fcwg";
+ let invoice = invoice_str.parse::<super::Invoice>().unwrap();
+ let serialized_invoice = serde_json::to_string(&invoice).unwrap();
+ let deserialized_invoice: super::Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
+ assert_eq!(invoice, deserialized_invoice);
+ assert_eq!(invoice_str, deserialized_invoice.to_string().as_str());
+ assert_eq!(invoice_str, serialized_invoice.as_str().trim_matches('\"'));
+ }
}
writer.write_all(&txid[..])?;
writer.write_all(&byte_utils::be64_to_array(htlc_infos.len() as u64))?;
for &(ref htlc_output, ref htlc_source) in htlc_infos.iter() {
+ debug_assert!(htlc_source.is_none() || Some(**txid) == self.current_counterparty_commitment_txid
+ || Some(**txid) == self.prev_counterparty_commitment_txid,
+ "HTLC Sources for all revoked commitment transactions should be none!");
serialize_htlc_in_commitment!(htlc_output);
htlc_source.as_ref().map(|b| b.as_ref()).write(writer)?;
}
/// as long as we examine both the current counterparty commitment transaction and, if it hasn't
/// been revoked yet, the previous one, we we will never "forget" to resolve an HTLC.
macro_rules! fail_unbroadcast_htlcs {
- ($self: expr, $commitment_tx_type: expr, $commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
+ ($self: expr, $commitment_tx_type: expr, $commitment_txid_confirmed: expr,
+ $commitment_tx_conf_height: expr, $confirmed_htlcs_list: expr, $logger: expr) => { {
macro_rules! check_htlc_fails {
($txid: expr, $commitment_tx: expr) => {
if let Some(ref latest_outpoints) = $self.counterparty_claimable_outpoints.get($txid) {
// cannot currently change after channel initialization, so we don't
// need to here.
let confirmed_htlcs_iter: &mut Iterator<Item = (&HTLCOutputInCommitment, Option<&HTLCSource>)> = &mut $confirmed_htlcs_list;
+
let mut matched_htlc = false;
for (ref broadcast_htlc, ref broadcast_source) in confirmed_htlcs_iter {
- if broadcast_htlc.transaction_output_index.is_some() && Some(&**source) == *broadcast_source {
+ if broadcast_htlc.transaction_output_index.is_some() &&
+ (Some(&**source) == *broadcast_source ||
+ (broadcast_source.is_none() &&
+ broadcast_htlc.payment_hash == htlc.payment_hash &&
+ broadcast_htlc.amount_msat == htlc.amount_msat)) {
matched_htlc = true;
break;
}
}
});
let entry = OnchainEventEntry {
- txid: *$txid,
+ txid: $commitment_txid_confirmed,
height: $commitment_tx_conf_height,
event: OnchainEvent::HTLCUpdate {
source: (**source).clone(),
commitment_tx_output_idx: None,
},
};
- log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction, waiting for confirmation (at height {})",
- log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type, entry.confirmation_threshold());
+ log_trace!($logger, "Failing HTLC with payment_hash {} from {} counterparty commitment tx due to broadcast of {} commitment transaction {}, waiting for confirmation (at height {})",
+ log_bytes!(htlc.payment_hash.0), $commitment_tx, $commitment_tx_type,
+ $commitment_txid_confirmed, entry.confirmation_threshold());
$self.onchain_events_awaiting_threshold_conf.push(entry);
}
}
}
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
- fail_unbroadcast_htlcs!(self, "revoked counterparty", height, [].iter().map(|a| *a), logger);
+ if let Some(per_commitment_data) = per_commitment_option {
+ fail_unbroadcast_htlcs!(self, "revoked_counterparty", commitment_txid, height,
+ per_commitment_data.iter().map(|(htlc, htlc_source)|
+ (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref()))
+ ), logger);
+ } else {
+ debug_assert!(false, "We should have per-commitment option for any recognized old commitment txn");
+ fail_unbroadcast_htlcs!(self, "revoked counterparty", commitment_txid, height,
+ [].iter().map(|reference| *reference), logger);
+ }
}
} else if let Some(per_commitment_data) = per_commitment_option {
// While this isn't useful yet, there is a potential race where if a counterparty
self.counterparty_commitment_txn_on_chain.insert(commitment_txid, commitment_number);
log_info!(logger, "Got broadcast of non-revoked counterparty commitment transaction {}", commitment_txid);
- fail_unbroadcast_htlcs!(self, "counterparty", height, per_commitment_data.iter().map(|(a, b)| (a, b.as_ref().map(|b| b.as_ref()))), logger);
+ fail_unbroadcast_htlcs!(self, "counterparty", commitment_txid, height,
+ per_commitment_data.iter().map(|(htlc, htlc_source)|
+ (htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref()))
+ ), logger);
let htlc_claim_reqs = self.get_counterparty_htlc_output_claim_reqs(commitment_number, commitment_txid, Some(tx));
for req in htlc_claim_reqs {
let res = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height);
let mut to_watch = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, tx);
append_onchain_update!(res, to_watch);
- fail_unbroadcast_htlcs!(self, "latest holder", height, self.current_holder_commitment_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
+ fail_unbroadcast_htlcs!(self, "latest holder", commitment_txid, height,
+ self.current_holder_commitment_tx.htlc_outputs.iter()
+ .map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())), logger);
} else if let &Some(ref holder_tx) = &self.prev_holder_signed_commitment_tx {
if holder_tx.txid == commitment_txid {
is_holder_tx = true;
let res = self.get_broadcasted_holder_claims(holder_tx, height);
let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_tx, tx);
append_onchain_update!(res, to_watch);
- fail_unbroadcast_htlcs!(self, "previous holder", height, holder_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
+ fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, height,
+ holder_tx.htlc_outputs.iter().map(|(htlc, _, htlc_source)| (htlc, htlc_source.as_ref())),
+ logger);
}
}
matured_htlcs.push(source.clone());
}
- log_debug!(logger, "HTLC {} failure update has got enough confirmations to be passed upstream", log_bytes!(payment_hash.0));
+ log_debug!(logger, "HTLC {} failure update in {} has got enough confirmations to be passed upstream",
+ log_bytes!(payment_hash.0), entry.txid);
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
payment_hash,
payment_preimage: None,
F::Target: FeeEstimator,
L::Target: Logger,
{
- self.onchain_events_awaiting_threshold_conf.retain(|ref entry| entry.txid != *txid);
+ self.onchain_events_awaiting_threshold_conf.retain(|ref entry| if entry.txid == *txid {
+ log_info!(logger, "Removing onchain event with txid {}", txid);
+ false
+ } else { true });
self.onchain_tx_handler.transaction_unconfirmed(txid, broadcaster, fee_estimator, logger);
}
use util::ser::{Readable, ReadableArgs, Writeable, Writer, VecWriter};
use util::logger::Logger;
use util::errors::APIError;
-use util::config::{UserConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
+use util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits};
use util::scid_utils::scid_from_parts;
use io;
/// transaction (not counting the value of the HTLCs themselves).
pub(crate) const MIN_AFFORDABLE_HTLC_COUNT: usize = 4;
+/// When a [`Channel`] has its [`ChannelConfig`] updated, its existing one is stashed for up to this
+/// number of ticks to allow forwarding HTLCs by nodes that have yet to receive the new
+/// ChannelUpdate prompted by the config update. This value was determined as follows:
+///
+/// * The expected interval between ticks (1 minute).
+/// * The average convergence delay of updates across the network, i.e., ~300 seconds on average
+/// for a node to see an update as seen on `<https://arxiv.org/pdf/2205.12737.pdf>`.
+/// * `EXPIRE_PREV_CONFIG_TICKS` = convergence_delay / tick_interval
+pub(crate) const EXPIRE_PREV_CONFIG_TICKS: usize = 5;
+
// TODO: We should refactor this to be an Inbound/OutboundChannel until initial setup handshaking
// has been completed, and then turn into a Channel to get compiler-time enforcement of things like
// calling channel_id() before we're set up or things like get_outbound_funding_signed on an
// Holder designates channel data owned for the benefice of the user client.
// Counterparty designates channel data owned by the another channel participant entity.
pub(super) struct Channel<Signer: Sign> {
- #[cfg(any(test, feature = "_test_utils"))]
- pub(crate) config: LegacyChannelConfig,
- #[cfg(not(any(test, feature = "_test_utils")))]
config: LegacyChannelConfig,
+ // Track the previous `ChannelConfig` so that we can continue forwarding HTLCs that were
+ // constructed using it. The second element in the tuple corresponds to the number of ticks that
+ // have elapsed since the update occurred.
+ prev_config: Option<(ChannelConfig, usize)>,
+
inbound_handshake_limits_override: Option<ChannelHandshakeLimits>,
user_id: u64,
commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey,
},
+ prev_config: None,
+
inbound_handshake_limits_override: Some(config.channel_handshake_limits.clone()),
channel_id: keys_provider.get_secure_random_bytes(),
commit_upfront_shutdown_pubkey: config.channel_handshake_config.commit_upfront_shutdown_pubkey,
},
+ prev_config: None,
+
inbound_handshake_limits_override: None,
channel_id: msg.temporary_channel_id,
self.config.options.max_dust_htlc_exposure_msat
}
+ /// Returns the previous [`ChannelConfig`] applied to this channel, if any.
+ pub fn prev_config(&self) -> Option<ChannelConfig> {
+ self.prev_config.map(|prev_config| prev_config.0)
+ }
+
+ /// Tracks the number of ticks elapsed since the previous [`ChannelConfig`] was updated. Once
+ /// [`EXPIRE_PREV_CONFIG_TICKS`] is reached, the previous config is considered expired and will
+ /// no longer be considered when forwarding HTLCs.
+ pub fn maybe_expire_prev_config(&mut self) {
+ if self.prev_config.is_none() {
+ return;
+ }
+ let prev_config = self.prev_config.as_mut().unwrap();
+ prev_config.1 += 1;
+ if prev_config.1 == EXPIRE_PREV_CONFIG_TICKS {
+ self.prev_config = None;
+ }
+ }
+
+ /// Returns the current [`ChannelConfig`] applied to the channel.
+ pub fn config(&self) -> ChannelConfig {
+ self.config.options
+ }
+
+ /// Updates the channel's config. A bool is returned indicating whether the config update
+ /// applied resulted in a new ChannelUpdate message.
+ pub fn update_config(&mut self, config: &ChannelConfig) -> bool {
+ let did_channel_update =
+ self.config.options.forwarding_fee_proportional_millionths != config.forwarding_fee_proportional_millionths ||
+ self.config.options.forwarding_fee_base_msat != config.forwarding_fee_base_msat ||
+ self.config.options.cltv_expiry_delta != config.cltv_expiry_delta;
+ if did_channel_update {
+ self.prev_config = Some((self.config.options, 0));
+ // Update the counter, which backs the ChannelUpdate timestamp, to allow the relay
+ // policy change to propagate throughout the network.
+ self.update_time_counter += 1;
+ }
+ self.config.options = *config;
+ did_channel_update
+ }
+
+ fn internal_htlc_satisfies_config(
+ &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, config: &ChannelConfig,
+ ) -> Result<(), (&'static str, u16)> {
+ let fee = amt_to_forward.checked_mul(config.forwarding_fee_proportional_millionths as u64)
+ .and_then(|prop_fee| (prop_fee / 1000000).checked_add(config.forwarding_fee_base_msat as u64));
+ if fee.is_none() || htlc.amount_msat < fee.unwrap() ||
+ (htlc.amount_msat - fee.unwrap()) < amt_to_forward {
+ return Err((
+ "Prior hop has deviated from specified fees parameters or origin node has obsolete ones",
+ 0x1000 | 12, // fee_insufficient
+ ));
+ }
+ if (htlc.cltv_expiry as u64) < outgoing_cltv_value as u64 + config.cltv_expiry_delta as u64 {
+ return Err((
+ "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+ 0x1000 | 13, // incorrect_cltv_expiry
+ ));
+ }
+ Ok(())
+ }
+
+ /// Determines whether the parameters of an incoming HTLC to be forwarded satisfy the channel's
+ /// [`ChannelConfig`]. This first looks at the channel's current [`ChannelConfig`], and if
+ /// unsuccessful, falls back to the previous one if one exists.
+ pub fn htlc_satisfies_config(
+ &self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32,
+ ) -> Result<(), (&'static str, u16)> {
+ self.internal_htlc_satisfies_config(&htlc, amt_to_forward, outgoing_cltv_value, &self.config())
+ .or_else(|err| {
+ if let Some(prev_config) = self.prev_config() {
+ self.internal_htlc_satisfies_config(htlc, amt_to_forward, outgoing_cltv_value, &prev_config)
+ } else {
+ Err(err)
+ }
+ })
+ }
+
pub fn get_feerate(&self) -> u32 {
self.feerate_per_kw
}
config: config.unwrap(),
+ prev_config: None,
+
// Note that we don't care about serializing handshake limits as we only ever serialize
// channel data after the handshake has completed.
inbound_handshake_limits_override: None,
use ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT, OptionalField};
use ln::wire::Encode;
use chain::keysinterface::{Sign, KeysInterface, KeysManager, InMemorySigner, Recipient};
-use util::config::UserConfig;
+use util::config::{UserConfig, ChannelConfig};
use util::events::{EventHandler, EventsProvider, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
use util::{byte_utils, events};
use util::scid_utils::fake_scid;
pub inbound_htlc_minimum_msat: Option<u64>,
/// The largest value HTLC (in msat) we currently will accept, for this channel.
pub inbound_htlc_maximum_msat: Option<u64>,
+ /// Set of configurable parameters that affect channel operation.
+ ///
+ /// This field is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.109.
+ pub config: Option<ChannelConfig>,
}
impl ChannelDetails {
is_usable: channel.is_live(),
is_public: channel.should_announce(),
inbound_htlc_minimum_msat: Some(channel.get_holder_htlc_minimum_msat()),
- inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat()
+ inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat(),
+ config: Some(channel.config()),
});
}
}
},
Some(id) => Some(id.clone()),
};
- let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
+ let chan_update_opt = if let Some(forwarding_id) = forwarding_id_opt {
let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
// Note that the behavior here should be identical to the above block - we
if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
}
- let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
- .and_then(|prop_fee| { (prop_fee / 1000000)
- .checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
- if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
- break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, chan_update_opt));
+ if let Err((err, code)) = chan.htlc_satisfies_config(&msg, *amt_to_forward, *outgoing_cltv_value) {
+ break Some((err, code, chan_update_opt));
+ }
+ chan_update_opt
+ } else {
+ if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + MIN_CLTV_EXPIRY_DELTA as u64 { // incorrect_cltv_expiry
+ break Some((
+ "Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
+ 0x1000 | 13, None,
+ ));
}
- (chan_update_opt, chan.get_cltv_expiry_delta())
- } else { (None, MIN_CLTV_EXPIRY_DELTA) };
+ None
+ };
- if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + forwardee_cltv_expiry_delta as u64 { // incorrect_cltv_expiry
- break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, chan_update_opt));
- }
let cur_height = self.best_block.read().unwrap().height() + 1;
// Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
// but we want to be robust wrt to counterparty packet sanitization (see
}
}
+ /// Atomically updates the [`ChannelConfig`] for the given channels.
+ ///
+ /// Once the updates are applied, each eligible channel (advertised with a known short channel
+ /// ID and a change in [`forwarding_fee_proportional_millionths`], [`forwarding_fee_base_msat`],
+ /// or [`cltv_expiry_delta`]) has a [`BroadcastChannelUpdate`] event message generated
+ /// containing the new [`ChannelUpdate`] message which should be broadcast to the network.
+ ///
+ /// Returns [`ChannelUnavailable`] when a channel is not found or an incorrect
+ /// `counterparty_node_id` is provided.
+ ///
+ /// Returns [`APIMisuseError`] when a [`cltv_expiry_delta`] update is to be applied with a value
+ /// below [`MIN_CLTV_EXPIRY_DELTA`].
+ ///
+ /// If an error is returned, none of the updates should be considered applied.
+ ///
+ /// [`forwarding_fee_proportional_millionths`]: ChannelConfig::forwarding_fee_proportional_millionths
+ /// [`forwarding_fee_base_msat`]: ChannelConfig::forwarding_fee_base_msat
+ /// [`cltv_expiry_delta`]: ChannelConfig::cltv_expiry_delta
+ /// [`BroadcastChannelUpdate`]: events::MessageSendEvent::BroadcastChannelUpdate
+ /// [`ChannelUpdate`]: msgs::ChannelUpdate
+ /// [`ChannelUnavailable`]: APIError::ChannelUnavailable
+ /// [`APIMisuseError`]: APIError::APIMisuseError
+ pub fn update_channel_config(
+ &self, counterparty_node_id: &PublicKey, channel_ids: &[[u8; 32]], config: &ChannelConfig,
+ ) -> Result<(), APIError> {
+ if config.cltv_expiry_delta < MIN_CLTV_EXPIRY_DELTA {
+ return Err(APIError::APIMisuseError {
+ err: format!("The chosen CLTV expiry delta is below the minimum of {}", MIN_CLTV_EXPIRY_DELTA),
+ });
+ }
+
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(
+ &self.total_consistency_lock, &self.persistence_notifier,
+ );
+ {
+ let mut channel_state_lock = self.channel_state.lock().unwrap();
+ let channel_state = &mut *channel_state_lock;
+ for channel_id in channel_ids {
+ let channel_counterparty_node_id = channel_state.by_id.get(channel_id)
+ .ok_or(APIError::ChannelUnavailable {
+ err: format!("Channel with ID {} was not found", log_bytes!(*channel_id)),
+ })?
+ .get_counterparty_node_id();
+ if channel_counterparty_node_id != *counterparty_node_id {
+ return Err(APIError::APIMisuseError {
+ err: "counterparty node id mismatch".to_owned(),
+ });
+ }
+ }
+ for channel_id in channel_ids {
+ let channel = channel_state.by_id.get_mut(channel_id).unwrap();
+ if !channel.update_config(config) {
+ continue;
+ }
+ if let Ok(msg) = self.get_channel_update_for_broadcast(channel) {
+ channel_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate { msg });
+ } else if let Ok(msg) = self.get_channel_update_for_unicast(channel) {
+ channel_state.pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
+ node_id: channel.get_counterparty_node_id(),
+ msg,
+ });
+ }
+ }
+ }
+ Ok(())
+ }
+
/// Processes HTLCs which are pending waiting on random forward delay.
///
/// Should only really ever be called in response to a PendingHTLCsForwardable event.
/// * Broadcasting `ChannelUpdate` messages if we've been disconnected from our peer for more
/// than a minute, informing the network that they should no longer attempt to route over
/// the channel.
+ /// * Expiring a channel's previous `ChannelConfig` if necessary to only allow forwarding HTLCs
+ /// with the current `ChannelConfig`.
///
/// Note that this may cause reentrancy through `chain::Watch::update_channel` calls or feerate
/// estimate fetches.
_ => {},
}
+ chan.maybe_expire_prev_config();
+
true
});
(4, counterparty, required),
(5, outbound_scid_alias, option),
(6, funding_txo, option),
+ (7, config, option),
(8, short_channel_id, option),
(10, channel_value_satoshis, required),
(12, unspendable_punishment_reserve, option),
($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
{
$node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
- let fee = $node.node.channel_state.lock().unwrap()
- .by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap()
- .config.options.forwarding_fee_base_msat;
+ let fee = {
+ let channel_state = $node.node.channel_state.lock().unwrap();
+ let channel = channel_state
+ .by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
+ if let Some(prev_config) = channel.prev_config() {
+ prev_config.forwarding_fee_base_msat
+ } else {
+ channel.config().forwarding_fee_base_msat
+ }
+ };
expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
expected_total_fee_msat += fee as u64;
check_added_monitors!($node, 1);
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
// Rebalance the network to generate htlc in the two directions
- send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
+ send_payment(&nodes[0], &[&nodes[1]], 8_000_000);
// node[0] is gonna to revoke an old state thus node[1] should be able to claim both offered/received HTLC outputs on top of commitment tx
- let payment_preimage_1 = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0;
- let (_payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3000000);
+ let payment_preimage_1 = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0;
+ let (_payment_preimage_2, payment_hash_2, _) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
// Get the will-be-revoked local txn from node[0]
let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
check_added_monitors!(nodes[1], 1);
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
- expect_payment_failed!(nodes[1], payment_hash_2, true);
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
- let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
+ let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert_eq!(node_txn.len(), 2); // ChannelMonitor: penalty tx, ChannelManager: local commitment
assert_eq!(node_txn[0].input.len(), 3); // Claim the revoked output + both revoked HTLC outputs
// Next nodes[1] broadcasts its current local tx state:
assert_eq!(node_txn[1].input.len(), 1);
- assert_eq!(node_txn[1].input[0].previous_output.txid, chan_1.3.txid()); //Spending funding tx unique txouput, tx broadcasted by ChannelManager
+ check_spends!(node_txn[1], chan_1.3);
+
+ // Finally, mine the penalty transaction and check that we get an HTLC failure after
+ // ANTI_REORG_DELAY confirmations.
+ mine_transaction(&nodes[1], &node_txn[0]);
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
+ expect_payment_failed!(nodes[1], payment_hash_2, true);
}
get_announce_close_broadcast_events(&nodes, 0, 1);
assert_eq!(nodes[0].node.list_channels().len(), 0);
let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
// Rebalance the network to generate htlc in the two directions
- send_payment(&nodes[0], &vec!(&nodes[1])[..], 8000000);
+ send_payment(&nodes[0], &[&nodes[1]], 8_000_000);
// node[0] is gonna to revoke an old state thus node[1] should be able to claim both offered/received HTLC outputs on top of commitment tx, but this
// time as two different claim transactions as we're gonna to timeout htlc with given a high current height
- let payment_preimage_1 = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0;
- let (_payment_preimage_2, payment_hash_2, _payment_secret_2) = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3000000);
+ let payment_preimage_1 = route_payment(&nodes[0], &[&nodes[1]], 3_000_000).0;
+ let (_payment_preimage_2, payment_hash_2, _payment_secret_2) = route_payment(&nodes[1], &[&nodes[0]], 3_000_000);
// Get the will-be-revoked local txn from node[0]
let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan_1.2);
}
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
- expect_payment_failed!(nodes[1], payment_hash_2, true);
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
- let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
+ let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
assert!(node_txn.len() == 9 || node_txn.len() == 10);
// Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration
assert_eq!(*witness_lens.iter().skip(0).next().unwrap(), 77); // revoked to_local
assert_eq!(*witness_lens.iter().skip(1).next().unwrap(), OFFERED_HTLC_SCRIPT_WEIGHT); // revoked offered HTLC
assert_eq!(*witness_lens.iter().skip(2).next().unwrap(), ACCEPTED_HTLC_SCRIPT_WEIGHT); // revoked received HTLC
+
+ // Finally, mine the penalty transactions and check that we get an HTLC failure after
+ // ANTI_REORG_DELAY confirmations.
+ mine_transaction(&nodes[1], &node_txn[2]);
+ mine_transaction(&nodes[1], &node_txn[3]);
+ mine_transaction(&nodes[1], &node_txn[4]);
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
+ expect_payment_failed!(nodes[1], payment_hash_2, true);
}
get_announce_close_broadcast_events(&nodes, 0, 1);
assert_eq!(nodes[0].node.list_channels().len(), 0);
check_added_monitors!(nodes[0], 1);
check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
+
connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
- timeout_tx.push(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[1].clone());
+ timeout_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().drain(..)
+ .filter(|tx| tx.input[0].previous_output.txid == bs_commitment_tx[0].txid()).collect();
+ check_spends!(timeout_tx[0], bs_commitment_tx[0]);
+ // For both a revoked or non-revoked commitment transaction, after ANTI_REORG_DELAY the
+ // dust HTLC should have been failed.
+ expect_payment_failed!(nodes[0], dust_hash, true);
+
if !revoked {
- expect_payment_failed!(nodes[0], dust_hash, true);
assert_eq!(timeout_tx[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
- // We fail non-dust-HTLC 2 by broadcast of local timeout tx on remote commitment tx
- mine_transaction(&nodes[0], &timeout_tx[0]);
- assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
- connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
- expect_payment_failed!(nodes[0], non_dust_hash, true);
} else {
- // If revoked, both dust & non-dust HTLCs should have been failed after ANTI_REORG_DELAY confs of revoked
- // commitment tx
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 2);
- let first;
- match events[0] {
- Event::PaymentPathFailed { payment_hash, .. } => {
- if payment_hash == dust_hash { first = true; }
- else { first = false; }
- },
- _ => panic!("Unexpected event"),
- }
- match events[1] {
- Event::PaymentPathFailed { payment_hash, .. } => {
- if first { assert_eq!(payment_hash, non_dust_hash); }
- else { assert_eq!(payment_hash, dust_hash); }
- },
- _ => panic!("Unexpected event"),
- }
+ assert_eq!(timeout_tx[0].lock_time, 0);
}
+ // We fail non-dust-HTLC 2 by broadcast of local timeout/revocation-claim tx
+ mine_transaction(&nodes[0], &timeout_tx[0]);
+ assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
+ connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
+ expect_payment_failed!(nodes[0], non_dust_hash, true);
}
}
} else { panic!(); }
}
+#[test]
+fn revoked_output_htlc_resolution_timing() {
+ // Tests that HTLCs which were present in a broadcasted remote revoked commitment transaction
+ // are resolved only after a spend of the HTLC output reaches six confirmations. Preivously
+ // they would resolve after the revoked commitment transaction itself reaches six
+ // confirmations.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known());
+
+ let payment_hash_1 = route_payment(&nodes[1], &[&nodes[0]], 1_000_000).1;
+
+ // Get a commitment transaction which contains the HTLC we care about, but which we'll revoke
+ // before forwarding.
+ let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan.2);
+ assert_eq!(revoked_local_txn.len(), 1);
+
+ // Route a dust payment to revoke the above commitment transaction
+ route_payment(&nodes[0], &[&nodes[1]], 1_000);
+
+ // Confirm the revoked commitment transaction, closing the channel.
+ mine_transaction(&nodes[1], &revoked_local_txn[0]);
+ check_added_monitors!(nodes[1], 1);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_broadcast!(nodes[1], true);
+
+ let bs_spend_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(bs_spend_txn.len(), 2);
+ check_spends!(bs_spend_txn[0], revoked_local_txn[0]);
+ check_spends!(bs_spend_txn[1], chan.3);
+
+ // After the commitment transaction confirms, we should still wait on the HTLC spend
+ // transaction to confirm before resolving the HTLC.
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ // Spend the HTLC output, generating a HTLC failure event after ANTI_REORG_DELAY confirmations.
+ mine_transaction(&nodes[1], &bs_spend_txn[0]);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
+ expect_payment_failed!(nodes[1], payment_hash_1, true);
+}
+
#[test]
fn chanmon_claim_value_coop_close() {
// Tests `get_claimable_balances` returns the correct values across a simple cooperative claim.
//! These tests work by standing up full nodes and route payments across the network, checking the
//! returned errors decode to the correct thing.
-use chain::channelmonitor::{CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
+use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
use chain::keysinterface::{KeysInterface, Recipient};
use ln::{PaymentHash, PaymentSecret};
-use ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
+use ln::channel::EXPIRE_PREV_CONFIG_TICKS;
+use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
use ln::onion_utils;
use routing::gossip::{NetworkUpdate, RoutingFees, NodeId};
use routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
use ln::msgs::{ChannelMessageHandler, ChannelUpdate, OptionalField};
use ln::wire::Encode;
use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
-use util::ser::{Writeable, Writer};
+use util::ser::{ReadableArgs, Writeable, Writer};
use util::{byte_utils, test_utils};
-use util::config::UserConfig;
+use util::config::{UserConfig, ChannelConfig};
+use util::errors::APIError;
use bitcoin::hash_types::BlockHash;
let preimage = send_along_route(&nodes[0], bogus_route, &[&nodes[1], &nodes[2]], amt_to_forward+1).0;
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], preimage);
- //TODO: with new config API, we will be able to generate both valid and
- //invalid channel_update cases.
let short_channel_id = channels[0].0.contents.short_channel_id;
run_onion_failure_test("fee_insufficient", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| {
msg.amount_msat -= 1;
}, true, Some(23), None, None);
}
+fn do_test_onion_failure_stale_channel_update(announced_channel: bool) {
+ // Create a network of three nodes and two channels connecting them. We'll be updating the
+ // HTLC relay policy of the second channel, causing forwarding failures at the first hop.
+ let mut config = UserConfig::default();
+ config.channel_handshake_config.announced_channel = announced_channel;
+ config.channel_handshake_limits.force_announced_channel_preference = false;
+ config.accept_forwards_to_priv_channels = !announced_channel;
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(config), None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let other_channel = create_chan_between_nodes(
+ &nodes[0], &nodes[1], InitFeatures::known(), InitFeatures::known(),
+ );
+ let channel_to_update = if announced_channel {
+ let channel = create_announced_chan_between_nodes(
+ &nodes, 1, 2, InitFeatures::known(), InitFeatures::known(),
+ );
+ (channel.2, channel.0.contents.short_channel_id)
+ } else {
+ let channel = create_unannounced_chan_between_nodes_with_value(
+ &nodes, 1, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known(),
+ );
+ (channel.0.channel_id, channel.0.short_channel_id_alias.unwrap())
+ };
+ let channel_to_update_counterparty = &nodes[2].node.get_our_node_id();
+
+ let default_config = ChannelConfig::default();
+
+ // A test payment should succeed as the ChannelConfig has not been changed yet.
+ const PAYMENT_AMT: u64 = 40000;
+ let (route, payment_hash, payment_preimage, payment_secret) = if announced_channel {
+ get_route_and_payment_hash!(nodes[0], nodes[2], PAYMENT_AMT)
+ } else {
+ let hop_hints = vec![RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[1].node.get_our_node_id(),
+ short_channel_id: channel_to_update.1,
+ fees: RoutingFees {
+ base_msat: default_config.forwarding_fee_base_msat,
+ proportional_millionths: default_config.forwarding_fee_proportional_millionths,
+ },
+ cltv_expiry_delta: default_config.cltv_expiry_delta,
+ htlc_maximum_msat: None,
+ htlc_minimum_msat: None,
+ }])];
+ let payment_params = PaymentParameters::from_node_id(*channel_to_update_counterparty)
+ .with_features(InvoiceFeatures::known())
+ .with_route_hints(hop_hints);
+ get_route_and_payment_hash!(nodes[0], nodes[2], payment_params, PAYMENT_AMT, TEST_FINAL_CLTV)
+ };
+ send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
+ payment_hash, payment_secret);
+ claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
+
+ // Closure to force expiry of a channel's previous config.
+ let expire_prev_config = || {
+ for _ in 0..EXPIRE_PREV_CONFIG_TICKS {
+ nodes[1].node.timer_tick_occurred();
+ }
+ };
+
+ // Closure to update and retrieve the latest ChannelUpdate.
+ let update_and_get_channel_update = |config: &ChannelConfig, expect_new_update: bool,
+ prev_update: Option<&msgs::ChannelUpdate>, should_expire_prev_config: bool| -> Option<msgs::ChannelUpdate> {
+ nodes[1].node.update_channel_config(
+ channel_to_update_counterparty, &[channel_to_update.0], config,
+ ).unwrap();
+ let events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(events.len(), expect_new_update as usize);
+ if !expect_new_update {
+ return None;
+ }
+ let new_update = match &events[0] {
+ MessageSendEvent::BroadcastChannelUpdate { msg } => {
+ assert!(announced_channel);
+ msg.clone()
+ },
+ MessageSendEvent::SendChannelUpdate { node_id, msg } => {
+ assert_eq!(node_id, channel_to_update_counterparty);
+ assert!(!announced_channel);
+ msg.clone()
+ },
+ _ => panic!("expected Broadcast/SendChannelUpdate event"),
+ };
+ if prev_update.is_some() {
+ assert!(new_update.contents.timestamp > prev_update.unwrap().contents.timestamp)
+ }
+ if should_expire_prev_config {
+ expire_prev_config();
+ }
+ Some(new_update)
+ };
+
+ // We'll be attempting to route payments using the default ChannelUpdate for channels. This will
+ // lead to onion failures at the first hop once we update the ChannelConfig for the
+ // second hop.
+ let expect_onion_failure = |name: &str, error_code: u16, channel_update: &msgs::ChannelUpdate| {
+ let short_channel_id = channel_to_update.1;
+ let network_update = NetworkUpdate::ChannelUpdateMessage { msg: channel_update.clone() };
+ run_onion_failure_test(
+ name, 0, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {}, true,
+ Some(error_code), Some(network_update), Some(short_channel_id),
+ );
+ };
+
+ // Updates to cltv_expiry_delta below MIN_CLTV_EXPIRY_DELTA should fail with APIMisuseError.
+ let mut invalid_config = default_config.clone();
+ invalid_config.cltv_expiry_delta = 0;
+ match nodes[1].node.update_channel_config(
+ channel_to_update_counterparty, &[channel_to_update.0], &invalid_config,
+ ) {
+ Err(APIError::APIMisuseError{ .. }) => {},
+ _ => panic!("unexpected result applying invalid cltv_expiry_delta"),
+ }
+
+ // Increase the base fee which should trigger a new ChannelUpdate.
+ let mut config = nodes[1].node.list_usable_channels().iter()
+ .find(|channel| channel.channel_id == channel_to_update.0).unwrap()
+ .config.unwrap();
+ config.forwarding_fee_base_msat = u32::max_value();
+ let msg = update_and_get_channel_update(&config, true, None, false).unwrap();
+
+ // The old policy should still be in effect until a new block is connected.
+ send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
+ payment_hash, payment_secret);
+ claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
+
+ // Connect a block, which should expire the previous config, leading to a failure when
+ // forwarding the HTLC.
+ expire_prev_config();
+ expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
+
+ // Redundant updates should not trigger a new ChannelUpdate.
+ assert!(update_and_get_channel_update(&config, false, None, false).is_none());
+
+ // Similarly, updates that do not have an affect on ChannelUpdate should not trigger a new one.
+ config.force_close_avoidance_max_fee_satoshis *= 2;
+ assert!(update_and_get_channel_update(&config, false, None, false).is_none());
+
+ // Reset the base fee to the default and increase the proportional fee which should trigger a
+ // new ChannelUpdate.
+ config.forwarding_fee_base_msat = default_config.forwarding_fee_base_msat;
+ config.cltv_expiry_delta = u16::max_value();
+ let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
+ expect_onion_failure("incorrect_cltv_expiry", UPDATE|13, &msg);
+
+ // Reset the proportional fee and increase the CLTV expiry delta which should trigger a new
+ // ChannelUpdate.
+ config.cltv_expiry_delta = default_config.cltv_expiry_delta;
+ config.forwarding_fee_proportional_millionths = u32::max_value();
+ let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
+ expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
+
+ // To test persistence of the updated config, we'll re-initialize the ChannelManager.
+ let config_after_restart = {
+ let persister = test_utils::TestPersister::new();
+ let chain_monitor = test_utils::TestChainMonitor::new(
+ Some(nodes[1].chain_source), nodes[1].tx_broadcaster.clone(), nodes[1].logger,
+ node_cfgs[1].fee_estimator, &persister, nodes[1].keys_manager,
+ );
+
+ let mut chanmon_1 = <(_, ChannelMonitor<_>)>::read(
+ &mut &get_monitor!(nodes[1], other_channel.3).encode()[..], nodes[1].keys_manager,
+ ).unwrap().1;
+ let mut chanmon_2 = <(_, ChannelMonitor<_>)>::read(
+ &mut &get_monitor!(nodes[1], channel_to_update.0).encode()[..], nodes[1].keys_manager,
+ ).unwrap().1;
+ let mut channel_monitors = HashMap::new();
+ channel_monitors.insert(chanmon_1.get_funding_txo().0, &mut chanmon_1);
+ channel_monitors.insert(chanmon_2.get_funding_txo().0, &mut chanmon_2);
+
+ let chanmgr = <(_, ChannelManager<_, _, _, _, _, _>)>::read(
+ &mut &nodes[1].node.encode()[..], ChannelManagerReadArgs {
+ default_config: *nodes[1].node.get_current_default_configuration(),
+ keys_manager: nodes[1].keys_manager,
+ fee_estimator: node_cfgs[1].fee_estimator,
+ chain_monitor: &chain_monitor,
+ tx_broadcaster: nodes[1].tx_broadcaster.clone(),
+ logger: nodes[1].logger,
+ channel_monitors: channel_monitors,
+ },
+ ).unwrap().1;
+ chanmgr.list_channels().iter()
+ .find(|channel| channel.channel_id == channel_to_update.0).unwrap()
+ .config.unwrap()
+ };
+ assert_eq!(config, config_after_restart);
+}
+
+#[test]
+fn test_onion_failure_stale_channel_update() {
+ do_test_onion_failure_stale_channel_update(false);
+ do_test_onion_failure_stale_channel_update(true);
+}
+
#[test]
fn test_default_to_onion_payload_tlv_format() {
// Tests that we default to creating tlv format onion payloads when no `NodeAnnouncementInfo`
use chain::channelmonitor::{ANTI_REORG_DELAY, ChannelMonitor, LATENCY_GRACE_PERIOD_BLOCKS};
use chain::transaction::OutPoint;
use chain::keysinterface::KeysInterface;
+use ln::channel::EXPIRE_PREV_CONFIG_TICKS;
use ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, ChannelManagerReadArgs, MPP_TIMEOUT_TICKS, PaymentId, PaymentSendFailure};
use ln::features::{InitFeatures, InvoiceFeatures};
use ln::msgs;
// Update the fee on the middle hop to ensure PaymentSent events have the correct (retried) fee
// and not the original fee. We also update node[1]'s relevant config as
// do_claim_payment_along_route expects us to never overpay.
- nodes[1].node.channel_state.lock().unwrap().by_id.get_mut(&chan_id_2).unwrap()
- .config.options.forwarding_fee_base_msat += 100_000;
- new_route.paths[0][0].fee_msat += 100_000;
+ {
+ let mut channel_state = nodes[1].node.channel_state.lock().unwrap();
+ let mut channel = channel_state.by_id.get_mut(&chan_id_2).unwrap();
+ let mut new_config = channel.config();
+ new_config.forwarding_fee_base_msat += 100_000;
+ channel.update_config(&new_config);
+ new_route.paths[0][0].fee_msat += 100_000;
+ }
+
+ // Force expiration of the channel's previous config.
+ for _ in 0..EXPIRE_PREV_CONFIG_TICKS {
+ nodes[1].node.timer_tick_occurred();
+ }
assert!(nodes[0].node.retry_payment(&new_route, payment_id_1).is_err()); // Shouldn't be allowed to retry a fulfilled payment
nodes[0].node.retry_payment(&new_route, payment_id).unwrap();
do_test_onchain_htlc_reorg(false, false);
}
+#[test]
+fn test_counterparty_revoked_reorg() {
+ // Test what happens when a revoked counterparty transaction is broadcast but then reorg'd out
+ // of the main chain. Specifically, HTLCs in the latest commitment transaction which are not
+ // included in the revoked commitment transaction should not be considered failed, and should
+ // still be claim-from-able after the reorg.
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known());
+
+ // Get the initial commitment transaction for broadcast, before any HTLCs are added at all.
+ let revoked_local_txn = get_local_commitment_txn!(nodes[0], chan.2);
+ assert_eq!(revoked_local_txn.len(), 1);
+
+ // Now add two HTLCs in each direction, one dust and one not.
+ route_payment(&nodes[0], &[&nodes[1]], 5_000_000);
+ route_payment(&nodes[0], &[&nodes[1]], 5_000);
+ let (payment_preimage_3, payment_hash_3, ..) = route_payment(&nodes[1], &[&nodes[0]], 4_000_000);
+ let payment_hash_4 = route_payment(&nodes[1], &[&nodes[0]], 4_000).1;
+
+ nodes[0].node.claim_funds(payment_preimage_3);
+ let _ = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ check_added_monitors!(nodes[0], 1);
+ expect_payment_claimed!(nodes[0], payment_hash_3, 4_000_000);
+
+ let mut unrevoked_local_txn = get_local_commitment_txn!(nodes[0], chan.2);
+ assert_eq!(unrevoked_local_txn.len(), 3); // commitment + 2 HTLC txn
+ // Sort the unrevoked transactions in reverse order, ie commitment tx, then HTLC 1 then HTLC 3
+ unrevoked_local_txn.sort_unstable_by_key(|tx| 1_000_000 - tx.output.iter().map(|outp| outp.value).sum::<u64>());
+
+ // Now mine A's old commitment transaction, which should close the channel, but take no action
+ // on any of the HTLCs, at least until we get six confirmations (which we won't get).
+ mine_transaction(&nodes[1], &revoked_local_txn[0]);
+ check_added_monitors!(nodes[1], 1);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ check_closed_broadcast!(nodes[1], true);
+
+ // Connect up to one block before the revoked transaction would be considered final, then do a
+ // reorg that disconnects the full chain and goes up to the height at which the revoked
+ // transaction would be final.
+ let theoretical_conf_height = nodes[1].best_block_info().1 + ANTI_REORG_DELAY - 1;
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ disconnect_all_blocks(&nodes[1]);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ connect_blocks(&nodes[1], theoretical_conf_height);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ // Now connect A's latest commitment transaction instead and resolve the HTLCs
+ mine_transaction(&nodes[1], &unrevoked_local_txn[0]);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+ assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
+
+ // Connect the HTLC claim transaction for HTLC 3
+ mine_transaction(&nodes[1], &unrevoked_local_txn[2]);
+ expect_payment_sent!(nodes[1], payment_preimage_3);
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ // Connect blocks to confirm the unrevoked commitment transaction
+ connect_blocks(&nodes[1], ANTI_REORG_DELAY - 2);
+ expect_payment_failed!(nodes[1], payment_hash_4, true);
+}
+
fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_unconfirmed: bool, connect_style: ConnectStyle) {
// After creating a chan between nodes, we disconnect all blocks previously seen to force a
// channel close on nodes[0] side. We also use this to provide very basic testing of logic
is_usable: true, is_public: true,
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
+ config: None,
}
}
is_public: true,
inbound_htlc_minimum_msat: None,
inbound_htlc_maximum_msat: None,
+ config: None,
}
}
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
/// with our counterparty.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ChannelConfig {
/// Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound
/// over the channel.
}
}
+impl_writeable_tlv_based!(ChannelConfig, {
+ (0, forwarding_fee_proportional_millionths, required),
+ (2, forwarding_fee_base_msat, required),
+ (4, cltv_expiry_delta, required),
+ (6, max_dust_htlc_exposure_msat, required),
+ // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
+ // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
+ // the next required type of 10, which if seen by the old serialization will always fail.
+ (10, force_close_avoidance_max_fee_satoshis, required),
+});
+
/// Legacy version of [`ChannelConfig`] that stored the static
/// [`ChannelHandshakeConfig::announced_channel`] and
/// [`ChannelHandshakeConfig::commit_upfront_shutdown_pubkey`] fields.