platform: [ ubuntu-latest ]
toolchain: [ stable,
beta,
- # 1.36.0 is MSRV for Rust-Lightning, lightning-invoice, and lightning-persister
- 1.36.0,
- # 1.41.0 is Debian stable
- 1.41.0,
+ # 1.41.1 is MSRV for Rust-Lightning, lightning-invoice, and lightning-persister
+ 1.41.1,
# 1.45.2 is MSRV for lightning-net-tokio, lightning-block-sync, and coverage generation
1.45.2,
# 1.47.0 will be the MSRV for no-std builds using hashbrown once core2 is updated
- toolchain: beta
build-net-tokio: true
build-no-std: true
- - toolchain: 1.36.0
+ - toolchain: 1.41.1
build-no-std: false
test-log-variants: true
- - toolchain: 1.41.0
- build-no-std: false
- toolchain: 1.45.2
build-net-old-tokio: true
build-net-tokio: true
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout source code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install Rust ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
with:
cargo test --verbose --color always --no-default-features --features no-std
# check if there is a conflict between no-std and the default std feature
cargo test --verbose --color always --features no-std
+ # check no-std compatibility across dependencies
+ cd ..
+ cd no-std-check
+ cargo check --verbose --color always
cd ..
- name: Test on no-std builds Rust ${{ matrix.toolchain }} and full code-linking for coverage generation
if: "matrix.build-no-std && matrix.coverage"
TOOLCHAIN: nightly
steps:
- name: Checkout source code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
uses: actions-rs/toolchain@v1
with:
TOOLCHAIN: 1.52.1
steps:
- name: Checkout source code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
TOOLCHAIN: stable
steps:
- name: Checkout source code
- uses: actions/checkout@v2
- - name: Install Rust ${{ env.TOOLCHAIN }} toolchain
+ uses: actions/checkout@v3
+ - name: Install Rust 1.58 toolchain
uses: actions-rs/toolchain@v1
with:
- toolchain: ${{ env.TOOLCHAIN }}
+ toolchain: 1.58
override: true
profile: minimal
- name: Install dependencies for honggfuzz
TOOLCHAIN: 1.47.0
steps:
- name: Checkout source code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Install Rust ${{ env.TOOLCHAIN }} toolchain
uses: actions-rs/toolchain@v1
with:
Cargo.lock
.idea
lightning/target
+no-std-check/target
+# 0.0.105 - 2022-02-28
+
+## API Updates
+ * `Phantom node` payments are now supported, allowing receipt of a payment on
+ any one of multiple nodes without any coordination across the nodes being
+ required. See the new `PhantomKeysManager`'s docs for more, as well as
+ requirements on `KeysInterface::get_inbound_payment_key_material` and
+ `lightning_invoice::utils::create_phantom_invoice` (#1199).
+ * In order to support phantom node payments, several `KeysInterface` methods
+ now accept a `Recipient` parameter to select between the local `node_id` and
+ a phantom-specific one.
+ * `ProbabilisticScorer`, a `Score` based on learning the current balances of
+ channels in the network, was added. It attempts to better capture payment
+ success probability than the existing `Scorer`, though may underperform on
+ nodes with low payment volume. We welcome feedback on performance (#1227).
+ * `Score::channel_penalty_msat` now always takes the channel value, instead of
+ an `Option` (#1227).
+ * `UserConfig::manually_accept_inbound_channels` was added which, when set,
+ generates a new `Event::OpenChannelRequest`, which allows manual acceptance
+ or rejection of incoming channels on a per-channel basis (#1281).
+ * `Payee` has been renamed to `PaymentParameters` (#1271).
+ * `PaymentParameters` now has a `max_total_cltv_expiry_delta` field. This
+ defaults to 1008 and limits the maximum amount of time an HTLC can be pending
+ before it will either fail or be claimed (#1234).
+ * The `lightning-invoice` crate now supports no-std environments. This required
+ numerous API changes around timestamp handling and std+no-std versions of
+ several methods that previously assumed knowledge of the time (#1223, #1230).
+ * `lightning-invoice` now supports parsing invoices with expiry times of more
+ than one year. This required changing the semantics of `ExpiryTime` (#1273).
+ * The `CounterpartyCommitmentSecrets` is now public, allowing external uses of
+ the `BOLT 3` secret storage scheme (#1299).
+ * Several `Sign` methods now receive HTLC preimages as proof of state
+ transition, see new documentation for more (#1251).
+ * `KeysInterface::sign_invoice` now provides the HRP and other invoice data
+ separately to make it simpler for external signers to parse (#1272).
+ * `Sign::sign_channel_announcement` now returns both the node's signature and
+ the per-channel signature. `InMemorySigner` now requires the node's secret
+ key in order to implement this (#1179).
+ * `ChannelManager` deserialization will now fail if the `KeysInterface` used
+ has a different `node_id` than the `ChannelManager` expects (#1250).
+ * A new `ErrorAction` variant was added to send `warning` messages (#1013).
+ * Several references to `chain::Listen` objects in `lightning-block-sync` no
+ longer require a mutable reference (#1304).
+
+## Bug Fixes
+ * Fixed a regression introduced in 0.0.104 where `ChannelManager`'s internal
+ locks could have an order violation leading to a deadlock (#1238).
+ * Fixed cases where slow code (including user I/O) could cause us to
+ disconnect peers with ping timeouts in `BackgroundProcessor` (#1269).
+ * Now persist the `ChannelManager` prior to `BackgroundProcessor` stopping,
+ preventing race conditions where channels are closed on startup even with a
+ clean shutdown. This requires that users stop network processing and
+ disconnect peers prior to `BackgroundProcessor` shutdown (#1253).
+ * Fields in `ChannelHandshakeLimits` provided via the `override_config` to
+ `create_channel` are now applied instead of the default config (#1292).
+ * Fixed the generation of documentation on docs.rs to include API surfaces
+ which are hidden behind feature flags (#1303).
+ * Added the `channel_type` field to `accept_channel` messages we send, which
+ may avoid some future compatibility issues with other nodes (#1314).
+ * Fixed a bug where, if a previous LDK run using `lightning-persister` crashed
+ while persisting updated data, we may have failed to initialize (#1332).
+ * Fixed a rare bug where having both pending inbound and outbound HTLCs on a
+ just-opened inbound channel could cause `ChannelDetails::balance_msat` to
+ underflow and be reported as large, or cause panics in debug mode (#1268).
+ * Moved more instances of verbose gossip logging from the `Trace` level to the
+ `Gossip` level (#1220).
+ * Delayed `announcement_signatures` until the channel has six confirmations,
+ slightly improving propagation of channel announcements (#1179).
+ * Several fixes in script and transaction weight calculations when anchor
+ outputs are enabled (#1229).
+
+## Serialization Compatibility
+ * Using `ChannelManager` data written by versions prior to 0.0.105 will result
+ in preimages for HTLCs that were pending at startup to be missing in calls
+ to `KeysInterface` methods (#1251).
+ * Any phantom invoice payments received on a node that is not upgraded to
+ 0.0.105 will fail with an "unknown channel" error. Further, downgrading to
+ 0.0.104 or before and then upgrading again will invalidate existing phantom
+ SCIDs which may be included in invoices (#1199).
+
+## Security
+0.0.105 fixes two denial-of-service vulnerabilities which may be reachable from
+untrusted input in certain application designs.
+
+ * Route calculation spuriously panics when a routing decision is made for a
+ path where the second-to-last hop is a private channel, included due to a
+ multi-hop route hint in an invoice.
+ * `ChannelMonitor::get_claimable_balances` spuriously panics in some scenarios
+ when the LDK application's local commitment transaction is confirmed while
+ HTLCs are still pending resolution.
+
+In total, this release features 109 files changed, 7270 insertions, 2131
+deletions in 108 commits from 15 authors, in alphabetical order:
+ * Conor Okus
+ * Devrandom
+ * Elias Rohrer
+ * Jeffrey Czyz
+ * Jurvis Tan
+ * Ken Sedgwick
+ * Matt Corallo
+ * Naveen
+ * Tibo-lg
+ * Valentine Wallace
+ * Viktor Tigerström
+ * dependabot[bot]
+ * hackerrdave
+ * naveen
+ * vss96
+
+
# 0.0.104 - 2021-12-17
## API Updates
When refactoring, structure your PR to make it easy to review and don't
hestitate to split it into multiple small, focused PRs.
-The Minimal Supported Rust Version is 1.36.0 (enforced by our GitHub Actions).
+The Minimum Supported Rust Version is 1.41.1 (enforced by our GitHub Actions).
Commits should cover both the issue fixed and the solution's rationale.
These [guidelines](https://chris.beams.io/posts/git-commit/) should be kept in mind.
"lightning-background-processor",
]
+exclude = [
+ "no-std-check",
+]
+
# Our tests do actual crypo and lots of work, the tradeoff for -O1 is well worth it.
# Ideally we would only do this in profile.test, but profile.test only applies to
# the test binary, not dependencies, which means most of the critical code still
best_block: BestBlock::from_genesis(network),
};
let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), Arc::clone(&logger), keys_manager.clone(), config, params));
+ // Adding new calls to `KeysInterface::get_secure_random_bytes` during startup can change all the
+ // keys subsequently generated in this test. Rather than regenerating all the messages manually,
+ // it's easier to just increment the counter here so the keys don't change.
keys_manager.counter.fetch_sub(1, Ordering::AcqRel);
let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash()));
final_value_msat,
final_cltv_expiry_delta: 42,
};
- let route = match find_route(&our_id, ¶ms, &network_graph, None, Arc::clone(&logger), &scorer) {
+ let random_seed_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
+ let route = match find_route(&our_id, ¶ms, &network_graph, None, Arc::clone(&logger), &scorer, &random_seed_bytes) {
Ok(route) => route,
Err(_) => return,
};
final_value_msat,
final_cltv_expiry_delta: 42,
};
- let mut route = match find_route(&our_id, ¶ms, &network_graph, None, Arc::clone(&logger), &scorer) {
+ let random_seed_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
+ let mut route = match find_route(&our_id, ¶ms, &network_graph, None, Arc::clone(&logger), &scorer, &random_seed_bytes) {
Ok(route) => route,
Err(_) => return,
};
},
funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id: Some(scid),
+ inbound_scid_alias: None,
channel_value_satoshis: slice_to_be64(get_slice!(8)),
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
}
}
let scorer = FixedPenaltyScorer::with_penalty(0);
+ let random_seed_bytes: [u8; 32] = [get_slice!(1)[0]; 32];
for target in node_pks.iter() {
let route_params = RouteParameters {
payment_params: PaymentParameters::from_node_id(*target).with_route_hints(last_hops.clone()),
};
let _ = find_route(&our_pubkey, &route_params, &net_graph,
first_hops.map(|c| c.iter().collect::<Vec<_>>()).as_ref().map(|a| a.as_slice()),
- Arc::clone(&logger), &scorer);
+ Arc::clone(&logger), &scorer, &random_seed_bytes);
}
},
}
[package]
name = "lightning-background-processor"
-version = "0.0.104"
+version = "0.0.105"
authors = ["Valentine Wallace <vwallace@protonmail.com>"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
"""
edition = "2018"
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
[dependencies]
bitcoin = "0.27"
-lightning = { version = "0.0.104", path = "../lightning", features = ["std"] }
-lightning-persister = { version = "0.0.104", path = "../lightning-persister" }
+lightning = { version = "0.0.105", path = "../lightning", features = ["std"] }
+lightning-persister = { version = "0.0.105", path = "../lightning-persister" }
[dev-dependencies]
-lightning = { version = "0.0.104", path = "../lightning", features = ["_test_utils"] }
-lightning-invoice = { version = "0.12.0", path = "../lightning-invoice" }
+lightning = { version = "0.0.105", path = "../lightning", features = ["_test_utils"] }
+lightning-invoice = { version = "0.13.0", path = "../lightning-invoice" }
#![deny(missing_docs)]
#![deny(unsafe_code)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
#[macro_use] extern crate lightning;
use lightning::chain;
#[test]
fn test_invoice_payer() {
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let nodes = create_nodes(2, "test_invoice_payer".to_string());
// Initiate the background processors to watch each node.
let data_dir = nodes[0].persister.get_data_dir();
let persister = move |node: &ChannelManager<InMemorySigner, Arc<ChainMonitor>, Arc<test_utils::TestBroadcaster>, Arc<KeysManager>, Arc<test_utils::TestFeeEstimator>, Arc<test_utils::TestLogger>>| FilesystemPersister::persist_manager(data_dir.clone(), node);
- let router = DefaultRouter::new(Arc::clone(&nodes[0].network_graph), Arc::clone(&nodes[0].logger));
let scorer = Arc::new(Mutex::new(test_utils::TestScorer::with_penalty(0)));
+ let router = DefaultRouter::new(Arc::clone(&nodes[0].network_graph), Arc::clone(&nodes[0].logger), random_seed_bytes);
let invoice_payer = Arc::new(InvoicePayer::new(Arc::clone(&nodes[0].node), router, scorer, Arc::clone(&nodes[0].logger), |_: &_| {}, RetryAttempts(2)));
let event_handler = Arc::clone(&invoice_payer);
let bg_processor = BackgroundProcessor::start(persister, event_handler, nodes[0].chain_monitor.clone(), nodes[0].node.clone(), nodes[0].net_graph_msg_handler.clone(), nodes[0].peer_manager.clone(), nodes[0].logger.clone());
[package]
name = "lightning-block-sync"
-version = "0.0.104"
+version = "0.0.105"
authors = ["Jeffrey Czyz", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "http://github.com/lightningdevkit/rust-lightning"
"""
edition = "2018"
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
[features]
rest-client = [ "serde", "serde_json", "chunked_transfer" ]
rpc-client = [ "serde", "serde_json", "chunked_transfer" ]
[dependencies]
bitcoin = "0.27"
-lightning = { version = "0.0.104", path = "../lightning" }
+lightning = { version = "0.0.105", path = "../lightning" }
tokio = { version = "1.0", features = [ "io-util", "net", "time" ], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }
}
/// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
-/// (e.g. createrawtransaction). See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197
+/// (e.g. createrawtransaction). See <https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197>
impl TryInto<Transaction> for JsonResponse {
type Error = std::io::Error;
fn try_into(self) -> std::io::Result<Transaction> {
/// [`SpvClient`]: crate::SpvClient
/// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager
/// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor
-pub async fn synchronize_listeners<B: BlockSource, C: Cache>(
+pub async fn synchronize_listeners<'a, B: BlockSource, C: Cache, L: chain::Listen + ?Sized>(
block_source: &mut B,
network: Network,
header_cache: &mut C,
- mut chain_listeners: Vec<(BlockHash, &dyn chain::Listen)>,
+ mut chain_listeners: Vec<(BlockHash, &'a L)>,
) -> BlockSourceResult<ValidatedBlockHeader> {
let best_header = validate_best_block_header(block_source).await?;
}
/// Wrapper for supporting dynamically sized chain listeners.
-struct DynamicChainListener<'a>(&'a dyn chain::Listen);
+struct DynamicChainListener<'a, L: chain::Listen + ?Sized>(&'a L);
-impl<'a> chain::Listen for DynamicChainListener<'a> {
+impl<'a, L: chain::Listen + ?Sized> chain::Listen for DynamicChainListener<'a, L> {
fn block_connected(&self, _block: &Block, _height: u32) {
unreachable!()
}
}
/// A set of dynamically sized chain listeners, each paired with a starting block height.
-struct ChainListenerSet<'a>(Vec<(u32, &'a dyn chain::Listen)>);
+struct ChainListenerSet<'a, L: chain::Listen + ?Sized>(Vec<(u32, &'a L)>);
-impl<'a> chain::Listen for ChainListenerSet<'a> {
+impl<'a, L: chain::Listen + ?Sized> chain::Listen for ChainListenerSet<'a, L> {
fn block_connected(&self, block: &Block, height: u32) {
for (starting_height, chain_listener) in self.0.iter() {
if height > *starting_height {
#![deny(missing_docs)]
#![deny(unsafe_code)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
#[cfg(any(feature = "rest-client", feature = "rpc-client"))]
pub mod http;
[package]
name = "lightning-invoice"
description = "Data structures to parse and serialize BOLT11 lightning invoices"
-version = "0.12.0"
+version = "0.13.0"
authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
documentation = "https://docs.rs/lightning-invoice/"
license = "MIT OR Apache-2.0"
keywords = [ "lightning", "bitcoin", "invoice", "BOLT11" ]
readme = "README.md"
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
[features]
default = ["std"]
no-std = ["hashbrown", "lightning/no-std", "core2/alloc"]
[dependencies]
bech32 = { version = "0.8", default-features = false }
-lightning = { version = "0.0.104", path = "../lightning", default-features = false }
+lightning = { version = "0.0.105", path = "../lightning", default-features = false }
secp256k1 = { version = "0.20", default-features = false, features = ["recovery", "alloc"] }
num-traits = { version = "0.2.8", default-features = false }
bitcoin_hashes = { version = "0.10", default-features = false }
core2 = { version = "0.3.0", default-features = false, optional = true }
[dev-dependencies]
-lightning = { version = "0.0.104", path = "../lightning", default-features = false, features = ["_test_utils"] }
+lightning = { version = "0.0.105", path = "../lightning", default-features = false, features = ["_test_utils"] }
hex = "0.4"
#![deny(unused_mut)]
#![deny(broken_intra_doc_links)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
#![cfg_attr(feature = "strict", deny(warnings))]
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
use payment::{Payer, Router};
use bech32::ToBase32;
-use bitcoin_hashes::Hash;
+use bitcoin_hashes::{Hash, sha256};
use crate::prelude::*;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
+use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
+#[cfg(feature = "std")]
+use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA};
use lightning::ln::msgs::LightningError;
use lightning::routing::scoring::Score;
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
use core::convert::TryInto;
use core::ops::Deref;
use core::time::Duration;
+use sync::Mutex;
#[cfg(feature = "std")]
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
for hint in phantom_route_hints {
for channel in &hint.channels {
- let short_channel_id = match channel.short_channel_id {
+ let short_channel_id = match channel.get_inbound_payment_scid() {
Some(id) => id,
None => continue,
};
let our_channels = channelmanager.list_usable_channels();
let mut route_hints = vec![];
for channel in our_channels {
- let short_channel_id = match channel.short_channel_id {
+ let short_channel_id = match channel.get_inbound_payment_scid() {
Some(id) => id,
None => continue,
};
pub struct DefaultRouter<G: Deref<Target = NetworkGraph>, L: Deref> where L::Target: Logger {
network_graph: G,
logger: L,
+ random_seed_bytes: Mutex<[u8; 32]>,
}
impl<G: Deref<Target = NetworkGraph>, L: Deref> DefaultRouter<G, L> where L::Target: Logger {
- /// Creates a new router using the given [`NetworkGraph`] and [`Logger`].
- pub fn new(network_graph: G, logger: L) -> Self {
- Self { network_graph, logger }
+ /// Creates a new router using the given [`NetworkGraph`], a [`Logger`], and a randomness source
+ /// `random_seed_bytes`.
+ pub fn new(network_graph: G, logger: L, random_seed_bytes: [u8; 32]) -> Self {
+ let random_seed_bytes = Mutex::new(random_seed_bytes);
+ Self { network_graph, logger, random_seed_bytes }
}
}
&self, payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash,
first_hops: Option<&[&ChannelDetails]>, scorer: &S
) -> Result<Route, LightningError> {
- find_route(payer, params, &*self.network_graph, first_hops, &*self.logger, scorer)
+ let random_seed_bytes = {
+ let mut locked_random_seed_bytes = self.random_seed_bytes.lock().unwrap();
+ *locked_random_seed_bytes = sha256::Hash::hash(&*locked_random_seed_bytes).into_inner();
+ *locked_random_seed_bytes
+ };
+ find_route(payer, params, &*self.network_graph, first_hops, &*self.logger, scorer, &random_seed_bytes)
}
}
use lightning::util::enforcing_trait_impls::EnforcingSigner;
use lightning::util::events::{MessageSendEvent, MessageSendEventsProvider, Event};
use lightning::util::test_utils;
+ use lightning::chain::keysinterface::KeysInterface;
use utils::create_invoice_from_channelmanager_and_duration_since_epoch;
#[test]
assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is
+ // available.
+ assert_eq!(invoice.route_hints().len(), 1);
+ assert_eq!(invoice.route_hints()[0].0.len(), 1);
+ assert_eq!(invoice.route_hints()[0].0[0].short_channel_id,
+ nodes[1].node.list_usable_channels()[0].inbound_scid_alias.unwrap());
+
let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key())
.with_features(invoice.features().unwrap().clone())
.with_route_hints(invoice.route_hints());
let network_graph = node_cfgs[0].network_graph;
let logger = test_utils::TestLogger::new();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, network_graph,
- Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,
+ Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer, &random_seed_bytes
).unwrap();
let payment_event = {
let network_graph = node_cfgs[0].network_graph;
let logger = test_utils::TestLogger::new();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&nodes[0].node.get_our_node_id(), ¶ms, network_graph,
- Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,
+ Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer, &random_seed_bytes
).unwrap();
let (payment_event, fwd_idx) = {
let mut payment_hash = PaymentHash([0; 32]);
[package]
name = "lightning-net-tokio"
-version = "0.0.104"
+version = "0.0.105"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
"""
edition = "2018"
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
[dependencies]
bitcoin = "0.27"
-lightning = { version = "0.0.104", path = "../lightning" }
+lightning = { version = "0.0.105", path = "../lightning" }
tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "sync", "net", "time" ] }
[dev-dependencies]
#![deny(broken_intra_doc_links)]
#![deny(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
use bitcoin::secp256k1::key::PublicKey;
use tokio::net::TcpStream;
[package]
name = "lightning-persister"
-version = "0.0.104"
+version = "0.0.105"
authors = ["Valentine Wallace", "Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Utilities to manage Rust-Lightning channel data persistence and retrieval.
"""
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
[features]
_bench_unstable = ["lightning/_bench_unstable"]
[dependencies]
bitcoin = "0.27"
-lightning = { version = "0.0.104", path = "../lightning" }
+lightning = { version = "0.0.105", path = "../lightning" }
libc = "0.2"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winbase"] }
[dev-dependencies]
-lightning = { version = "0.0.104", path = "../lightning", features = ["_test_utils"] }
+lightning = { version = "0.0.105", path = "../lightning", features = ["_test_utils"] }
#![deny(broken_intra_doc_links)]
#![deny(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
#![cfg_attr(all(test, feature = "_bench_unstable"), feature(test))]
#[cfg(all(test, feature = "_bench_unstable"))] extern crate test;
"Invalid ChannelMonitor file name",
));
}
+ if filename.unwrap().ends_with(".tmp") {
+ // If we were in the middle of committing an new update and crashed, it should be
+ // safe to ignore the update - we should never have returned to the caller and
+ // irrevocably committed to the new state in any way.
+ continue;
+ }
let txid = Txid::from_hex(filename.unwrap().split_at(64).0);
if txid.is_err() {
[package]
name = "lightning"
-version = "0.0.104"
+version = "0.0.105"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Still missing tons of error-handling. See GitHub issues for suggested projects if you want to contribute. Don't have to bother telling you not to use this for anything serious, because you'd have to build a client around it to even try.
"""
+[package.metadata.docs.rs]
+features = ["std"]
+rustdoc-args = ["--cfg", "docsrs"]
+
[features]
# Internal test utilities exposed to other repo crates
_test_utils = ["hex", "regex", "bitcoin/bitcoinconsensus"]
hashbrown = { version = "0.11", optional = true }
hex = { version = "0.4", optional = true }
-regex = { version = "0.1.80", optional = true }
+regex = { version = "0.2.11", optional = true }
backtrace = { version = "0.3", optional = true }
core2 = { version = "0.3.0", optional = true, default-features = false }
[dev-dependencies]
hex = "0.4"
-regex = "0.1.80"
+regex = "0.2.11"
# TODO remove this once rust-bitcoin PR #637 is released
secp256k1 = { version = "0.20.2", default-features = false, features = ["alloc"] }
},
}
+impl ChannelMonitorUpdateStep {
+ fn variant_name(&self) -> &'static str {
+ match self {
+ ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { .. } => "LatestHolderCommitmentTXInfo",
+ ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. } => "LatestCounterpartyCommitmentTXInfo",
+ ChannelMonitorUpdateStep::PaymentPreimage { .. } => "PaymentPreimage",
+ ChannelMonitorUpdateStep::CommitmentSecret { .. } => "CommitmentSecret",
+ ChannelMonitorUpdateStep::ChannelForceClosed { .. } => "ChannelForceClosed",
+ ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript",
+ }
+ }
+}
+
impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(0, LatestHolderCommitmentTXInfo) => {
(0, commitment_tx, required),
($holder_commitment: expr, $htlc_iter: expr) => {
for htlc in $htlc_iter {
if let Some(htlc_input_idx) = htlc.transaction_output_index {
- if us.htlcs_resolved_on_chain.iter().any(|v| v.input_idx == htlc_input_idx) {
- assert!(us.funding_spend_confirmed.is_some());
+ if let Some(conf_thresh) = us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
+ if let OnchainEvent::MaturingOutput { descriptor: SpendableOutputDescriptor::DelayedPaymentOutput(descriptor) } = &event.event {
+ if descriptor.outpoint.index as u32 == htlc_input_idx { Some(event.confirmation_threshold()) } else { None }
+ } else { None }
+ }) {
+ debug_assert!($holder_commitment);
+ res.push(Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: htlc.amount_msat / 1000,
+ confirmation_height: conf_thresh,
+ });
+ } else if us.htlcs_resolved_on_chain.iter().any(|v| v.input_idx == htlc_input_idx) {
+ // Funding transaction spends should be fully confirmed by the time any
+ // HTLC transactions are resolved, unless we're talking about a holder
+ // commitment tx, whose resolution is delayed until the CSV timeout is
+ // reached, even though HTLCs may be resolved after only
+ // ANTI_REORG_DELAY confirmations.
+ debug_assert!($holder_commitment || us.funding_spend_confirmed.is_some());
} else if htlc.offered == $holder_commitment {
// If the payment was outbound, check if there's an HTLCUpdate
// indicating we have spent this HTLC with a timeout, claiming it back
F::Target: FeeEstimator,
L::Target: Logger,
{
+ log_info!(logger, "Applying update to monitor {}, bringing update_id from {} to {} with {} changes.",
+ log_funding_info!(self), self.latest_update_id, updates.update_id, updates.updates.len());
// ChannelMonitor updates may be applied after force close if we receive a
// preimage for a broadcasted commitment transaction HTLC output that we'd
// like to claim on-chain. If this is the case, we no longer have guaranteed
// access to the monitor's update ID, so we use a sentinel value instead.
if updates.update_id == CLOSED_CHANNEL_UPDATE_ID {
+ assert_eq!(updates.updates.len(), 1);
match updates.updates[0] {
ChannelMonitorUpdateStep::PaymentPreimage { .. } => {},
- _ => panic!("Attempted to apply post-force-close ChannelMonitorUpdate that wasn't providing a payment preimage"),
+ _ => {
+ log_error!(logger, "Attempted to apply post-force-close ChannelMonitorUpdate of type {}", updates.updates[0].variant_name());
+ panic!("Attempted to apply post-force-close ChannelMonitorUpdate that wasn't providing a payment preimage");
+ },
}
- assert_eq!(updates.updates.len(), 1);
} else if self.latest_update_id + 1 != updates.update_id {
panic!("Attempted to apply ChannelMonitorUpdates out of order, check the update_id before passing an update to update_monitor!");
}
#![allow(bare_trait_objects)]
#![allow(ellipsis_inclusive_range_patterns)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
#![cfg_attr(all(any(test, feature = "_test_utils"), feature = "_bench_unstable"), feature(test))]
/// This channel's type, as negotiated during channel open
channel_type: ChannelTypeFeatures,
+
+ // Our counterparty can offer us SCID aliases which they will map to this channel when routing
+ // outbound payments. These can be used in invoice route hints to avoid explicitly revealing
+ // the channel's funding UTXO.
+ // We only bother storing the most recent SCID alias at any time, though our counterparty has
+ // to store all of them.
+ latest_inbound_scid_alias: Option<u64>,
+
+ // We always offer our counterparty a static SCID alias, which we recognize as for this channel
+ // if we see it in HTLC forwarding instructions. We don't bother rotating the alias given we
+ // don't currently support node id aliases and eventually privacy should be provided with
+ // blinded paths instead of simple scid+node_id aliases.
+ outbound_scid_alias: u64,
}
#[cfg(any(test, fuzzing))]
// Constructors:
pub fn new_outbound<K: Deref, F: Deref>(
fee_estimator: &F, keys_provider: &K, counterparty_node_id: PublicKey, their_features: &InitFeatures,
- channel_value_satoshis: u64, push_msat: u64, user_id: u64, config: &UserConfig, current_chain_height: u32
+ channel_value_satoshis: u64, push_msat: u64, user_id: u64, config: &UserConfig, current_chain_height: u32,
+ outbound_scid_alias: u64
) -> Result<Channel<Signer>, APIError>
where K::Target: KeysInterface<Signer = Signer>,
F::Target: FeeEstimator,
workaround_lnd_bug_4006: None,
+ latest_inbound_scid_alias: None,
+ outbound_scid_alias,
+
#[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills: HashSet::new(),
/// Assumes chain_hash has already been checked and corresponds with what we expect!
pub fn new_from_req<K: Deref, F: Deref, L: Deref>(
fee_estimator: &F, keys_provider: &K, counterparty_node_id: PublicKey, their_features: &InitFeatures,
- msg: &msgs::OpenChannel, user_id: u64, config: &UserConfig, current_chain_height: u32, logger: &L
+ msg: &msgs::OpenChannel, user_id: u64, config: &UserConfig, current_chain_height: u32, logger: &L,
+ outbound_scid_alias: u64
) -> Result<Channel<Signer>, ChannelError>
where K::Target: KeysInterface<Signer = Signer>,
F::Target: FeeEstimator,
workaround_lnd_bug_4006: None,
+ latest_inbound_scid_alias: None,
+ outbound_scid_alias,
+
#[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills: HashSet::new(),
return Err(ChannelError::Ignore("Peer sent funding_locked when we needed a channel_reestablish. The peer is likely lnd, see https://github.com/lightningnetwork/lnd/issues/4006".to_owned()));
}
+ if let Some(scid_alias) = msg.short_channel_id_alias {
+ if Some(scid_alias) != self.short_channel_id {
+ // The scid alias provided can be used to route payments *from* our counterparty,
+ // i.e. can be used for inbound payments and provided in invoices, but is not used
+ // when routing outbound payments.
+ self.latest_inbound_scid_alias = Some(scid_alias);
+ }
+ }
+
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if non_shutdown_state == ChannelState::FundingSent as u32 {
} else if non_shutdown_state == (ChannelState::FundingSent as u32 | ChannelState::OurFundingLocked as u32) {
self.channel_state = ChannelState::ChannelFunded as u32 | (self.channel_state & MULTI_STATE_FLAGS);
self.update_time_counter += 1;
- } else if (self.channel_state & (ChannelState::ChannelFunded as u32) != 0 &&
- // Note that funding_signed/funding_created will have decremented both by 1!
- self.cur_holder_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 &&
- self.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1) ||
- // If we reconnected before sending our funding locked they may still resend theirs:
- (self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32) ==
- (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32)) {
- if self.counterparty_cur_commitment_point != Some(msg.next_per_commitment_point) {
+ } else if self.channel_state & (ChannelState::ChannelFunded as u32) != 0 ||
+ // If we reconnected before sending our funding locked they may still resend theirs:
+ (self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32) ==
+ (ChannelState::FundingSent as u32 | ChannelState::TheirFundingLocked as u32))
+ {
+ // They probably disconnected/reconnected and re-sent the funding_locked, which is
+ // required, or they're sending a fresh SCID alias.
+ let expected_point =
+ if self.cur_counterparty_commitment_transaction_number == INITIAL_COMMITMENT_NUMBER - 1 {
+ // If they haven't ever sent an updated point, the point they send should match
+ // the current one.
+ self.counterparty_cur_commitment_point
+ } else {
+ // If they have sent updated points, funding_locked is always supposed to match
+ // their "first" point, which we re-derive here.
+ Some(PublicKey::from_secret_key(&self.secp_ctx, &SecretKey::from_slice(
+ &self.commitment_secrets.get_secret(INITIAL_COMMITMENT_NUMBER - 1).expect("We should have all prev secrets available")
+ ).expect("We already advanced, so previous secret keys should have been validated already")))
+ };
+ if expected_point != Some(msg.next_per_commitment_point) {
return Err(ChannelError::Close("Peer sent a reconnect funding_locked with a different point".to_owned()));
}
- // They probably disconnected/reconnected and re-sent the funding_locked, which is required
return Ok(None);
} else {
return Err(ChannelError::Close("Peer sent a funding_locked at a strange time".to_owned()));
Some(msgs::FundingLocked {
channel_id: self.channel_id(),
next_per_commitment_point,
+ short_channel_id_alias: Some(self.outbound_scid_alias),
})
} else { None };
funding_locked: Some(msgs::FundingLocked {
channel_id: self.channel_id(),
next_per_commitment_point,
+ short_channel_id_alias: Some(self.outbound_scid_alias),
}),
raa: None, commitment_update: None, mon_update: None,
order: RAACommitmentOrder::CommitmentFirst,
Some(msgs::FundingLocked {
channel_id: self.channel_id(),
next_per_commitment_point,
+ short_channel_id_alias: Some(self.outbound_scid_alias),
})
} else { None };
self.short_channel_id
}
+ /// Allowed in any state (including after shutdown)
+ pub fn latest_inbound_scid_alias(&self) -> Option<u64> {
+ self.latest_inbound_scid_alias
+ }
+
+ /// Allowed in any state (including after shutdown)
+ pub fn outbound_scid_alias(&self) -> u64 {
+ self.outbound_scid_alias
+ }
+ /// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
+ /// indicating we were written by LDK prior to 0.0.106 which did not set outbound SCID aliases.
+ pub fn set_outbound_scid_alias(&mut self, outbound_scid_alias: u64) {
+ assert_eq!(self.outbound_scid_alias, 0);
+ self.outbound_scid_alias = outbound_scid_alias;
+ }
+
/// Returns the funding_txo we either got from our peer, or were given by
/// get_outbound_funding_created.
pub fn get_funding_txo(&self) -> Option<OutPoint> {
if need_commitment_update {
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
if self.channel_state & (ChannelState::PeerDisconnected as u32) == 0 {
- let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
+ let next_per_commitment_point =
+ self.holder_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &self.secp_ctx);
return Some(msgs::FundingLocked {
channel_id: self.channel_id,
next_per_commitment_point,
+ short_channel_id_alias: Some(self.outbound_scid_alias),
});
}
} else {
(13, self.channel_creation_height, required),
(15, preimages, vec_type),
(17, self.announcement_sigs_state, required),
+ (19, self.latest_inbound_scid_alias, option),
+ (21, self.outbound_scid_alias, required),
});
Ok(())
// If we read an old Channel, for simplicity we just treat it as "we never sent an
// AnnouncementSignatures" which implies we'll re-send it on reconnect, but that's fine.
let mut announcement_sigs_state = Some(AnnouncementSigsState::NotSent);
+ let mut latest_inbound_scid_alias = None;
+ let mut outbound_scid_alias = None;
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(13, channel_creation_height, option),
(15, preimages_opt, vec_type),
(17, announcement_sigs_state, option),
+ (19, latest_inbound_scid_alias, option),
+ (21, outbound_scid_alias, option),
});
if let Some(preimages) = preimages_opt {
workaround_lnd_bug_4006: None,
+ latest_inbound_scid_alias,
+ // Later in the ChannelManager deserialization phase we scan for channels and assign scid aliases if its missing
+ outbound_scid_alias: outbound_scid_alias.unwrap_or(0),
+
#[cfg(any(test, fuzzing))]
historical_inbound_htlc_fulfills,
let secp_ctx = Secp256k1::new();
let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- match Channel::<EnforcingSigner>::new_outbound(&&fee_estimator, &&keys_provider, node_id, &features, 10000000, 100000, 42, &config, 0) {
+ match Channel::<EnforcingSigner>::new_outbound(&&fee_estimator, &&keys_provider, node_id, &features, 10000000, 100000, 42, &config, 0, 42) {
Err(APIError::IncompatibleShutdownScript { script }) => {
assert_eq!(script.into_inner(), non_v0_segwit_shutdown_script.into_inner());
},
let node_a_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&fee_est, &&keys_provider, node_a_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0).unwrap();
+ let node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&fee_est, &&keys_provider, node_a_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0, 42).unwrap();
// Now change the fee so we can check that the fee in the open_channel message is the
// same as the old fee.
// Create Node A's channel pointing to Node B's pubkey
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0).unwrap();
+ let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0, 42).unwrap();
// Create Node B's channel by receiving Node A's open_channel message
// Make sure A's dust limit is as we expect.
let open_channel_msg = node_a_chan.get_open_channel(genesis_block(network).header.block_hash());
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), &open_channel_msg, 7, &config, 0, &&logger).unwrap();
+ let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
// Node B --> Node A: accept channel, explicitly setting B's dust limit.
let mut accept_channel_msg = node_b_chan.accept_inbound_channel();
let node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut chan = Channel::<EnforcingSigner>::new_outbound(&&fee_est, &&keys_provider, node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0).unwrap();
+ let mut chan = Channel::<EnforcingSigner>::new_outbound(&&fee_est, &&keys_provider, node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0, 42).unwrap();
let commitment_tx_fee_0_htlcs = Channel::<EnforcingSigner>::commit_tx_fee_msat(chan.feerate_per_kw, 0, chan.opt_anchors());
let commitment_tx_fee_1_htlc = Channel::<EnforcingSigner>::commit_tx_fee_msat(chan.feerate_per_kw, 1, chan.opt_anchors());
// Create Node A's channel pointing to Node B's pubkey
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0).unwrap();
+ let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0, 42).unwrap();
// Create Node B's channel by receiving Node A's open_channel message
let open_channel_msg = node_a_chan.get_open_channel(chain_hash);
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[7; 32]).unwrap());
- let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), &open_channel_msg, 7, &config, 0, &&logger).unwrap();
+ let mut node_b_chan = Channel::<EnforcingSigner>::new_from_req(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), &open_channel_msg, 7, &config, 0, &&logger, 42).unwrap();
// Node B --> Node A: accept channel
let accept_channel_msg = node_b_chan.accept_inbound_channel();
// Create a channel.
let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let config = UserConfig::default();
- let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0).unwrap();
+ let mut node_a_chan = Channel::<EnforcingSigner>::new_outbound(&&feeest, &&keys_provider, node_b_node_id, &InitFeatures::known(), 10000000, 100000, 42, &config, 0, 42).unwrap();
assert!(node_a_chan.counterparty_forwarding_info.is_none());
assert_eq!(node_a_chan.holder_htlc_minimum_msat, 1); // the default
assert!(node_a_chan.counterparty_forwarding_info().is_none());
let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
let mut config = UserConfig::default();
config.channel_options.announced_channel = false;
- let mut chan = Channel::<InMemorySigner>::new_outbound(&&feeest, &&keys_provider, counterparty_node_id, &InitFeatures::known(), 10_000_000, 100000, 42, &config, 0).unwrap(); // Nothing uses their network key in this test
+ let mut chan = Channel::<InMemorySigner>::new_outbound(&&feeest, &&keys_provider, counterparty_node_id, &InitFeatures::known(), 10_000_000, 100000, 42, &config, 0, 42).unwrap(); // Nothing uses their network key in this test
chan.holder_dust_limit_satoshis = 546;
chan.counterparty_selected_channel_reserve_satoshis = Some(0); // Filled in in accept_channel
// our payment, which we can use to decode errors or inform the user that the payment was sent.
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
-enum PendingHTLCRouting {
+pub(super) enum PendingHTLCRouting {
Forward {
onion_packet: msgs::OnionPacket,
short_channel_id: u64, // This should be NonZero<u64> eventually when we bump MSRV
Receive {
payment_data: msgs::FinalOnionHopData,
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
+ phantom_shared_secret: Option<[u8; 32]>,
},
ReceiveKeysend {
payment_preimage: PaymentPreimage,
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
pub(super) struct PendingHTLCInfo {
- routing: PendingHTLCRouting,
- incoming_shared_secret: [u8; 32],
+ pub(super) routing: PendingHTLCRouting,
+ pub(super) incoming_shared_secret: [u8; 32],
payment_hash: PaymentHash,
pub(super) amt_to_forward: u64,
pub(super) outgoing_cltv_value: u32,
short_channel_id: u64,
htlc_id: u64,
incoming_packet_shared_secret: [u8; 32],
+ phantom_shared_secret: Option<[u8; 32]>,
// This field is consumed by `claim_funds_from_hop()` when updating a force-closed backwards
// channel with a preimage provided by the forward channel.
// Note this is only exposed in cfg(test):
pub(super) struct ChannelHolder<Signer: Sign> {
pub(super) by_id: HashMap<[u8; 32], Channel<Signer>>,
+ /// SCIDs (and outbound SCID aliases) to the real channel id. Outbound SCID aliases are added
+ /// here once the channel is available for normal use, with SCIDs being added once the funding
+ /// transaction is confirmed at the channel's required confirmation depth.
pub(super) short_to_id: HashMap<u64, [u8; 32]>,
- /// short channel id -> forward infos. Key of 0 means payments received
+ /// SCID/SCID Alias -> forward infos. Key of 0 means payments received.
+ ///
+ /// Note that because we may have an SCID Alias as the key we can have two entries per channel,
+ /// though in practice we probably won't be receiving HTLCs for a channel both via the alias
+ /// and via the classic SCID.
+ ///
/// Note that while this is held in the same mutex as the channels themselves, no consistency
/// guarantees are made about the existence of a channel with the short id here, nor the short
/// ids in the PendingHTLCInfo!
/// Locked *after* channel_state.
pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
+ /// The set of outbound SCID aliases across all our channels, including unconfirmed channels
+ /// and some closed channels which reached a usable state prior to being closed. This is used
+ /// only to avoid duplicates, and is not persisted explicitly to disk, but rebuilt from the
+ /// active channel list on load.
+ outbound_scid_aliases: Mutex<HashSet<u64>>,
+
our_network_key: SecretKey,
our_network_pubkey: PublicKey,
pub funding_txo: Option<OutPoint>,
/// The position of the funding transaction in the chain. None if the funding transaction has
/// not yet been confirmed and the channel fully opened.
+ ///
+ /// Note that if [`inbound_scid_alias`] is set, it must be used for invoices and inbound
+ /// payments instead of this. See [`get_inbound_payment_scid`].
+ ///
+ /// [`inbound_scid_alias`]: Self::inbound_scid_alias
+ /// [`get_inbound_payment_scid`]: Self::get_inbound_payment_scid
pub short_channel_id: Option<u64>,
+ /// An optional [`short_channel_id`] alias for this channel, randomly generated by our
+ /// counterparty and usable in place of [`short_channel_id`] in invoice route hints. Our
+ /// counterparty will recognize the alias provided here in place of the [`short_channel_id`]
+ /// when they see a payment to be routed to us.
+ ///
+ /// [`short_channel_id`]: Self::short_channel_id
+ pub inbound_scid_alias: Option<u64>,
/// The value, in satoshis, of this channel as appears in the funding output
pub channel_value_satoshis: u64,
/// The value, in satoshis, that must always be held in the channel for us. This value ensures
pub is_public: bool,
}
+impl ChannelDetails {
+ /// Gets the SCID which should be used to identify this channel for inbound payments. This
+ /// should be used for providing invoice hints or in any other context where our counterparty
+ /// will forward a payment to us.
+ pub fn get_inbound_payment_scid(&self) -> Option<u64> {
+ self.inbound_scid_alias.or(self.short_channel_id)
+ }
+}
+
/// If a payment fails to send, it can be in one of several states. This enum is returned as the
/// Err() type describing which state the payment is in, see the description of individual enum
/// states for more.
/// Route hints used in constructing invoices for [phantom node payents].
///
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
+#[derive(Clone)]
pub struct PhantomRouteHints {
/// The list of channels to be included in the invoice route hints.
pub channels: Vec<ChannelDetails>,
}
}
+macro_rules! update_maps_on_chan_removal {
+ ($self: expr, $short_to_id: expr, $channel: expr) => {
+ if let Some(short_id) = $channel.get_short_channel_id() {
+ $short_to_id.remove(&short_id);
+ } else {
+ // If the channel was never confirmed on-chain prior to its closure, remove the
+ // outbound SCID alias we used for it from the collision-prevention set. While we
+ // generally want to avoid ever re-using an outbound SCID alias across all channels, we
+ // also don't want a counterparty to be able to trivially cause a memory leak by simply
+ // opening a million channels with us which are closed before we ever reach the funding
+ // stage.
+ let alias_removed = $self.outbound_scid_aliases.lock().unwrap().remove(&$channel.outbound_scid_alias());
+ debug_assert!(alias_removed);
+ }
+ $short_to_id.remove(&$channel.outbound_scid_alias());
+ }
+}
+
/// Returns (boolean indicating if we should remove the Channel object from memory, a mapped error)
macro_rules! convert_chan_err {
($self: ident, $err: expr, $short_to_id: expr, $channel: expr, $channel_id: expr) => {
},
ChannelError::Close(msg) => {
log_error!($self.logger, "Closing channel {} due to close-required error: {}", log_bytes!($channel_id[..]), msg);
- if let Some(short_id) = $channel.get_short_channel_id() {
- $short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!($self, $short_to_id, $channel);
let shutdown_res = $channel.force_shutdown(true);
(true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, $channel.get_user_id(),
shutdown_res, $self.get_channel_update_for_broadcast(&$channel).ok()))
},
ChannelError::CloseDelayBroadcast(msg) => {
log_error!($self.logger, "Channel {} need to be shutdown but closing transactions not broadcast due to {}", log_bytes!($channel_id[..]), msg);
- if let Some(short_id) = $channel.get_short_channel_id() {
- $short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!($self, $short_to_id, $channel);
let shutdown_res = $channel.force_shutdown(false);
(true, MsgHandleErrInternal::from_finish_shutdown(msg, *$channel_id, $channel.get_user_id(),
shutdown_res, $self.get_channel_update_for_broadcast(&$channel).ok()))
}
macro_rules! remove_channel {
- ($channel_state: expr, $entry: expr) => {
+ ($self: expr, $channel_state: expr, $entry: expr) => {
{
let channel = $entry.remove_entry().1;
- if let Some(short_id) = channel.get_short_channel_id() {
- $channel_state.short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!($self, $channel_state.short_to_id, channel);
channel
}
}
}
macro_rules! handle_monitor_err {
- ($self: ident, $err: expr, $channel_state: expr, $entry: expr, $action_type: path, $resend_raa: expr, $resend_commitment: expr) => {
- handle_monitor_err!($self, $err, $channel_state, $entry, $action_type, $resend_raa, $resend_commitment, Vec::new(), Vec::new())
- };
($self: ident, $err: expr, $short_to_id: expr, $chan: expr, $action_type: path, $resend_raa: expr, $resend_commitment: expr, $failed_forwards: expr, $failed_fails: expr, $failed_finalized_fulfills: expr, $chan_id: expr) => {
match $err {
ChannelMonitorUpdateErr::PermanentFailure => {
log_error!($self.logger, "Closing channel {} due to monitor update ChannelMonitorUpdateErr::PermanentFailure", log_bytes!($chan_id[..]));
- if let Some(short_id) = $chan.get_short_channel_id() {
- $short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!($self, $short_to_id, $chan);
// TODO: $failed_fails is dropped here, which will cause other channels to hit the
// chain in a confused state! We need to move them into the ChannelMonitor which
// will be responsible for failing backwards once things confirm on-chain.
}
res
} };
+ ($self: ident, $err: expr, $channel_state: expr, $entry: expr, $action_type: path, $chan_id: expr, COMMITMENT_UPDATE_ONLY) => { {
+ debug_assert!($action_type == RAACommitmentOrder::CommitmentFirst);
+ handle_monitor_err!($self, $err, $channel_state, $entry, $action_type, false, true, Vec::new(), Vec::new(), Vec::new(), $chan_id)
+ } };
+ ($self: ident, $err: expr, $channel_state: expr, $entry: expr, $action_type: path, $chan_id: expr, NO_UPDATE) => {
+ handle_monitor_err!($self, $err, $channel_state, $entry, $action_type, false, false, Vec::new(), Vec::new(), Vec::new(), $chan_id)
+ };
+ ($self: ident, $err: expr, $channel_state: expr, $entry: expr, $action_type: path, $resend_raa: expr, $resend_commitment: expr) => {
+ handle_monitor_err!($self, $err, $channel_state, $entry, $action_type, $resend_raa, $resend_commitment, Vec::new(), Vec::new(), Vec::new())
+ };
($self: ident, $err: expr, $channel_state: expr, $entry: expr, $action_type: path, $resend_raa: expr, $resend_commitment: expr, $failed_forwards: expr, $failed_fails: expr) => {
handle_monitor_err!($self, $err, $channel_state, $entry, $action_type, $resend_raa, $resend_commitment, $failed_forwards, $failed_fails, Vec::new())
- }
+ };
}
macro_rules! return_monitor_err {
}
}
+macro_rules! send_funding_locked {
+ ($short_to_id: expr, $pending_msg_events: expr, $channel: expr, $funding_locked_msg: expr) => {
+ $pending_msg_events.push(events::MessageSendEvent::SendFundingLocked {
+ node_id: $channel.get_counterparty_node_id(),
+ msg: $funding_locked_msg,
+ });
+ // Note that we may send a funding locked multiple times for a channel if we reconnect, so
+ // we allow collisions, but we shouldn't ever be updating the channel ID pointed to.
+ let outbound_alias_insert = $short_to_id.insert($channel.outbound_scid_alias(), $channel.channel_id());
+ assert!(outbound_alias_insert.is_none() || outbound_alias_insert.unwrap() == $channel.channel_id(),
+ "SCIDs should never collide - ensure you weren't behind the chain tip by a full month when creating channels");
+ if let Some(real_scid) = $channel.get_short_channel_id() {
+ let scid_insert = $short_to_id.insert(real_scid, $channel.channel_id());
+ assert!(scid_insert.is_none() || scid_insert.unwrap() == $channel.channel_id(),
+ "SCIDs should never collide - ensure you weren't behind the chain tip by a full month when creating channels");
+ }
+ }
+}
+
macro_rules! handle_chan_restoration_locked {
($self: ident, $channel_lock: expr, $channel_state: expr, $channel_entry: expr,
$raa: expr, $commitment_update: expr, $order: expr, $chanmon_update: expr,
$pending_forwards: expr, $funding_broadcastable: expr, $funding_locked: expr, $announcement_sigs: expr) => { {
let mut htlc_forwards = None;
- let counterparty_node_id = $channel_entry.get().get_counterparty_node_id();
let chanmon_update: Option<ChannelMonitorUpdate> = $chanmon_update; // Force type-checking to resolve
let chanmon_update_is_none = chanmon_update.is_none();
+ let counterparty_node_id = $channel_entry.get().get_counterparty_node_id();
let res = loop {
let forwards: Vec<(PendingHTLCInfo, u64)> = $pending_forwards; // Force type-checking to resolve
if !forwards.is_empty() {
// Similar to the above, this implies that we're letting the funding_locked fly
// before it should be allowed to.
assert!(chanmon_update.is_none());
- $channel_state.pending_msg_events.push(events::MessageSendEvent::SendFundingLocked {
- node_id: counterparty_node_id,
- msg,
- });
- $channel_state.short_to_id.insert($channel_entry.get().get_short_channel_id().unwrap(), $channel_entry.get().channel_id());
+ send_funding_locked!($channel_state.short_to_id, $channel_state.pending_msg_events, $channel_entry.get(), msg);
}
if let Some(msg) = $announcement_sigs {
$channel_state.pending_msg_events.push(events::MessageSendEvent::SendAnnouncementSignatures {
claimable_htlcs: HashMap::new(),
pending_msg_events: Vec::new(),
}),
+ outbound_scid_aliases: Mutex::new(HashSet::new()),
pending_inbound_payments: Mutex::new(HashMap::new()),
pending_outbound_payments: Mutex::new(HashMap::new()),
&self.default_configuration
}
+ fn create_and_insert_outbound_scid_alias(&self) -> u64 {
+ let height = self.best_block.read().unwrap().height();
+ let mut outbound_scid_alias = 0;
+ let mut i = 0;
+ loop {
+ if cfg!(fuzzing) { // fuzzing chacha20 doesn't use the key at all so we always get the same alias
+ outbound_scid_alias += 1;
+ } else {
+ outbound_scid_alias = fake_scid::Namespace::OutboundAlias.get_fake_scid(height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager);
+ }
+ if outbound_scid_alias != 0 && self.outbound_scid_aliases.lock().unwrap().insert(outbound_scid_alias) {
+ break;
+ }
+ i += 1;
+ if i > 1_000_000 { panic!("Your RNG is busted or we ran out of possible outbound SCID aliases (which should never happen before we run out of memory to store channels"); }
+ }
+ outbound_scid_alias
+ }
+
/// Creates a new outbound channel to the given remote node and with the given value.
///
/// `user_channel_id` will be provided back as in
let per_peer_state = self.per_peer_state.read().unwrap();
match per_peer_state.get(&their_network_key) {
Some(peer_state) => {
+ let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
let peer_state = peer_state.lock().unwrap();
let their_features = &peer_state.latest_features;
let config = if override_config.is_some() { override_config.as_ref().unwrap() } else { &self.default_configuration };
- Channel::new_outbound(&self.fee_estimator, &self.keys_manager, their_network_key, their_features,
- channel_value_satoshis, push_msat, user_channel_id, config, self.best_block.read().unwrap().height())?
+ match Channel::new_outbound(&self.fee_estimator, &self.keys_manager, their_network_key,
+ their_features, channel_value_satoshis, push_msat, user_channel_id, config,
+ self.best_block.read().unwrap().height(), outbound_scid_alias)
+ {
+ Ok(res) => res,
+ Err(e) => {
+ self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
+ return Err(e);
+ },
+ }
},
None => return Err(APIError::ChannelUnavailable { err: format!("Not connected to node: {}", their_network_key) }),
}
},
funding_txo: channel.get_funding_txo(),
short_channel_id: channel.get_short_channel_id(),
+ inbound_scid_alias: channel.latest_inbound_scid_alias(),
channel_value_satoshis: channel.get_value_satoshis(),
unspendable_punishment_reserve: to_self_reserve_satoshis,
balance_msat,
if let Some(monitor_update) = monitor_update {
if let Err(e) = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), monitor_update) {
let (result, is_permanent) =
- handle_monitor_err!(self, e, channel_state.short_to_id, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, false, false, Vec::new(), Vec::new(), Vec::new(), chan_entry.key());
+ handle_monitor_err!(self, e, channel_state.short_to_id, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, chan_entry.key(), NO_UPDATE);
if is_permanent {
- remove_channel!(channel_state, chan_entry);
+ remove_channel!(self, channel_state, chan_entry);
break result;
}
}
});
if chan_entry.get().is_shutdown() {
- let channel = remove_channel!(channel_state, chan_entry);
+ let channel = remove_channel!(self, channel_state, chan_entry);
if let Ok(channel_update) = self.get_channel_update_for_broadcast(&channel) {
channel_state.pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
msg: channel_update
return Err(APIError::ChannelUnavailable{err: "No such channel".to_owned()});
}
}
- if let Some(short_id) = chan.get().get_short_channel_id() {
- channel_state.short_to_id.remove(&short_id);
- }
if peer_node_id.is_some() {
if let Some(peer_msg) = peer_msg {
self.issue_channel_close_events(chan.get(),ClosureReason::CounterpartyForceClosed { peer_msg: peer_msg.to_string() });
} else {
self.issue_channel_close_events(chan.get(),ClosureReason::HolderForceClosed);
}
- chan.remove_entry().1
+ remove_channel!(self, channel_state, chan)
} else {
return Err(APIError::ChannelUnavailable{err: "No such channel".to_owned()});
}
}
fn construct_recv_pending_htlc_info(&self, hop_data: msgs::OnionHopData, shared_secret: [u8; 32],
- payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32) -> Result<PendingHTLCInfo, ReceiveError>
+ payment_hash: PaymentHash, amt_msat: u64, cltv_expiry: u32, phantom_shared_secret: Option<[u8; 32]>) -> Result<PendingHTLCInfo, ReceiveError>
{
// final_incorrect_cltv_expiry
if hop_data.outgoing_cltv_value != cltv_expiry {
PendingHTLCRouting::Receive {
payment_data: data,
incoming_cltv_expiry: hop_data.outgoing_cltv_value,
+ phantom_shared_secret,
}
} else if let Some(payment_preimage) = keysend_preimage {
// We need to check that the sender knows the keysend preimage before processing this
let pending_forward_info = match next_hop {
onion_utils::Hop::Receive(next_hop_data) => {
// OUR PAYMENT!
- match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry) {
+ match self.construct_recv_pending_htlc_info(next_hop_data, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None) {
Ok(info) => {
// Note that we could obviously respond immediately with an update_fulfill_htlc
// message, however that would leak that we are the recipient of this payment, so
routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
prev_funding_outpoint } => {
macro_rules! fail_forward {
- ($msg: expr, $err_code: expr, $err_data: expr) => {
+ ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr) => {
{
log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg);
let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
- short_channel_id: short_chan_id,
+ short_channel_id: prev_short_channel_id,
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
+ phantom_shared_secret: $phantom_ss,
});
failed_forwards.push((htlc_source, payment_hash,
- HTLCFailReason::Reason { failure_code: $err_code, data: $err_data }
+ HTLCFailReason::Reason { failure_code: $err_code, data: $err_data }
));
continue;
}
if let PendingHTLCRouting::Forward { onion_packet, .. } = routing {
let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode);
if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) {
- let shared_secret = {
+ let phantom_shared_secret = {
let mut arr = [0; 32];
arr.copy_from_slice(&SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap())[..]);
arr
};
- let next_hop = match onion_utils::decode_next_hop(shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
+ let next_hop = match onion_utils::decode_next_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) {
Ok(res) => res,
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
- fail_forward!(err_msg, err_code, Vec::new());
+ let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner();
+ // In this scenario, the phantom would have sent us an
+ // `update_fail_malformed_htlc`, meaning here we encrypt the error as
+ // if it came from us (the second-to-last hop) but contains the sha256
+ // of the onion.
+ fail_forward!(err_msg, err_code, sha256_of_onion.to_vec(), None);
},
Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => {
- fail_forward!(err_msg, err_code, Vec::new());
+ fail_forward!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret));
},
};
match next_hop {
onion_utils::Hop::Receive(hop_data) => {
- match self.construct_recv_pending_htlc_info(hop_data, shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value) {
+ match self.construct_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value, Some(phantom_shared_secret)) {
Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, vec![(info, prev_htlc_id)])),
- Err(ReceiveError { err_code, err_data, msg }) => fail_forward!(msg, err_code, err_data)
+ Err(ReceiveError { err_code, err_data, msg }) => fail_forward!(msg, err_code, err_data, Some(phantom_shared_secret))
}
},
_ => panic!(),
}
} else {
- fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
}
} else {
- fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new());
+ fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
}
},
HTLCForwardInfo::FailHTLC { .. } => {
// the channel is now on chain and our counterparty is
// trying to broadcast the HTLC-Timeout, but that's their
// problem, not ours.
- //
- // `fail_htlc_backwards_internal` is never called for
- // phantom payments, so this is unreachable for them.
}
}
}
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
+ // Phantom payments are only PendingHTLCRouting::Receive.
+ phantom_shared_secret: None,
});
match chan.get_mut().send_htlc(amt_to_forward, payment_hash, outgoing_cltv_value, htlc_source.clone(), onion_packet, &self.logger) {
Err(e) => {
}
ChannelError::Close(msg) => {
log_trace!(self.logger, "Closing channel {} due to Close-required error: {}", log_bytes!(chan.key()[..]), msg);
- let (channel_id, mut channel) = chan.remove_entry();
- if let Some(short_id) = channel.get_short_channel_id() {
- channel_state.short_to_id.remove(&short_id);
- }
+ let mut channel = remove_channel!(self, channel_state, chan);
// ChannelClosed event is generated by handle_error for us.
- Err(MsgHandleErrInternal::from_finish_shutdown(msg, channel_id, channel.get_user_id(), channel.force_shutdown(true), self.get_channel_update_for_broadcast(&channel).ok()))
+ Err(MsgHandleErrInternal::from_finish_shutdown(msg, channel.channel_id(), channel.get_user_id(), channel.force_shutdown(true), self.get_channel_update_for_broadcast(&channel).ok()))
},
ChannelError::CloseDelayBroadcast(_) => { panic!("Wait is only generated on receipt of channel_reestablish, which is handled by try_chan_entry, we don't bother to support it here"); }
};
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
routing, incoming_shared_secret, payment_hash, amt_to_forward, .. },
prev_funding_outpoint } => {
- let (cltv_expiry, onion_payload) = match routing {
- PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry } =>
- (incoming_cltv_expiry, OnionPayload::Invoice(payment_data)),
+ let (cltv_expiry, onion_payload, phantom_shared_secret) = match routing {
+ PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } =>
+ (incoming_cltv_expiry, OnionPayload::Invoice(payment_data), phantom_shared_secret),
PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
- (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage)),
+ (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None),
_ => {
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
}
outpoint: prev_funding_outpoint,
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: incoming_shared_secret,
+ phantom_shared_secret,
},
value: amt_to_forward,
cltv_expiry,
outpoint: prev_funding_outpoint,
htlc_id: $htlc.prev_hop.htlc_id,
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
+ phantom_shared_secret,
}), payment_hash,
HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: htlc_msat_height_data }
));
let ret_err = match res {
Ok(Some((update_fee, commitment_signed, monitor_update))) => {
if let Err(e) = self.chain_monitor.update_channel(chan.get_funding_txo().unwrap(), monitor_update) {
- let (res, drop) = handle_monitor_err!(self, e, short_to_id, chan, RAACommitmentOrder::CommitmentFirst, false, true, Vec::new(), Vec::new(), Vec::new(), chan_id);
+ let (res, drop) = handle_monitor_err!(self, e, short_to_id, chan, RAACommitmentOrder::CommitmentFirst, chan_id, COMMITMENT_UPDATE_ONLY);
if drop { retain_channel = false; }
res
} else {
pending_events.push(path_failure);
if let Some(ev) = full_failure_ev { pending_events.push(ev); }
},
- HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id, htlc_id, incoming_packet_shared_secret, .. }) => {
+ HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id, htlc_id, incoming_packet_shared_secret, phantom_shared_secret, .. }) => {
let err_packet = match onion_error {
HTLCFailReason::Reason { failure_code, data } => {
log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with code {}", log_bytes!(payment_hash.0), failure_code);
- let packet = onion_utils::build_failure_packet(&incoming_packet_shared_secret, failure_code, &data[..]).encode();
- onion_utils::encrypt_failure_packet(&incoming_packet_shared_secret, &packet)
+ if let Some(phantom_ss) = phantom_shared_secret {
+ let phantom_packet = onion_utils::build_failure_packet(&phantom_ss, failure_code, &data[..]).encode();
+ let encrypted_phantom_packet = onion_utils::encrypt_failure_packet(&phantom_ss, &phantom_packet);
+ onion_utils::encrypt_failure_packet(&incoming_packet_shared_secret, &encrypted_phantom_packet.data[..])
+ } else {
+ let packet = onion_utils::build_failure_packet(&incoming_packet_shared_secret, failure_code, &data[..]).encode();
+ onion_utils::encrypt_failure_packet(&incoming_packet_shared_secret, &packet)
+ }
},
HTLCFailReason::LightningError { err } => {
log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards with pre-built LightningError", log_bytes!(payment_hash.0));
return Err(MsgHandleErrInternal::send_err_msg_no_close("No inbound channels accepted".to_owned(), msg.temporary_channel_id.clone()));
}
- let mut channel = Channel::new_from_req(&self.fee_estimator, &self.keys_manager, counterparty_node_id.clone(),
- &their_features, msg, 0, &self.default_configuration, self.best_block.read().unwrap().height(), &self.logger)
- .map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id))?;
+ let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
+ let mut channel = match Channel::new_from_req(&self.fee_estimator, &self.keys_manager,
+ counterparty_node_id.clone(), &their_features, msg, 0, &self.default_configuration,
+ self.best_block.read().unwrap().height(), &self.logger, outbound_scid_alias)
+ {
+ Err(e) => {
+ self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
+ return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id));
+ },
+ Ok(res) => res
+ };
let mut channel_state_lock = self.channel_state.lock().unwrap();
let channel_state = &mut *channel_state_lock;
match channel_state.by_id.entry(channel.channel_id()) {
- hash_map::Entry::Occupied(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision!".to_owned(), msg.temporary_channel_id.clone())),
+ hash_map::Entry::Occupied(_) => {
+ self.outbound_scid_aliases.lock().unwrap().remove(&outbound_scid_alias);
+ return Err(MsgHandleErrInternal::send_err_msg_no_close("temporary_channel_id collision!".to_owned(), msg.temporary_channel_id.clone()))
+ },
hash_map::Entry::Vacant(entry) => {
if !self.default_configuration.manually_accept_inbound_channels {
channel_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
if let Some(monitor_update) = monitor_update {
if let Err(e) = self.chain_monitor.update_channel(chan_entry.get().get_funding_txo().unwrap(), monitor_update) {
let (result, is_permanent) =
- handle_monitor_err!(self, e, channel_state.short_to_id, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, false, false, Vec::new(), Vec::new(), Vec::new(), chan_entry.key());
+ handle_monitor_err!(self, e, channel_state.short_to_id, chan_entry.get_mut(), RAACommitmentOrder::CommitmentFirst, chan_entry.key(), NO_UPDATE);
if is_permanent {
- remove_channel!(channel_state, chan_entry);
+ remove_channel!(self, channel_state, chan_entry);
break result;
}
}
// also implies there are no pending HTLCs left on the channel, so we can
// fully delete it from tracking (the channel monitor is still around to
// watch for old state broadcasts)!
- if let Some(short_id) = chan_entry.get().get_short_channel_id() {
- channel_state.short_to_id.remove(&short_id);
- }
- (tx, Some(chan_entry.remove_entry().1))
+ (tx, Some(remove_channel!(self, channel_state, chan_entry)))
} else { (tx, None) }
},
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find corresponding channel".to_owned(), msg.channel_id))
onion_utils::build_first_hop_failure_packet(incoming_shared_secret, error_code, &{
let mut res = Vec::with_capacity(8 + 128);
// TODO: underspecified, follow https://github.com/lightningnetwork/lightning-rfc/issues/791
- res.extend_from_slice(&byte_utils::be16_to_array(0));
+ if error_code == 0x1000 | 20 {
+ res.extend_from_slice(&byte_utils::be16_to_array(0));
+ }
res.extend_from_slice(&upd.encode_with_len()[..]);
res
}[..])
let mut channel_lock = self.channel_state.lock().unwrap();
let channel_state = &mut *channel_lock;
let by_id = &mut channel_state.by_id;
- let short_to_id = &mut channel_state.short_to_id;
let pending_msg_events = &mut channel_state.pending_msg_events;
- if let Some(mut chan) = by_id.remove(&funding_outpoint.to_channel_id()) {
- if let Some(short_id) = chan.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
+ if let hash_map::Entry::Occupied(chan_entry) = by_id.entry(funding_outpoint.to_channel_id()) {
+ let mut chan = remove_channel!(self, channel_state, chan_entry);
failed_channels.push(chan.force_shutdown(false));
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
if let Some((commitment_update, monitor_update)) = commitment_opt {
if let Err(e) = self.chain_monitor.update_channel(chan.get_funding_txo().unwrap(), monitor_update) {
has_monitor_update = true;
- let (res, close_channel) = handle_monitor_err!(self, e, short_to_id, chan, RAACommitmentOrder::CommitmentFirst, false, true, Vec::new(), Vec::new(), Vec::new(), channel_id);
+ let (res, close_channel) = handle_monitor_err!(self, e, short_to_id, chan, RAACommitmentOrder::CommitmentFirst, channel_id, COMMITMENT_UPDATE_ONLY);
handle_errors.push((chan.get_counterparty_node_id(), res));
if close_channel { return false; }
} else {
if let Some(tx) = tx_opt {
// We're done with this channel. We got a closing_signed and sent back
// a closing_signed with a closing transaction to broadcast.
- if let Some(short_id) = chan.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
-
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
msg: update
log_info!(self.logger, "Broadcasting {}", log_tx!(tx));
self.tx_broadcaster.broadcast_transaction(&tx);
+ update_maps_on_chan_removal!(self, short_to_id, chan);
false
} else { true }
},
/// Legacy version of [`create_inbound_payment`]. Use this method if you wish to share
/// serialized state with LDK node(s) running 0.0.103 and earlier.
///
+ /// May panic if `invoice_expiry_delta_secs` is greater than one year.
+ ///
/// # Note
/// This method is deprecated and will be removed soon.
///
/// If you need exact expiry semantics, you should enforce them upon receipt of
/// [`PaymentReceived`].
///
- /// May panic if `invoice_expiry_delta_secs` is greater than one year.
- ///
/// Note that invoices generated for inbound payments should have their `min_final_cltv_expiry`
/// set to at least [`MIN_FINAL_CLTV_EXPIRY`].
///
/// Legacy version of [`create_inbound_payment_for_hash`]. Use this method if you wish to share
/// serialized state with LDK node(s) running 0.0.103 and earlier.
///
+ /// May panic if `invoice_expiry_delta_secs` is greater than one year.
+ ///
/// # Note
/// This method is deprecated and will be removed soon.
///
let mut channel_state = self.channel_state.lock().unwrap();
let best_block = self.best_block.read().unwrap();
loop {
- let scid_candidate = fake_scid::get_phantom_scid(&self.fake_scid_rand_bytes, best_block.height(), &self.genesis_hash, &self.keys_manager);
+ let scid_candidate = fake_scid::Namespace::Phantom.get_fake_scid(best_block.height(), &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager);
// Ensure the generated scid doesn't conflict with a real channel.
match channel_state.short_to_id.entry(scid_candidate) {
hash_map::Entry::Occupied(_) => continue,
}));
}
if let Some(funding_locked) = funding_locked_opt {
- pending_msg_events.push(events::MessageSendEvent::SendFundingLocked {
- node_id: channel.get_counterparty_node_id(),
- msg: funding_locked,
- });
+ send_funding_locked!(short_to_id, pending_msg_events, channel, funding_locked);
if channel.is_usable() {
log_trace!(self.logger, "Sending funding_locked with private initial channel_update for our counterparty on channel {}", log_bytes!(channel.channel_id()));
pending_msg_events.push(events::MessageSendEvent::SendChannelUpdate {
} else {
log_trace!(self.logger, "Sending funding_locked WITHOUT channel_update for {}", log_bytes!(channel.channel_id()));
}
- short_to_id.insert(channel.get_short_channel_id().unwrap(), channel.channel_id());
}
if let Some(announcement_sigs) = announcement_sigs {
log_trace!(self.logger, "Sending announcement_signatures for channel {}", log_bytes!(channel.channel_id()));
}
}
} else if let Err(reason) = res {
- if let Some(short_id) = channel.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!(self, short_to_id, channel);
// It looks like our counterparty went on-chain or funding transaction was
// reorged out of the main chain. Close the channel.
failed_channels.push(channel.force_shutdown(true));
{
let mut channel_state_lock = self.channel_state.lock().unwrap();
let channel_state = &mut *channel_state_lock;
- let short_to_id = &mut channel_state.short_to_id;
let pending_msg_events = &mut channel_state.pending_msg_events;
+ let short_to_id = &mut channel_state.short_to_id;
if no_connection_possible {
log_debug!(self.logger, "Failing all channels with {} due to no_connection_possible", log_pubkey!(counterparty_node_id));
channel_state.by_id.retain(|_, chan| {
if chan.get_counterparty_node_id() == *counterparty_node_id {
- if let Some(short_id) = chan.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!(self, short_to_id, chan);
failed_channels.push(chan.force_shutdown(true));
if let Ok(update) = self.get_channel_update_for_broadcast(&chan) {
pending_msg_events.push(events::MessageSendEvent::BroadcastChannelUpdate {
if chan.get_counterparty_node_id() == *counterparty_node_id {
chan.remove_uncommitted_htlcs_and_mark_paused(&self.logger);
if chan.is_shutdown() {
- if let Some(short_id) = chan.get_short_channel_id() {
- short_to_id.remove(&short_id);
- }
+ update_maps_on_chan_removal!(self, short_to_id, chan);
self.issue_channel_close_events(chan, ClosureReason::DisconnectedPeer);
return false;
} else {
});
impl_writeable_tlv_based!(ChannelDetails, {
+ (1, inbound_scid_alias, option),
(2, channel_id, required),
(4, counterparty, required),
(6, funding_txo, option),
},
(1, Receive) => {
(0, payment_data, required),
+ (1, phantom_shared_secret, option),
(2, incoming_cltv_expiry, required),
},
(2, ReceiveKeysend) => {
impl_writeable_tlv_based!(HTLCPreviousHopData, {
(0, short_channel_id, required),
+ (1, phantom_shared_secret, option),
(2, outpoint, required),
(4, htlc_id, required),
(6, incoming_packet_shared_secret, required)
}
}
+ let mut outbound_scid_aliases = HashSet::new();
+ for (chan_id, chan) in by_id.iter_mut() {
+ if chan.outbound_scid_alias() == 0 {
+ let mut outbound_scid_alias;
+ loop {
+ outbound_scid_alias = fake_scid::Namespace::OutboundAlias
+ .get_fake_scid(best_block_height, &genesis_hash, fake_scid_rand_bytes.as_ref().unwrap(), &args.keys_manager);
+ if outbound_scid_aliases.insert(outbound_scid_alias) { break; }
+ }
+ chan.set_outbound_scid_alias(outbound_scid_alias);
+ } else if !outbound_scid_aliases.insert(chan.outbound_scid_alias()) {
+ // Note that in rare cases its possible to hit this while reading an older
+ // channel if we just happened to pick a colliding outbound alias above.
+ log_error!(args.logger, "Got duplicate outbound SCID alias; {}", chan.outbound_scid_alias());
+ return Err(DecodeError::InvalidValue);
+ }
+ if chan.is_usable() {
+ if short_to_id.insert(chan.outbound_scid_alias(), *chan_id).is_some() {
+ // Note that in rare cases its possible to hit this while reading an older
+ // channel if we just happened to pick a colliding outbound alias above.
+ log_error!(args.logger, "Got duplicate outbound SCID alias; {}", chan.outbound_scid_alias());
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ }
+
let inbound_pmt_key_material = args.keys_manager.get_inbound_payment_key_material();
let expanded_inbound_key = inbound_payment::ExpandedKey::new(&inbound_pmt_key_material);
let channel_manager = ChannelManager {
inbound_payment_key: expanded_inbound_key,
pending_inbound_payments: Mutex::new(pending_inbound_payments),
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
+
+ outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
our_network_key,
use util::errors::APIError;
use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
use util::test_utils;
+ use chain::keysinterface::KeysInterface;
#[cfg(feature = "std")]
#[test]
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
// To start (1), send a regular payment but don't claim it.
let expected_route = [&nodes[1]];
};
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, nodes[0].network_graph, None,
- nodes[0].logger, &scorer
+ nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage)).unwrap();
check_added_monitors!(nodes[0], 1);
let payment_preimage = PaymentPreimage([42; 32]);
let route = find_route(
&nodes[0].node.get_our_node_id(), &route_params, nodes[0].network_graph, None,
- nodes[0].logger, &scorer
+ nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
let (payment_hash, _) = nodes[0].node.send_spontaneous_payment(&route, Some(payment_preimage)).unwrap();
check_added_monitors!(nodes[0], 1);
let network_graph = nodes[0].network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&payer_pubkey, &route_params, network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer
+ nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
let network_graph = nodes[0].network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&payer_pubkey, &route_params, network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer
+ nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
pub mod bench {
use chain::Listen;
use chain::chainmonitor::{ChainMonitor, Persist};
- use chain::keysinterface::{KeysManager, InMemorySigner};
+ use chain::keysinterface::{KeysManager, KeysInterface, InMemorySigner};
use ln::channelmanager::{BestBlock, ChainParameters, ChannelManager, PaymentHash, PaymentPreimage};
use ln::features::{InitFeatures, InvoiceFeatures};
use ln::functional_test_utils::*;
use routing::router::{PaymentParameters, get_route};
use util::test_utils;
use util::config::UserConfig;
- use util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PaymentPurpose};
+ use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
use bitcoin::hashes::Hash;
use bitcoin::hashes::sha256::Hash as Sha256;
let payment_params = PaymentParameters::from_node_id($node_b.get_our_node_id())
.with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
- let route = get_route(&$node_a.get_our_node_id(), &payment_params, &dummy_graph,
- Some(&usable_channels.iter().map(|r| r).collect::<Vec<_>>()), 10_000, TEST_FINAL_CLTV, &logger_a, &scorer).unwrap();
+ let seed = [3u8; 32];
+ let keys_manager = KeysManager::new(&seed, 42, 42);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let route = get_route(&$node_a.get_our_node_id(), &payment_params, &dummy_graph.read_only(),
+ Some(&usable_channels.iter().map(|r| r).collect::<Vec<_>>()), 10_000, TEST_FINAL_CLTV, &logger_a, &scorer, &random_seed_bytes).unwrap();
let mut payment_preimage = PaymentPreimage([0; 32]);
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
//! A bunch of useful utilities for building networks of nodes and exchanging messages between
//! nodes for functional tests.
-use chain::{BestBlock, Confirm, Listen, Watch};
+use chain::{BestBlock, Confirm, Listen, Watch, keysinterface::KeysInterface};
use chain::channelmonitor::ChannelMonitor;
use chain::transaction::OutPoint;
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
do_connect_block(node, block, false);
}
+fn call_claimable_balances<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>) {
+ // Ensure `get_claimable_balances`' self-tests never panic
+ for funding_outpoint in node.chain_monitor.chain_monitor.list_monitors() {
+ node.chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances();
+ }
+}
+
fn do_connect_block<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, block: &Block, skip_intermediaries: bool) {
+ call_claimable_balances(node);
let height = node.best_block_info().1 + 1;
if !skip_intermediaries {
let txdata: Vec<_> = block.txdata.iter().enumerate().collect();
match *node.connect_style.borrow() {
ConnectStyle::BestBlockFirst|ConnectStyle::BestBlockFirstSkippingBlocks => {
node.chain_monitor.chain_monitor.best_block_updated(&block.header, height);
+ call_claimable_balances(node);
node.chain_monitor.chain_monitor.transactions_confirmed(&block.header, &txdata, height);
node.node.best_block_updated(&block.header, height);
node.node.transactions_confirmed(&block.header, &txdata, height);
},
ConnectStyle::TransactionsFirst|ConnectStyle::TransactionsFirstSkippingBlocks => {
node.chain_monitor.chain_monitor.transactions_confirmed(&block.header, &txdata, height);
+ call_claimable_balances(node);
node.chain_monitor.chain_monitor.best_block_updated(&block.header, height);
node.node.transactions_confirmed(&block.header, &txdata, height);
node.node.best_block_updated(&block.header, height);
}
}
}
+ call_claimable_balances(node);
node.node.test_process_background_events();
node.blocks.lock().unwrap().push((block.header, height));
}
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
+ call_claimable_balances(node);
for i in 0..count {
let orig_header = node.blocks.lock().unwrap().pop().unwrap();
assert!(orig_header.1 > 0); // Cannot disconnect genesis
node.node.best_block_updated(&prev_header.0, prev_header.1);
},
}
+ call_claimable_balances(node);
}
}
(chan_announcement.1, chan_announcement.2, chan_announcement.3, chan_announcement.4)
}
+pub fn create_unannounced_chan_between_nodes_with_value<'a, 'b, 'c, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize, channel_value: u64, push_msat: u64, a_flags: InitFeatures, b_flags: InitFeatures) -> (msgs::FundingLocked, Transaction) {
+ let mut no_announce_cfg = test_default_channel_config();
+ no_announce_cfg.channel_options.announced_channel = false;
+ nodes[a].node.create_channel(nodes[b].node.get_our_node_id(), channel_value, push_msat, 42, Some(no_announce_cfg)).unwrap();
+ let open_channel = get_event_msg!(nodes[a], MessageSendEvent::SendOpenChannel, nodes[b].node.get_our_node_id());
+ nodes[b].node.handle_open_channel(&nodes[a].node.get_our_node_id(), a_flags, &open_channel);
+ let accept_channel = get_event_msg!(nodes[b], MessageSendEvent::SendAcceptChannel, nodes[a].node.get_our_node_id());
+ nodes[a].node.handle_accept_channel(&nodes[b].node.get_our_node_id(), b_flags, &accept_channel);
+
+ let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[a], channel_value, 42);
+ nodes[a].node.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
+ nodes[b].node.handle_funding_created(&nodes[a].node.get_our_node_id(), &get_event_msg!(nodes[a], MessageSendEvent::SendFundingCreated, nodes[b].node.get_our_node_id()));
+ check_added_monitors!(nodes[b], 1);
+
+ let cs_funding_signed = get_event_msg!(nodes[b], MessageSendEvent::SendFundingSigned, nodes[a].node.get_our_node_id());
+ nodes[a].node.handle_funding_signed(&nodes[b].node.get_our_node_id(), &cs_funding_signed);
+ check_added_monitors!(nodes[a], 1);
+
+ let conf_height = core::cmp::max(nodes[a].best_block_info().1 + 1, nodes[b].best_block_info().1 + 1);
+ confirm_transaction_at(&nodes[a], &tx, conf_height);
+ connect_blocks(&nodes[a], CHAN_CONFIRM_DEPTH - 1);
+ confirm_transaction_at(&nodes[b], &tx, conf_height);
+ connect_blocks(&nodes[b], CHAN_CONFIRM_DEPTH - 1);
+ let as_funding_locked = get_event_msg!(nodes[a], MessageSendEvent::SendFundingLocked, nodes[b].node.get_our_node_id());
+ nodes[a].node.handle_funding_locked(&nodes[b].node.get_our_node_id(), &get_event_msg!(nodes[b], MessageSendEvent::SendFundingLocked, nodes[a].node.get_our_node_id()));
+ let as_update = get_event_msg!(nodes[a], MessageSendEvent::SendChannelUpdate, nodes[b].node.get_our_node_id());
+ nodes[b].node.handle_funding_locked(&nodes[a].node.get_our_node_id(), &as_funding_locked);
+ let bs_update = get_event_msg!(nodes[b], MessageSendEvent::SendChannelUpdate, nodes[a].node.get_our_node_id());
+
+ nodes[a].node.handle_channel_update(&nodes[b].node.get_our_node_id(), &bs_update);
+ nodes[b].node.handle_channel_update(&nodes[a].node.get_our_node_id(), &as_update);
+
+ let mut found_a = false;
+ for chan in nodes[a].node.list_usable_channels() {
+ if chan.channel_id == as_funding_locked.channel_id {
+ assert!(!found_a);
+ found_a = true;
+ assert!(!chan.is_public);
+ }
+ }
+ assert!(found_a);
+
+ let mut found_b = false;
+ for chan in nodes[b].node.list_usable_channels() {
+ if chan.channel_id == as_funding_locked.channel_id {
+ assert!(!found_b);
+ found_b = true;
+ assert!(!chan.is_public);
+ }
+ }
+ assert!(found_b);
+
+ (as_funding_locked, tx)
+}
+
pub fn update_nodes_with_chan_announce<'a, 'b, 'c, 'd>(nodes: &'a Vec<Node<'b, 'c, 'd>>, a: usize, b: usize, ann: &msgs::ChannelAnnouncement, upd_1: &msgs::ChannelUpdate, upd_2: &msgs::ChannelUpdate) {
nodes[a].node.broadcast_node_announcement([0, 0, 0], [0; 32], Vec::new());
let a_events = nodes[a].node.get_and_clear_pending_msg_events();
node.net_graph_msg_handler.handle_channel_update(upd_2).unwrap();
node.net_graph_msg_handler.handle_node_announcement(&a_node_announcement).unwrap();
node.net_graph_msg_handler.handle_node_announcement(&b_node_announcement).unwrap();
+
+ // Note that channel_updates are also delivered to ChannelManagers to ensure we have
+ // forwarding info for local channels even if its not accepted in the network graph.
+ node.node.handle_channel_update(&nodes[a].node.get_our_node_id(), &upd_1);
+ node.node.handle_channel_update(&nodes[b].node.get_our_node_id(), &upd_2);
}
}
$crate::get_route_and_payment_hash!($send_node, $recv_node, vec![], $recv_value, TEST_FINAL_CLTV)
}};
($send_node: expr, $recv_node: expr, $last_hops: expr, $recv_value: expr, $cltv: expr) => {{
+ use $crate::chain::keysinterface::KeysInterface;
let (payment_preimage, payment_hash, payment_secret) = $crate::get_payment_preimage_hash!($recv_node, Some($recv_value));
let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id())
.with_features($crate::ln::features::InvoiceFeatures::known())
.with_route_hints($last_hops);
let scorer = $crate::util::test_utils::TestScorer::with_penalty(0);
+ let keys_manager = $crate::util::test_utils::TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = $crate::routing::router::get_route(
- &$send_node.node.get_our_node_id(), &payment_params, $send_node.network_graph,
+ &$send_node.node.get_our_node_id(), &payment_params, &$send_node.network_graph.read_only(),
Some(&$send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
- $recv_value, $cltv, $send_node.logger, &scorer
+ $recv_value, $cltv, $send_node.logger, &scorer, &random_seed_bytes
).unwrap();
(route, payment_hash, payment_preimage, payment_secret)
}}
pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id())
.with_features(InvoiceFeatures::known());
+ let network_graph = origin_node.network_graph.read_only();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let seed = [0u8; 32];
+ let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = get_route(
- &origin_node.node.get_our_node_id(), &payment_params, &origin_node.network_graph,
+ &origin_node.node.get_our_node_id(), &payment_params, &network_graph,
Some(&origin_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
- recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer).unwrap();
+ recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
pub fn route_over_limit<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id())
.with_features(InvoiceFeatures::known());
+ let network_graph = origin_node.network_graph.read_only();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let seed = [0u8; 32];
+ let keys_manager = test_utils::TestKeysInterface::new(&seed, Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = get_route(
- &origin_node.node.get_our_node_id(), &payment_params, origin_node.network_graph,
- None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer).unwrap();
+ &origin_node.node.get_our_node_id(), &payment_params, &network_graph,
+ None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), expected_route.len());
for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
use chain::channelmonitor;
use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
use chain::transaction::OutPoint;
-use chain::keysinterface::BaseSign;
+use chain::keysinterface::{BaseSign, KeysInterface};
use ln::{PaymentPreimage, PaymentSecret, PaymentHash};
use ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT};
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, PaymentId, RAACommitmentOrder, PaymentSendFailure, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, PAYMENT_EXPIRY_BLOCKS };
use ln::channel::{Channel, ChannelError};
use ln::{chan_utils, onion_utils};
use ln::chan_utils::{htlc_success_tx_weight, htlc_timeout_tx_weight, HTLCOutputInCommitment};
-use routing::network_graph::RoutingFees;
-use routing::router::{PaymentParameters, Route, RouteHop, RouteHint, RouteHintHop, RouteParameters, find_route, get_route};
+use routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, find_route, get_route};
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
use ln::msgs;
use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler, ErrorAction};
check_added_monitors!(nodes[1], 1);
}
-fn do_test_1_conf_open(connect_style: ConnectStyle) {
- // Previously, if the minium_depth config was set to 1, we'd never send a funding_locked. This
- // tests that we properly send one in that case.
- let mut alice_config = UserConfig::default();
- alice_config.own_channel_config.minimum_depth = 1;
- alice_config.channel_options.announced_channel = true;
- alice_config.peer_channel_config_limits.force_announced_channel_preference = false;
- let mut bob_config = UserConfig::default();
- bob_config.own_channel_config.minimum_depth = 1;
- bob_config.channel_options.announced_channel = true;
- bob_config.peer_channel_config_limits.force_announced_channel_preference = false;
- 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, &[Some(alice_config), Some(bob_config)]);
- let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- *nodes[0].connect_style.borrow_mut() = connect_style;
-
- let tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 100000, 10001, InitFeatures::known(), InitFeatures::known());
- mine_transaction(&nodes[1], &tx);
- nodes[0].node.handle_funding_locked(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendFundingLocked, nodes[0].node.get_our_node_id()));
- assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
-
- mine_transaction(&nodes[0], &tx);
- let as_msg_events = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(as_msg_events.len(), 2);
- let as_funding_locked = if let MessageSendEvent::SendFundingLocked { ref node_id, ref msg } = as_msg_events[0] {
- assert_eq!(*node_id, nodes[1].node.get_our_node_id());
- msg.clone()
- } else { panic!("Unexpected event"); };
- if let MessageSendEvent::SendChannelUpdate { ref node_id, msg: _ } = as_msg_events[1] {
- assert_eq!(*node_id, nodes[1].node.get_our_node_id());
- } else { panic!("Unexpected event"); }
-
- nodes[1].node.handle_funding_locked(&nodes[0].node.get_our_node_id(), &as_funding_locked);
- let bs_msg_events = nodes[1].node.get_and_clear_pending_msg_events();
- assert_eq!(bs_msg_events.len(), 1);
- if let MessageSendEvent::SendChannelUpdate { ref node_id, msg: _ } = bs_msg_events[0] {
- assert_eq!(*node_id, nodes[0].node.get_our_node_id());
- } else { panic!("Unexpected event"); }
-
- send_payment(&nodes[0], &[&nodes[1]], 100_000);
-
- // After 6 confirmations, as required by the spec, we'll send announcement_signatures and
- // broadcast the channel_announcement (but not before exactly 6 confirmations).
- connect_blocks(&nodes[0], 4);
- assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
- connect_blocks(&nodes[0], 1);
- nodes[1].node.handle_announcement_signatures(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendAnnouncementSignatures, nodes[1].node.get_our_node_id()));
- assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
-
- connect_blocks(&nodes[1], 5);
- let bs_announce_events = nodes[1].node.get_and_clear_pending_msg_events();
- assert_eq!(bs_announce_events.len(), 2);
- let bs_announcement_sigs = if let MessageSendEvent::SendAnnouncementSignatures { ref node_id, ref msg } = bs_announce_events[0] {
- assert_eq!(*node_id, nodes[0].node.get_our_node_id());
- msg.clone()
- } else { panic!("Unexpected event"); };
- let (bs_announcement, bs_update) = if let MessageSendEvent::BroadcastChannelAnnouncement { ref msg, ref update_msg } = bs_announce_events[1] {
- (msg.clone(), update_msg.clone())
- } else { panic!("Unexpected event"); };
-
- nodes[0].node.handle_announcement_signatures(&nodes[1].node.get_our_node_id(), &bs_announcement_sigs);
- let as_announce_events = nodes[0].node.get_and_clear_pending_msg_events();
- assert_eq!(as_announce_events.len(), 1);
- let (announcement, as_update) = if let MessageSendEvent::BroadcastChannelAnnouncement { ref msg, ref update_msg } = as_announce_events[0] {
- (msg.clone(), update_msg.clone())
- } else { panic!("Unexpected event"); };
- assert_eq!(announcement, bs_announcement);
-
- for node in nodes {
- assert!(node.net_graph_msg_handler.handle_channel_announcement(&announcement).unwrap());
- node.net_graph_msg_handler.handle_channel_update(&as_update).unwrap();
- node.net_graph_msg_handler.handle_channel_update(&bs_update).unwrap();
- }
-}
-#[test]
-fn test_1_conf_open() {
- do_test_1_conf_open(ConnectStyle::BestBlockFirst);
- do_test_1_conf_open(ConnectStyle::TransactionsFirst);
- do_test_1_conf_open(ConnectStyle::FullBlockViaListen);
-}
-
fn do_test_sanity_on_in_flight_opens(steps: u8) {
// Previously, we had issues deserializing channels when we hadn't connected the first block
// after creation. To catch that and similar issues, we lean on the Node::drop impl to test
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);
- //Force duplicate channel ids
+ // Force duplicate randomness for every get-random call
for node in nodes.iter() {
- *node.keys_manager.override_channel_id_priv.lock().unwrap() = Some([0; 32]);
+ *node.keys_manager.override_random_bytes.lock().unwrap() = Some([0; 32]);
}
// BOLT #2 spec: Sending node must ensure temporary_channel_id is unique from any other channel ID with the same peer.
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None).unwrap();
let node0_to_1_send_open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &node0_to_1_send_open_channel);
+ get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
- //Create a second channel with a channel_id collision
- assert!(nodes[0].node.create_channel(nodes[0].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None).is_err());
+ // Create a second channel with the same random values. This used to panic due to a colliding
+ // channel_id, but now panics due to a colliding outbound SCID alias.
+ assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, push_msat, 42, None).is_err());
}
#[test]
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
// We test config.our_to_self > BREAKDOWN_TIMEOUT is enforced in Channel::new_outbound()
- if let Err(error) = Channel::new_outbound(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), 1000000, 1000000, 0, &low_our_to_self_config, 0) {
+ if let Err(error) = Channel::new_outbound(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
+ &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), 1000000, 1000000, 0,
+ &low_our_to_self_config, 0, 42)
+ {
match error {
APIError::APIMisuseError { err } => { assert!(regex::Regex::new(r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks").unwrap().is_match(err.as_str())); },
_ => panic!("Unexpected event"),
nodes[1].node.create_channel(nodes[0].node.get_our_node_id(), 1000000, 1000000, 42, None).unwrap();
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
- if let Err(error) = Channel::new_from_req(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), &open_channel, 0, &low_our_to_self_config, 0, &nodes[0].logger) {
+ if let Err(error) = Channel::new_from_req(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
+ &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), &open_channel, 0,
+ &low_our_to_self_config, 0, &nodes[0].logger, 42)
+ {
match error {
ChannelError::Close(err) => { assert!(regex::Regex::new(r"Configured with an unreasonable our_to_self_delay \(\d+\) putting user funds at risks").unwrap().is_match(err.as_str())); },
_ => panic!("Unexpected event"),
nodes[1].node.create_channel(nodes[0].node.get_our_node_id(), 1000000, 1000000, 42, None).unwrap();
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
- if let Err(error) = Channel::new_from_req(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), &open_channel, 0, &high_their_to_self_config, 0, &nodes[0].logger) {
+ if let Err(error) = Channel::new_from_req(&&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
+ &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &InitFeatures::known(), &open_channel, 0,
+ &high_their_to_self_config, 0, &nodes[0].logger, 42)
+ {
match error {
ChannelError::Close(err) => { assert!(regex::Regex::new(r"They wanted our payments to be delayed by a needlessly long period\. Upper limit: \d+\. Actual: \d+").unwrap().is_match(err.as_str())); },
_ => panic!("Unexpected event"),
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()).with_features(InvoiceFeatures::known());
- let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, nodes[0].network_graph, None, 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer).unwrap();
+ let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
let (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]);
let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200).unwrap();
nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
assert!(chans_disabled.is_empty());
}
-#[test]
-fn test_priv_forwarding_rejection() {
- // If we have a private channel with outbound liquidity, and
- // UserConfig::accept_forwards_to_priv_channels is set to false, we should reject any attempts
- // to forward through that channel.
- let chanmon_cfgs = create_chanmon_cfgs(3);
- let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
- let mut no_announce_cfg = test_default_channel_config();
- no_announce_cfg.channel_options.announced_channel = false;
- no_announce_cfg.accept_forwards_to_priv_channels = false;
- let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(no_announce_cfg), None]);
- let persister: test_utils::TestPersister;
- let new_chain_monitor: test_utils::TestChainMonitor;
- let nodes_1_deserialized: ChannelManager<EnforcingSigner, &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>;
- let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
-
- let chan_id_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known()).2;
-
- // Note that the create_*_chan functions in utils requires announcement_signatures, which we do
- // not send for private channels.
- nodes[1].node.create_channel(nodes[2].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None).unwrap();
- let open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[2].node.get_our_node_id());
- nodes[2].node.handle_open_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &open_channel);
- let accept_channel = get_event_msg!(nodes[2], MessageSendEvent::SendAcceptChannel, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_accept_channel(&nodes[2].node.get_our_node_id(), InitFeatures::known(), &accept_channel);
-
- let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[1], 1_000_000, 42);
- nodes[1].node.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
- nodes[2].node.handle_funding_created(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendFundingCreated, nodes[2].node.get_our_node_id()));
- check_added_monitors!(nodes[2], 1);
-
- let cs_funding_signed = get_event_msg!(nodes[2], MessageSendEvent::SendFundingSigned, nodes[1].node.get_our_node_id());
- nodes[1].node.handle_funding_signed(&nodes[2].node.get_our_node_id(), &cs_funding_signed);
- check_added_monitors!(nodes[1], 1);
-
- let conf_height = core::cmp::max(nodes[1].best_block_info().1 + 1, nodes[2].best_block_info().1 + 1);
- confirm_transaction_at(&nodes[1], &tx, conf_height);
- connect_blocks(&nodes[1], CHAN_CONFIRM_DEPTH - 1);
- confirm_transaction_at(&nodes[2], &tx, conf_height);
- connect_blocks(&nodes[2], CHAN_CONFIRM_DEPTH - 1);
- let as_funding_locked = get_event_msg!(nodes[1], MessageSendEvent::SendFundingLocked, nodes[2].node.get_our_node_id());
- nodes[1].node.handle_funding_locked(&nodes[2].node.get_our_node_id(), &get_event_msg!(nodes[2], MessageSendEvent::SendFundingLocked, nodes[1].node.get_our_node_id()));
- get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[2].node.get_our_node_id());
- nodes[2].node.handle_funding_locked(&nodes[1].node.get_our_node_id(), &as_funding_locked);
- get_event_msg!(nodes[2], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
-
- assert!(nodes[0].node.list_usable_channels()[0].is_public);
- assert_eq!(nodes[1].node.list_usable_channels().len(), 2);
- assert!(!nodes[2].node.list_usable_channels()[0].is_public);
-
- // We should always be able to forward through nodes[1] as long as its out through a public
- // channel:
- send_payment(&nodes[2], &[&nodes[1], &nodes[0]], 10_000);
-
- // ... however, if we send to nodes[2], we will have to pass the private channel from nodes[1]
- // to nodes[2], which should be rejected:
- let route_hint = RouteHint(vec![RouteHintHop {
- src_node_id: nodes[1].node.get_our_node_id(),
- short_channel_id: nodes[2].node.list_channels()[0].short_channel_id.unwrap(),
- fees: RoutingFees { base_msat: 1000, proportional_millionths: 0 },
- cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
- htlc_minimum_msat: None,
- htlc_maximum_msat: None,
- }]);
- let last_hops = vec![route_hint];
- let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], last_hops, 10_000, TEST_FINAL_CLTV);
-
- nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
- check_added_monitors!(nodes[0], 1);
- let payment_event = SendEvent::from_event(nodes[0].node.get_and_clear_pending_msg_events().remove(0));
- nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
- commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false, true);
-
- let htlc_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
- assert!(htlc_fail_updates.update_add_htlcs.is_empty());
- assert_eq!(htlc_fail_updates.update_fail_htlcs.len(), 1);
- assert!(htlc_fail_updates.update_fail_malformed_htlcs.is_empty());
- assert!(htlc_fail_updates.update_fee.is_none());
-
- nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_fail_updates.update_fail_htlcs[0]);
- commitment_signed_dance!(nodes[0], nodes[1], htlc_fail_updates.commitment_signed, true, true);
- expect_payment_failed_with_update!(nodes[0], our_payment_hash, false, nodes[2].node.list_channels()[0].short_channel_id.unwrap(), true);
-
- // Now disconnect nodes[1] from its peers and restart with accept_forwards_to_priv_channels set
- // to true. Sadly there is currently no way to change it at runtime.
-
- nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
- nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
-
- let nodes_1_serialized = nodes[1].node.encode();
- let mut monitor_a_serialized = test_utils::TestVecWriter(Vec::new());
- let mut monitor_b_serialized = test_utils::TestVecWriter(Vec::new());
- get_monitor!(nodes[1], chan_id_1).write(&mut monitor_a_serialized).unwrap();
- get_monitor!(nodes[1], cs_funding_signed.channel_id).write(&mut monitor_b_serialized).unwrap();
-
- persister = test_utils::TestPersister::new();
- let keys_manager = &chanmon_cfgs[1].keys_manager;
- new_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, keys_manager);
- nodes[1].chain_monitor = &new_chain_monitor;
-
- let mut monitor_a_read = &monitor_a_serialized.0[..];
- let mut monitor_b_read = &monitor_b_serialized.0[..];
- let (_, mut monitor_a) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut monitor_a_read, keys_manager).unwrap();
- let (_, mut monitor_b) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut monitor_b_read, keys_manager).unwrap();
- assert!(monitor_a_read.is_empty());
- assert!(monitor_b_read.is_empty());
-
- no_announce_cfg.accept_forwards_to_priv_channels = true;
-
- let mut nodes_1_read = &nodes_1_serialized[..];
- let (_, nodes_1_deserialized_tmp) = {
- let mut channel_monitors = HashMap::new();
- channel_monitors.insert(monitor_a.get_funding_txo().0, &mut monitor_a);
- channel_monitors.insert(monitor_b.get_funding_txo().0, &mut monitor_b);
- <(BlockHash, ChannelManager<EnforcingSigner, &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>)>::read(&mut nodes_1_read, ChannelManagerReadArgs {
- default_config: no_announce_cfg,
- keys_manager,
- fee_estimator: node_cfgs[1].fee_estimator,
- chain_monitor: nodes[1].chain_monitor,
- tx_broadcaster: nodes[1].tx_broadcaster.clone(),
- logger: nodes[1].logger,
- channel_monitors,
- }).unwrap()
- };
- assert!(nodes_1_read.is_empty());
- nodes_1_deserialized = nodes_1_deserialized_tmp;
-
- assert!(nodes[1].chain_monitor.watch_channel(monitor_a.get_funding_txo().0, monitor_a).is_ok());
- assert!(nodes[1].chain_monitor.watch_channel(monitor_b.get_funding_txo().0, monitor_b).is_ok());
- check_added_monitors!(nodes[1], 2);
- nodes[1].node = &nodes_1_deserialized;
-
- nodes[0].node.peer_connected(&nodes[1].node.get_our_node_id(), &msgs::Init { features: InitFeatures::known() });
- nodes[1].node.peer_connected(&nodes[0].node.get_our_node_id(), &msgs::Init { features: InitFeatures::empty() });
- let as_reestablish = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, nodes[1].node.get_our_node_id());
- let bs_reestablish = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, nodes[0].node.get_our_node_id());
- nodes[1].node.handle_channel_reestablish(&nodes[0].node.get_our_node_id(), &as_reestablish);
- nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
- get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
- get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[0].node.get_our_node_id());
-
- nodes[1].node.peer_connected(&nodes[2].node.get_our_node_id(), &msgs::Init { features: InitFeatures::known() });
- nodes[2].node.peer_connected(&nodes[1].node.get_our_node_id(), &msgs::Init { features: InitFeatures::empty() });
- let bs_reestablish = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, nodes[2].node.get_our_node_id());
- let cs_reestablish = get_event_msg!(nodes[2], MessageSendEvent::SendChannelReestablish, nodes[1].node.get_our_node_id());
- nodes[2].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
- nodes[1].node.handle_channel_reestablish(&nodes[2].node.get_our_node_id(), &cs_reestablish);
- get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[2].node.get_our_node_id());
- get_event_msg!(nodes[2], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
-
- nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
- check_added_monitors!(nodes[0], 1);
- pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 10_000, our_payment_hash, our_payment_secret);
- claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], our_payment_preimage);
-}
-
#[test]
fn test_bump_penalty_txn_on_revoked_commitment() {
// In case of penalty txn with too low feerates for getting into mempools, RBF-bump them to be sure
// Lock HTLC in both directions (using a slightly lower CLTV delay to provide timely RBF bumps)
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()).with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
- let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph, None,
- 3_000_000, 50, nodes[0].logger, &scorer).unwrap();
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
+ let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None,
+ 3_000_000, 50, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
let payment_preimage = send_along_route(&nodes[0], route, &[&nodes[1]], 3_000_000).0;
let payment_params = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id()).with_features(InvoiceFeatures::known());
- let route = get_route(&nodes[1].node.get_our_node_id(), &payment_params, nodes[1].network_graph, None,
- 3_000_000, 50, nodes[0].logger, &scorer).unwrap();
+ let route = get_route(&nodes[1].node.get_our_node_id(), &payment_params, &nodes[1].network_graph.read_only(), None,
+ 3_000_000, 50, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
send_along_route(&nodes[1], route, &[&nodes[0]], 3_000_000);
let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan.2);
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
.with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = get_route(
- &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph,
+ &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(),
Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer).unwrap();
+ 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
let (_, our_payment_hash, our_payment_secret) = get_payment_preimage_hash!(&nodes[1]);
final_cltv_expiry_delta: 40,
};
let scorer = test_utils::TestScorer::with_penalty(0);
- let route = find_route(&payer_pubkey, &route_params, network_graph, None, nodes[0].logger, &scorer).unwrap();
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
+ let route = find_route(&payer_pubkey, &route_params, network_graph, None, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
let (payment_hash, _) = nodes[0].node.send_spontaneous_payment(&route, Some(test_preimage)).unwrap();
let network_graph = nodes[0].network_graph;
let first_hops = nodes[0].node.list_usable_channels();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
let route = find_route(
&payer_pubkey, &route_params, network_graph, Some(&first_hops.iter().collect::<Vec<_>>()),
- nodes[0].logger, &scorer
+ nodes[0].logger, &scorer, &random_seed_bytes
).unwrap();
let test_preimage = PaymentPreimage([42; 32]);
mod payment_tests;
#[cfg(test)]
#[allow(unused_mut)]
+mod priv_short_conf_tests;
+#[cfg(test)]
+#[allow(unused_mut)]
mod chanmon_update_fail_tests;
#[cfg(test)]
#[allow(unused_mut)]
do_test_claim_value_force_close(true);
do_test_claim_value_force_close(false);
}
+
+#[test]
+fn test_balances_on_local_commitment_htlcs() {
+ // Previously, when handling the broadcast of a local commitment transactions (with associated
+ // CSV delays prior to spendability), we incorrectly handled the CSV delays on HTLC
+ // transactions. This caused us to miss spendable outputs for HTLCs which were awaiting a CSV
+ // delay prior to spendability.
+ //
+ // Further, because of this, we could hit an assertion as `get_claimable_balances` asserted
+ // that HTLCs were resolved after the funding spend was resolved, which was not true if the
+ // HTLC did not have a CSV delay attached (due to the above bug or due to it being an HTLC
+ // claim by our counterparty).
+ 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 mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ // Create a single channel with two pending HTLCs from nodes[0] to nodes[1], one which nodes[1]
+ // knows the preimage for, one which it does not.
+ let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0, InitFeatures::known(), InitFeatures::known());
+ let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 };
+
+ let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[1], 10_000_000);
+ let htlc_cltv_timeout = nodes[0].best_block_info().1 + TEST_FINAL_CLTV + 1; // Note ChannelManager adds one to CLTV timeouts for safety
+ nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+
+ let updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], updates.commitment_signed, false);
+
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_payment_received!(nodes[1], payment_hash, payment_secret, 10_000_000);
+
+ let (route_2, payment_hash_2, payment_preimage_2, payment_secret_2) = get_route_and_payment_hash!(nodes[0], nodes[1], 20_000_000);
+ nodes[0].node.send_payment(&route_2, payment_hash_2, &Some(payment_secret_2)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+
+ let updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], updates.commitment_signed, false);
+
+ expect_pending_htlcs_forwardable!(nodes[1]);
+ expect_payment_received!(nodes[1], payment_hash_2, payment_secret_2, 20_000_000);
+ assert!(nodes[1].node.claim_funds(payment_preimage_2));
+ get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(nodes[1], 1);
+
+ let chan_feerate = get_feerate!(nodes[0], chan_id) as u64;
+ let opt_anchors = get_opt_anchors!(nodes[0], chan_id);
+
+ // Get nodes[0]'s commitment transaction and HTLC-Timeout transactions
+ let as_txn = get_local_commitment_txn!(nodes[0], chan_id);
+ assert_eq!(as_txn.len(), 3);
+ check_spends!(as_txn[1], as_txn[0]);
+ check_spends!(as_txn[2], as_txn[0]);
+ check_spends!(as_txn[0], funding_tx);
+
+ // First confirm the commitment transaction on nodes[0], which should leave us with three
+ // claimable balances.
+ let node_a_commitment_claimable = nodes[0].best_block_info().1 + BREAKDOWN_TIMEOUT as u32;
+ mine_transaction(&nodes[0], &as_txn[0]);
+ check_added_monitors!(nodes[0], 1);
+ check_closed_broadcast!(nodes[0], true);
+ check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
+
+ assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ confirmation_height: node_a_commitment_claimable,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 10_000,
+ claimable_height: htlc_cltv_timeout,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 20_000,
+ claimable_height: htlc_cltv_timeout,
+ }]),
+ sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
+
+ // Get nodes[1]'s HTLC claim tx for the second HTLC
+ mine_transaction(&nodes[1], &as_txn[0]);
+ check_added_monitors!(nodes[1], 1);
+ check_closed_broadcast!(nodes[1], true);
+ check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
+ let bs_htlc_claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(bs_htlc_claim_txn.len(), 3);
+ check_spends!(bs_htlc_claim_txn[0], as_txn[0]);
+ check_spends!(bs_htlc_claim_txn[1], funding_tx);
+ check_spends!(bs_htlc_claim_txn[2], bs_htlc_claim_txn[1]);
+
+ // Connect blocks until the HTLCs expire, allowing us to (validly) broadcast the HTLC-Timeout
+ // transaction.
+ connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1);
+ assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ confirmation_height: node_a_commitment_claimable,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 10_000,
+ claimable_height: htlc_cltv_timeout,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 20_000,
+ claimable_height: htlc_cltv_timeout,
+ }]),
+ sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
+ assert_eq!(as_txn[1].lock_time, nodes[0].best_block_info().1 + 1); // as_txn[1] can be included in the next block
+
+ // Now confirm nodes[0]'s HTLC-Timeout transaction, which changes the claimable balance to an
+ // "awaiting confirmations" one.
+ let node_a_htlc_claimable = nodes[0].best_block_info().1 + BREAKDOWN_TIMEOUT as u32;
+ mine_transaction(&nodes[0], &as_txn[1]);
+ // Note that prior to the fix in the commit which introduced this test, this (and the next
+ // balance) check failed. With this check removed, the code panicked in the `connect_blocks`
+ // call, as described, two hunks down.
+ assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ confirmation_height: node_a_commitment_claimable,
+ }, Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 10_000,
+ confirmation_height: node_a_htlc_claimable,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 20_000,
+ claimable_height: htlc_cltv_timeout,
+ }]),
+ sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
+
+ // Now confirm nodes[1]'s HTLC claim, giving nodes[0] the preimage. Note that the "maybe
+ // claimable" balance remains until we see ANTI_REORG_DELAY blocks.
+ mine_transaction(&nodes[0], &bs_htlc_claim_txn[0]);
+ expect_payment_sent!(nodes[0], payment_preimage_2);
+ assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ confirmation_height: node_a_commitment_claimable,
+ }, Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 10_000,
+ confirmation_height: node_a_htlc_claimable,
+ }, Balance::MaybeClaimableHTLCAwaitingTimeout {
+ claimable_amount_satoshis: 20_000,
+ claimable_height: htlc_cltv_timeout,
+ }]),
+ sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
+
+ // Finally make the HTLC transactions have ANTI_REORG_DELAY blocks. This call previously
+ // panicked as described in the test introduction. This will remove the "maybe claimable"
+ // spendable output as nodes[1] has fully claimed the second HTLC.
+ connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
+ expect_payment_failed!(nodes[0], payment_hash, true);
+
+ assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 1_000_000 - 10_000 - 20_000 - chan_feerate *
+ (channel::commitment_tx_base_weight(opt_anchors) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
+ confirmation_height: node_a_commitment_claimable,
+ }, Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 10_000,
+ confirmation_height: node_a_htlc_claimable,
+ }]),
+ sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
+
+ // Connect blocks until the commitment transaction's CSV expires, providing us the relevant
+ // `SpendableOutputs` event and removing the claimable balance entry.
+ connect_blocks(&nodes[0], node_a_commitment_claimable - nodes[0].best_block_info().1);
+ assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
+ claimable_amount_satoshis: 10_000,
+ confirmation_height: node_a_htlc_claimable,
+ }],
+ nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
+ let mut node_a_spendable = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ assert_eq!(node_a_spendable.len(), 1);
+ if let Event::SpendableOutputs { outputs } = node_a_spendable.pop().unwrap() {
+ assert_eq!(outputs.len(), 1);
+ let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
+ Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap();
+ check_spends!(spend_tx, as_txn[0]);
+ }
+
+ // Connect blocks until the HTLC-Timeout's CSV expires, providing us the relevant
+ // `SpendableOutputs` event and removing the claimable balance entry.
+ connect_blocks(&nodes[0], node_a_htlc_claimable - nodes[0].best_block_info().1);
+ assert!(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances().is_empty());
+ let mut node_a_spendable = nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events();
+ assert_eq!(node_a_spendable.len(), 1);
+ if let Event::SpendableOutputs { outputs } = node_a_spendable.pop().unwrap() {
+ assert_eq!(outputs.len(), 1);
+ let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(),
+ Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap();
+ check_spends!(spend_tx, as_txn[1]);
+ }
+}
pub channel_id: [u8; 32],
/// The per-commitment point of the second commitment transaction
pub next_per_commitment_point: PublicKey,
+ /// If set, provides a short_channel_id alias for this channel. The sender will accept payments
+ /// to be forwarded over this SCID and forward them to this messages' recipient.
+ pub short_channel_id_alias: Option<u64>,
}
/// A shutdown message to be sent or received from a peer
impl_writeable_msg!(FundingLocked, {
channel_id,
next_per_commitment_point,
-}, {});
+}, {
+ (1, short_channel_id_alias, option),
+});
impl Writeable for Init {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
impl Writeable for OnionHopData {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- // Note that this should never be reachable if Rust-Lightning generated the message, as we
- // check values are sane long before we get here, though its possible in the future
- // user-generated messages may hit this.
- if self.amt_to_forward > MAX_VALUE_MSAT { panic!("We should never be sending infinite/overflow onion payments"); }
match self.format {
OnionHopDataFormat::Legacy { short_channel_id } => {
0u8.write(w)?;
});
},
OnionHopDataFormat::FinalNode { ref payment_data, ref keysend_preimage } => {
- if let Some(final_data) = payment_data {
- if final_data.total_msat > MAX_VALUE_MSAT { panic!("We should never be sending infinite/overflow onion payments"); }
- }
encode_varint_length_prefixed_tlv!(w, {
(2, HighZeroBytesDroppedVarInt(self.amt_to_forward), required),
(4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value), required),
let funding_locked = msgs::FundingLocked {
channel_id: [2; 32],
next_per_commitment_point: pubkey_1,
+ short_channel_id_alias: None,
};
let encoded_value = funding_locked.encode();
let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f").unwrap();
//! returned errors decode to the correct thing.
use chain::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};
+use ln::channelmanager::{HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
use ln::onion_utils;
-use routing::network_graph::NetworkUpdate;
-use routing::router::Route;
-use ln::features::InitFeatures;
+use routing::network_graph::{NetworkUpdate, RoutingFees};
+use routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop};
+use ln::features::{InitFeatures, InvoiceFeatures};
use ln::msgs;
use ln::msgs::{ChannelMessageHandler, ChannelUpdate, OptionalField};
use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
use util::ser::{Writeable, Writer};
+use util::{byte_utils, test_utils};
use util::config::UserConfig;
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::Hash;
+use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::secp256k1;
use bitcoin::secp256k1::Secp256k1;
-use bitcoin::secp256k1::key::SecretKey;
+use bitcoin::secp256k1::key::{PublicKey, SecretKey};
use io;
use prelude::*;
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(config), Some(config), Some(node_2_cfg)]);
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+ let channels = [create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()), create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known())];
for node in nodes.iter() {
- *node.keys_manager.override_session_priv.lock().unwrap() = Some([3; 32]);
+ *node.keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]);
}
- let channels = [create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()), create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known())];
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 40000);
// positive case
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000);
nodes[2].node.fail_htlc_backwards(&payment_hash);
}, true, Some(23), None, None);
}
+
+macro_rules! get_phantom_route {
+ ($nodes: expr, $amt: expr, $channel: expr) => {{
+ let secp_ctx = Secp256k1::new();
+ let phantom_secret = $nodes[1].keys_manager.get_node_secret(Recipient::PhantomNode).unwrap();
+ let phantom_pubkey = PublicKey::from_secret_key(&secp_ctx, &phantom_secret);
+ let phantom_route_hint = $nodes[1].node.get_phantom_route_hints();
+ let payment_params = PaymentParameters::from_node_id(phantom_pubkey)
+ .with_features(InvoiceFeatures::known())
+ .with_route_hints(vec![RouteHint(vec![
+ RouteHintHop {
+ src_node_id: $nodes[0].node.get_our_node_id(),
+ short_channel_id: $channel.0.contents.short_channel_id,
+ fees: RoutingFees {
+ base_msat: $channel.0.contents.fee_base_msat,
+ proportional_millionths: $channel.0.contents.fee_proportional_millionths,
+ },
+ cltv_expiry_delta: $channel.0.contents.cltv_expiry_delta,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ },
+ RouteHintHop {
+ src_node_id: phantom_route_hint.real_node_pubkey,
+ short_channel_id: phantom_route_hint.phantom_scid,
+ fees: RoutingFees {
+ base_msat: 0,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ }
+ ])]);
+ let scorer = test_utils::TestScorer::with_penalty(0);
+ let network_graph = $nodes[0].network_graph.read_only();
+ (get_route(
+ &$nodes[0].node.get_our_node_id(), &payment_params, &network_graph,
+ Some(&$nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
+ $amt, TEST_FINAL_CLTV, $nodes[0].logger, &scorer, &[0u8; 32]
+ ).unwrap(), phantom_route_hint.phantom_scid)
+ }
+}}
+
+#[test]
+fn test_phantom_onion_hmac_failure() {
+ 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 channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route.
+ let recv_value_msat = 10_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat));
+ let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel);
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ // Modify the payload so the phantom hop's HMAC is bogus.
+ let sha256_of_onion = {
+ let mut channel_state = nodes[1].node.channel_state.lock().unwrap();
+ let mut pending_forward = channel_state.forward_htlcs.get_mut(&phantom_scid).unwrap();
+ match pending_forward[0] {
+ HTLCForwardInfo::AddHTLC {
+ forward_info: PendingHTLCInfo {
+ routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. },
+ ..
+ }, ..
+ } => {
+ onion_packet.hmac[onion_packet.hmac.len() - 1] ^= 1;
+ Sha256::hash(&onion_packet.hop_data).into_inner().to_vec()
+ },
+ _ => panic!("Unexpected forward"),
+ }
+ };
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .blamed_chan_closed(true)
+ .expected_htlc_error_data(0x8000 | 0x4000 | 5, &sha256_of_onion);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, false, fail_conditions);
+}
+
+#[test]
+fn test_phantom_invalid_onion_payload() {
+ 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 mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route.
+ let recv_value_msat = 10_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat));
+ let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel);
+
+ // We'll use the session priv later when constructing an invalid onion packet.
+ let session_priv = [3; 32];
+ *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(session_priv);
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ // Modify the onion packet to have an invalid payment amount.
+ for (_, pending_forwards) in nodes[1].node.channel_state.lock().unwrap().forward_htlcs.iter_mut() {
+ for f in pending_forwards.iter_mut() {
+ match f {
+ &mut HTLCForwardInfo::AddHTLC {
+ forward_info: PendingHTLCInfo {
+ routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. },
+ ..
+ }, ..
+ } => {
+ // Construct the onion payloads for the entire route and an invalid amount.
+ let height = nodes[0].best_block_info().1;
+ let session_priv = SecretKey::from_slice(&session_priv).unwrap();
+ let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap();
+ let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], msgs::MAX_VALUE_MSAT + 1, &Some(payment_secret), height + 1, &None).unwrap();
+ // We only want to construct the onion packet for the last hop, not the entire route, so
+ // remove the first hop's payload and its keys.
+ onion_keys.remove(0);
+ onion_payloads.remove(0);
+
+ let new_onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
+ onion_packet.hop_data = new_onion_packet.hop_data;
+ onion_packet.hmac = new_onion_packet.hmac;
+ },
+ _ => panic!("Unexpected forward"),
+ }
+ }
+ }
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let error_data = Vec::new();
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .blamed_chan_closed(true)
+ .expected_htlc_error_data(0x4000 | 22, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, true, fail_conditions);
+}
+
+#[test]
+fn test_phantom_final_incorrect_cltv_expiry() {
+ 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 channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route.
+ let recv_value_msat = 10_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat));
+ let (route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel);
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ // Modify the payload so the phantom hop's HMAC is bogus.
+ for (_, pending_forwards) in nodes[1].node.channel_state.lock().unwrap().forward_htlcs.iter_mut() {
+ for f in pending_forwards.iter_mut() {
+ match f {
+ &mut HTLCForwardInfo::AddHTLC {
+ forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, ..
+ } => {
+ *outgoing_cltv_value += 1;
+ },
+ _ => panic!("Unexpected forward"),
+ }
+ }
+ }
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let expected_cltv = 82;
+ let error_data = byte_utils::be32_to_array(expected_cltv).to_vec();
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .expected_htlc_error_data(18, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, false, fail_conditions);
+}
+
+#[test]
+fn test_phantom_failure_too_low_cltv() {
+ 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 channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route.
+ let recv_value_msat = 10_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_value_msat));
+ let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_value_msat, channel);
+
+ // Modify the route to have a too-low cltv.
+ route.paths[0][1].cltv_expiry_delta = 5;
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let error_data = Vec::new();
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .expected_htlc_error_data(17, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, false, fail_conditions);
+}
+
+#[test]
+fn test_phantom_failure_too_low_recv_amt() {
+ 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 channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route with a too-low amount.
+ let recv_amt_msat = 10_000;
+ let bad_recv_amt_msat = recv_amt_msat - 10;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat));
+ let (mut route, phantom_scid) = get_phantom_route!(nodes, bad_recv_amt_msat, channel);
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let mut error_data = byte_utils::be64_to_array(bad_recv_amt_msat).to_vec();
+ error_data.extend_from_slice(
+ &byte_utils::be32_to_array(nodes[1].node.best_block.read().unwrap().height()),
+ );
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .expected_htlc_error_data(0x4000 | 15, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, true, fail_conditions);
+}
+
+#[test]
+fn test_phantom_dust_exposure_failure() {
+ // Set the max dust exposure to the dust limit.
+ let max_dust_exposure = 546;
+ let mut receiver_config = UserConfig::default();
+ receiver_config.channel_options.max_dust_htlc_exposure_msat = max_dust_exposure;
+ receiver_config.channel_options.announced_channel = true;
+
+ 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, Some(receiver_config)]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route with an amount exceeding the dust exposure threshold of nodes[1].
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(max_dust_exposure + 1));
+ let (mut route, _) = get_phantom_route!(nodes, max_dust_exposure + 1, channel);
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let mut error_data = channel.1.encode_with_len();
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(channel.0.contents.short_channel_id)
+ .blamed_chan_closed(false)
+ .expected_htlc_error_data(0x1000 | 7, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, false, fail_conditions);
+}
+
+#[test]
+fn test_phantom_failure_reject_payment() {
+ // Test that the user can successfully fail back a phantom node payment.
+ 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 channel = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
+
+ // Get the route with a too-low amount.
+ let recv_amt_msat = 10_000;
+ let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1], Some(recv_amt_msat));
+ let (mut route, phantom_scid) = get_phantom_route!(nodes, recv_amt_msat, channel);
+
+ // Route the HTLC through to the destination.
+ nodes[0].node.send_payment(&route, payment_hash.clone(), &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
+ let mut update_add = update_0.update_add_htlcs[0].clone();
+
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
+ commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
+
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+ expect_payment_received!(nodes[1], payment_hash, payment_secret, recv_amt_msat);
+ assert!(nodes[1].node.fail_htlc_backwards(&payment_hash));
+ expect_pending_htlcs_forwardable_ignore!(nodes[1]);
+ nodes[1].node.process_pending_htlc_forwards();
+
+ let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ check_added_monitors!(&nodes[1], 1);
+ assert!(update_1.update_fail_htlcs.len() == 1);
+ let fail_msg = update_1.update_fail_htlcs[0].clone();
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
+ commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
+
+ // Ensure the payment fails with the expected error.
+ let mut error_data = byte_utils::be64_to_array(recv_amt_msat).to_vec();
+ error_data.extend_from_slice(
+ &byte_utils::be32_to_array(nodes[1].node.best_block.read().unwrap().height()),
+ );
+ let mut fail_conditions = PaymentFailedConditions::new()
+ .blamed_scid(phantom_scid)
+ .expected_htlc_error_data(0x4000 | 15, &error_data);
+ expect_payment_failed_conditions!(nodes[0], payment_hash, true, fail_conditions);
+}
+
use chain::{ChannelMonitorUpdateErr, Confirm, Listen, Watch};
use chain::channelmonitor::{ANTI_REORG_DELAY, ChannelMonitor, LATENCY_GRACE_PERIOD_BLOCKS};
use chain::transaction::OutPoint;
+use chain::keysinterface::KeysInterface;
use ln::channelmanager::{BREAKDOWN_TIMEOUT, ChannelManager, ChannelManagerReadArgs, PaymentId, PaymentSendFailure};
use ln::features::{InitFeatures, InvoiceFeatures};
use ln::msgs;
use io;
use bitcoin::{Block, BlockHeader, BlockHash};
+use bitcoin::network::constants::Network;
use prelude::*;
check_spends!(bs_htlc_claim_txn[0], as_commitment_tx);
expect_payment_forwarded!(nodes[1], None, false);
- mine_transaction(&nodes[0], &as_commitment_tx);
+ if !confirm_before_reload {
+ mine_transaction(&nodes[0], &as_commitment_tx);
+ }
mine_transaction(&nodes[0], &bs_htlc_claim_txn[0]);
expect_payment_sent!(nodes[0], payment_preimage_1);
connect_blocks(&nodes[0], TEST_FINAL_CLTV*4 + 20);
check_added_monitors!(nodes[1], 1);
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
let claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
+ assert_eq!(claim_txn.len(), 3);
+ check_spends!(claim_txn[0], node_txn[1]);
+ check_spends!(claim_txn[1], funding_tx);
+ check_spends!(claim_txn[2], claim_txn[1]);
header.prev_blockhash = nodes[0].best_block_hash();
connect_block(&nodes[0], &Block { header, txdata: vec![node_txn[1].clone()]});
}
header.prev_blockhash = nodes[0].best_block_hash();
- let claim_block = Block { header, txdata: if payment_timeout { timeout_txn } else { claim_txn } };
+ let claim_block = Block { header, txdata: if payment_timeout { timeout_txn } else { vec![claim_txn[0].clone()] } };
if payment_timeout {
assert!(confirm_commitment_tx); // Otherwise we're spending below our CSV!
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
.with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = get_route(
- &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph,
+ &nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(),
Some(&nodes[0].node.list_usable_channels().iter().collect::<Vec<_>>()),
- amt_msat, TEST_FINAL_CLTV, nodes[0].logger, &scorer).unwrap();
+ amt_msat, TEST_FINAL_CLTV, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
let _payment_id = nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap();
check_added_monitors!(nodes[0], 1);
/// Thus, to avoid needlessly disconnecting a peer, we allow a peer to take this many timer ticks
/// per connected peer to respond to a ping, as long as they send us at least one message during
/// each tick, ensuring we aren't actually just disconnected.
-/// With a timer tick interval of five seconds, this translates to about 30 seconds per connected
+/// With a timer tick interval of ten seconds, this translates to about 40 seconds per connected
/// peer.
///
/// When we improve parallelism somewhat we should reduce this to e.g. this many timer ticks per
/// two connected peers, assuming most LDK-running systems have at least two cores.
-const MAX_BUFFER_DRAIN_TICK_INTERVALS_PER_PEER: i8 = 6;
+const MAX_BUFFER_DRAIN_TICK_INTERVALS_PER_PEER: i8 = 4;
/// This is the minimum number of messages we expect a peer to be able to handle within one timer
/// tick. Once we have sent this many messages since the last ping, we send a ping right away to
/// Send pings to each peer and disconnect those which did not respond to the last round of
/// pings.
///
- /// This may be called on any timescale you want, however, roughly once every five to ten
- /// seconds is preferred. The call rate determines both how often we send a ping to our peers
- /// and how much time they have to respond before we disconnect them.
+ /// This may be called on any timescale you want, however, roughly once every ten seconds is
+ /// preferred. The call rate determines both how often we send a ping to our peers and how much
+ /// time they have to respond before we disconnect them.
///
/// May call [`send_data`] on all [`SocketDescriptor`]s. Thus, be very careful with reentrancy
/// issues!
--- /dev/null
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Tests that test ChannelManager behavior with fewer confirmations required than the default and
+//! other behavior that exists only on private channels or with a semi-trusted counterparty (eg
+//! LSP).
+
+use chain::Watch;
+use chain::channelmonitor::ChannelMonitor;
+use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, MIN_CLTV_EXPIRY_DELTA};
+use routing::network_graph::RoutingFees;
+use routing::router::{RouteHint, RouteHintHop};
+use ln::features::InitFeatures;
+use ln::msgs;
+use ln::msgs::{ChannelMessageHandler, RoutingMessageHandler};
+use util::enforcing_trait_impls::EnforcingSigner;
+use util::events::{Event, MessageSendEvent, MessageSendEventsProvider};
+use util::config::UserConfig;
+use util::ser::{Writeable, ReadableArgs};
+use util::test_utils;
+
+use prelude::*;
+use core::default::Default;
+
+use ln::functional_test_utils::*;
+
+use bitcoin::hash_types::BlockHash;
+
+#[test]
+fn test_priv_forwarding_rejection() {
+ // If we have a private channel with outbound liquidity, and
+ // UserConfig::accept_forwards_to_priv_channels is set to false, we should reject any attempts
+ // to forward through that channel.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let mut no_announce_cfg = test_default_channel_config();
+ no_announce_cfg.accept_forwards_to_priv_channels = false;
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(no_announce_cfg), None]);
+ let persister: test_utils::TestPersister;
+ let new_chain_monitor: test_utils::TestChainMonitor;
+ let nodes_1_deserialized: ChannelManager<EnforcingSigner, &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>;
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ let chan_id_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known()).2;
+ let chan_id_2 = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known()).0.channel_id;
+
+ // We should always be able to forward through nodes[1] as long as its out through a public
+ // channel:
+ send_payment(&nodes[2], &[&nodes[1], &nodes[0]], 10_000);
+
+ // ... however, if we send to nodes[2], we will have to pass the private channel from nodes[1]
+ // to nodes[2], which should be rejected:
+ let route_hint = RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[1].node.get_our_node_id(),
+ short_channel_id: nodes[2].node.list_channels()[0].short_channel_id.unwrap(),
+ fees: RoutingFees { base_msat: 1000, proportional_millionths: 0 },
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ }]);
+ let last_hops = vec![route_hint];
+ let (route, our_payment_hash, our_payment_preimage, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], last_hops, 10_000, TEST_FINAL_CLTV);
+
+ nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ let payment_event = SendEvent::from_event(nodes[0].node.get_and_clear_pending_msg_events().remove(0));
+ nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
+ commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false, true);
+
+ let htlc_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
+ assert!(htlc_fail_updates.update_add_htlcs.is_empty());
+ assert_eq!(htlc_fail_updates.update_fail_htlcs.len(), 1);
+ assert!(htlc_fail_updates.update_fail_malformed_htlcs.is_empty());
+ assert!(htlc_fail_updates.update_fee.is_none());
+
+ nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_fail_updates.update_fail_htlcs[0]);
+ commitment_signed_dance!(nodes[0], nodes[1], htlc_fail_updates.commitment_signed, true, true);
+ expect_payment_failed_with_update!(nodes[0], our_payment_hash, false, nodes[2].node.list_channels()[0].short_channel_id.unwrap(), true);
+
+ // Now disconnect nodes[1] from its peers and restart with accept_forwards_to_priv_channels set
+ // to true. Sadly there is currently no way to change it at runtime.
+
+ nodes[0].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
+ nodes[2].node.peer_disconnected(&nodes[1].node.get_our_node_id(), false);
+
+ let nodes_1_serialized = nodes[1].node.encode();
+ let mut monitor_a_serialized = test_utils::TestVecWriter(Vec::new());
+ let mut monitor_b_serialized = test_utils::TestVecWriter(Vec::new());
+ get_monitor!(nodes[1], chan_id_1).write(&mut monitor_a_serialized).unwrap();
+ get_monitor!(nodes[1], chan_id_2).write(&mut monitor_b_serialized).unwrap();
+
+ persister = test_utils::TestPersister::new();
+ let keys_manager = &chanmon_cfgs[1].keys_manager;
+ new_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, keys_manager);
+ nodes[1].chain_monitor = &new_chain_monitor;
+
+ let mut monitor_a_read = &monitor_a_serialized.0[..];
+ let mut monitor_b_read = &monitor_b_serialized.0[..];
+ let (_, mut monitor_a) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut monitor_a_read, keys_manager).unwrap();
+ let (_, mut monitor_b) = <(BlockHash, ChannelMonitor<EnforcingSigner>)>::read(&mut monitor_b_read, keys_manager).unwrap();
+ assert!(monitor_a_read.is_empty());
+ assert!(monitor_b_read.is_empty());
+
+ no_announce_cfg.accept_forwards_to_priv_channels = true;
+
+ let mut nodes_1_read = &nodes_1_serialized[..];
+ let (_, nodes_1_deserialized_tmp) = {
+ let mut channel_monitors = HashMap::new();
+ channel_monitors.insert(monitor_a.get_funding_txo().0, &mut monitor_a);
+ channel_monitors.insert(monitor_b.get_funding_txo().0, &mut monitor_b);
+ <(BlockHash, ChannelManager<EnforcingSigner, &test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestLogger>)>::read(&mut nodes_1_read, ChannelManagerReadArgs {
+ default_config: no_announce_cfg,
+ keys_manager,
+ fee_estimator: node_cfgs[1].fee_estimator,
+ chain_monitor: nodes[1].chain_monitor,
+ tx_broadcaster: nodes[1].tx_broadcaster.clone(),
+ logger: nodes[1].logger,
+ channel_monitors,
+ }).unwrap()
+ };
+ assert!(nodes_1_read.is_empty());
+ nodes_1_deserialized = nodes_1_deserialized_tmp;
+
+ assert!(nodes[1].chain_monitor.watch_channel(monitor_a.get_funding_txo().0, monitor_a).is_ok());
+ assert!(nodes[1].chain_monitor.watch_channel(monitor_b.get_funding_txo().0, monitor_b).is_ok());
+ check_added_monitors!(nodes[1], 2);
+ nodes[1].node = &nodes_1_deserialized;
+
+ nodes[0].node.peer_connected(&nodes[1].node.get_our_node_id(), &msgs::Init { features: InitFeatures::known() });
+ nodes[1].node.peer_connected(&nodes[0].node.get_our_node_id(), &msgs::Init { features: InitFeatures::empty() });
+ let as_reestablish = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReestablish, nodes[1].node.get_our_node_id());
+ let bs_reestablish = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, nodes[0].node.get_our_node_id());
+ nodes[1].node.handle_channel_reestablish(&nodes[0].node.get_our_node_id(), &as_reestablish);
+ nodes[0].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
+ get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
+ get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[0].node.get_our_node_id());
+
+ nodes[1].node.peer_connected(&nodes[2].node.get_our_node_id(), &msgs::Init { features: InitFeatures::known() });
+ nodes[2].node.peer_connected(&nodes[1].node.get_our_node_id(), &msgs::Init { features: InitFeatures::empty() });
+ let bs_reestablish = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReestablish, nodes[2].node.get_our_node_id());
+ let cs_reestablish = get_event_msg!(nodes[2], MessageSendEvent::SendChannelReestablish, nodes[1].node.get_our_node_id());
+ nodes[2].node.handle_channel_reestablish(&nodes[1].node.get_our_node_id(), &bs_reestablish);
+ nodes[1].node.handle_channel_reestablish(&nodes[2].node.get_our_node_id(), &cs_reestablish);
+ get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[2].node.get_our_node_id());
+ get_event_msg!(nodes[2], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
+
+ nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+ pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 10_000, our_payment_hash, our_payment_secret);
+ claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], our_payment_preimage);
+}
+
+fn do_test_1_conf_open(connect_style: ConnectStyle) {
+ // Previously, if the minium_depth config was set to 1, we'd never send a funding_locked. This
+ // tests that we properly send one in that case.
+ let mut alice_config = UserConfig::default();
+ alice_config.own_channel_config.minimum_depth = 1;
+ alice_config.channel_options.announced_channel = true;
+ alice_config.peer_channel_config_limits.force_announced_channel_preference = false;
+ let mut bob_config = UserConfig::default();
+ bob_config.own_channel_config.minimum_depth = 1;
+ bob_config.channel_options.announced_channel = true;
+ bob_config.peer_channel_config_limits.force_announced_channel_preference = false;
+ 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, &[Some(alice_config), Some(bob_config)]);
+ let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+ *nodes[0].connect_style.borrow_mut() = connect_style;
+
+ let tx = create_chan_between_nodes_with_value_init(&nodes[0], &nodes[1], 100000, 10001, InitFeatures::known(), InitFeatures::known());
+ mine_transaction(&nodes[1], &tx);
+ nodes[0].node.handle_funding_locked(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendFundingLocked, nodes[0].node.get_our_node_id()));
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+
+ mine_transaction(&nodes[0], &tx);
+ let as_msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(as_msg_events.len(), 2);
+ let as_funding_locked = if let MessageSendEvent::SendFundingLocked { ref node_id, ref msg } = as_msg_events[0] {
+ assert_eq!(*node_id, nodes[1].node.get_our_node_id());
+ msg.clone()
+ } else { panic!("Unexpected event"); };
+ if let MessageSendEvent::SendChannelUpdate { ref node_id, msg: _ } = as_msg_events[1] {
+ assert_eq!(*node_id, nodes[1].node.get_our_node_id());
+ } else { panic!("Unexpected event"); }
+
+ nodes[1].node.handle_funding_locked(&nodes[0].node.get_our_node_id(), &as_funding_locked);
+ let bs_msg_events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(bs_msg_events.len(), 1);
+ if let MessageSendEvent::SendChannelUpdate { ref node_id, msg: _ } = bs_msg_events[0] {
+ assert_eq!(*node_id, nodes[0].node.get_our_node_id());
+ } else { panic!("Unexpected event"); }
+
+ send_payment(&nodes[0], &[&nodes[1]], 100_000);
+
+ // After 6 confirmations, as required by the spec, we'll send announcement_signatures and
+ // broadcast the channel_announcement (but not before exactly 6 confirmations).
+ connect_blocks(&nodes[0], 4);
+ assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
+ connect_blocks(&nodes[0], 1);
+ nodes[1].node.handle_announcement_signatures(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendAnnouncementSignatures, nodes[1].node.get_our_node_id()));
+ assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
+
+ connect_blocks(&nodes[1], 5);
+ let bs_announce_events = nodes[1].node.get_and_clear_pending_msg_events();
+ assert_eq!(bs_announce_events.len(), 2);
+ let bs_announcement_sigs = if let MessageSendEvent::SendAnnouncementSignatures { ref node_id, ref msg } = bs_announce_events[0] {
+ assert_eq!(*node_id, nodes[0].node.get_our_node_id());
+ msg.clone()
+ } else { panic!("Unexpected event"); };
+ let (bs_announcement, bs_update) = if let MessageSendEvent::BroadcastChannelAnnouncement { ref msg, ref update_msg } = bs_announce_events[1] {
+ (msg.clone(), update_msg.clone())
+ } else { panic!("Unexpected event"); };
+
+ nodes[0].node.handle_announcement_signatures(&nodes[1].node.get_our_node_id(), &bs_announcement_sigs);
+ let as_announce_events = nodes[0].node.get_and_clear_pending_msg_events();
+ assert_eq!(as_announce_events.len(), 1);
+ let (announcement, as_update) = if let MessageSendEvent::BroadcastChannelAnnouncement { ref msg, ref update_msg } = as_announce_events[0] {
+ (msg.clone(), update_msg.clone())
+ } else { panic!("Unexpected event"); };
+ assert_eq!(announcement, bs_announcement);
+
+ for node in nodes {
+ assert!(node.net_graph_msg_handler.handle_channel_announcement(&announcement).unwrap());
+ node.net_graph_msg_handler.handle_channel_update(&as_update).unwrap();
+ node.net_graph_msg_handler.handle_channel_update(&bs_update).unwrap();
+ }
+}
+#[test]
+fn test_1_conf_open() {
+ do_test_1_conf_open(ConnectStyle::BestBlockFirst);
+ do_test_1_conf_open(ConnectStyle::TransactionsFirst);
+ do_test_1_conf_open(ConnectStyle::FullBlockViaListen);
+}
+
+#[test]
+fn test_routed_scid_alias() {
+ // Trivially test sending a payment which is routed through an SCID alias.
+ let chanmon_cfgs = create_chanmon_cfgs(3);
+ let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
+ let mut no_announce_cfg = test_default_channel_config();
+ no_announce_cfg.accept_forwards_to_priv_channels = true;
+ let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(no_announce_cfg), None]);
+ let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
+
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known()).2;
+ let mut as_funding_locked = create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 500_000_000, InitFeatures::known(), InitFeatures::known()).0;
+
+ let last_hop = nodes[2].node.list_usable_channels();
+ let hop_hints = vec![RouteHint(vec![RouteHintHop {
+ src_node_id: nodes[1].node.get_our_node_id(),
+ short_channel_id: last_hop[0].inbound_scid_alias.unwrap(),
+ fees: RoutingFees {
+ base_msat: last_hop[0].counterparty.forwarding_info.as_ref().unwrap().fee_base_msat,
+ proportional_millionths: last_hop[0].counterparty.forwarding_info.as_ref().unwrap().fee_proportional_millionths,
+ },
+ cltv_expiry_delta: last_hop[0].counterparty.forwarding_info.as_ref().unwrap().cltv_expiry_delta,
+ htlc_maximum_msat: None,
+ htlc_minimum_msat: None,
+ }])];
+ let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], hop_hints, 100_000, 42);
+ assert_eq!(route.paths[0][1].short_channel_id, last_hop[0].inbound_scid_alias.unwrap());
+ nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap();
+ check_added_monitors!(nodes[0], 1);
+
+ pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], 100_000, payment_hash, payment_secret);
+ claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
+
+ // Now test that if a peer sends us a second funding_locked after the channel is operational we
+ // will use the new alias.
+ as_funding_locked.short_channel_id_alias = Some(0xdeadbeef);
+ nodes[2].node.handle_funding_locked(&nodes[1].node.get_our_node_id(), &as_funding_locked);
+ // Note that we always respond to a funding_locked with a channel_update. Not a lot of reason
+ // to bother updating that code, so just drop the message here.
+ get_event_msg!(nodes[2], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
+ let updated_channel_info = nodes[2].node.list_usable_channels();
+ assert_eq!(updated_channel_info.len(), 1);
+ assert_eq!(updated_channel_info[0].inbound_scid_alias.unwrap(), 0xdeadbeef);
+ // Note that because we never send a duplicate funding_locked we can't send a payment through
+ // the 0xdeadbeef SCID alias.
+}
vec![node_1_commitment_txn[0].clone(), node_2_commitment_txn[0].clone()]
} else {
// Broadcast node 2 commitment txn
- let node_2_commitment_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
+ let mut node_2_commitment_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
assert_eq!(node_2_commitment_txn.len(), 2); // 1 local commitment tx, 1 Received HTLC-Claim
assert_eq!(node_2_commitment_txn[0].output.len(), 2); // to-remote and Received HTLC (to-self is dust)
check_spends!(node_2_commitment_txn[0], chan_2.3);
check_spends!(node_1_commitment_txn[0], chan_2.3);
check_spends!(node_1_commitment_txn[1], node_2_commitment_txn[0]);
- // Confirm node 2's commitment txn (and node 1's HTLC-Timeout) on node 1
- header.prev_blockhash = nodes[1].best_block_hash();
- let block = Block { header, txdata: vec![node_2_commitment_txn[0].clone(), node_1_commitment_txn[1].clone()] };
- connect_block(&nodes[1], &block);
+ // Confirm node 1's HTLC-Timeout on node 1
+ mine_transaction(&nodes[1], &node_1_commitment_txn[1]);
// ...but return node 2's commitment tx (and claim) in case claim is set and we're preparing to reorg
- node_2_commitment_txn
+ vec![node_2_commitment_txn.pop().unwrap()]
};
check_added_monitors!(nodes[1], 1);
check_closed_broadcast!(nodes[1], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate)
let channel_state = nodes[0].node.channel_state.lock().unwrap();
assert_eq!(channel_state.by_id.len(), 1);
- assert_eq!(channel_state.short_to_id.len(), 1);
+ assert_eq!(channel_state.short_to_id.len(), 2);
mem::drop(channel_state);
if !reorg_after_reload {
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::opcodes;
+use bitcoin::network::constants::Network;
use regex;
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known());
let logger = test_utils::TestLogger::new();
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let (payment_preimage, _, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100000);
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[0]);
let payment_params_1 = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()).with_features(InvoiceFeatures::known());
- let route_1 = get_route(&nodes[0].node.get_our_node_id(), &payment_params_1, nodes[0].network_graph, None, 100000, TEST_FINAL_CLTV, &logger, &scorer).unwrap();
+ let route_1 = get_route(&nodes[0].node.get_our_node_id(), &payment_params_1, &nodes[0].network_graph.read_only(), None, 100000, TEST_FINAL_CLTV, &logger, &scorer, &random_seed_bytes).unwrap();
let payment_params_2 = PaymentParameters::from_node_id(nodes[0].node.get_our_node_id()).with_features(InvoiceFeatures::known());
- let route_2 = get_route(&nodes[1].node.get_our_node_id(), &payment_params_2, nodes[1].network_graph, None, 100000, TEST_FINAL_CLTV, &logger, &scorer).unwrap();
+ let route_2 = get_route(&nodes[1].node.get_our_node_id(), &payment_params_2, &nodes[1].network_graph.read_only(), None, 100000, TEST_FINAL_CLTV, &logger, &scorer, &random_seed_bytes).unwrap();
unwrap_send_err!(nodes[0].node.send_payment(&route_1, payment_hash, &Some(payment_secret)), true, APIError::ChannelUnavailable {..}, {});
unwrap_send_err!(nodes[1].node.send_payment(&route_2, payment_hash, &Some(payment_secret)), true, APIError::ChannelUnavailable {..}, {});
};
Some((DirectedChannelInfo { channel: self, direction }, source))
}
+
+ /// Returns a [`DirectedChannelInfo`] for the channel directed from the given `source` to a
+ /// returned `target`, or `None` if `source` is not one of the channel's counterparties.
+ pub fn as_directed_from(&self, source: &NodeId) -> Option<(DirectedChannelInfo, &NodeId)> {
+ let (direction, target) = {
+ if source == &self.node_one {
+ (self.one_to_two.as_ref(), &self.node_two)
+ } else if source == &self.node_two {
+ (self.two_to_one.as_ref(), &self.node_one)
+ } else {
+ return None;
+ }
+ };
+ Some((DirectedChannelInfo { channel: self, direction }, target))
+ }
}
impl fmt::Display for ChannelInfo {
use ln::features::{ChannelFeatures, InvoiceFeatures, NodeFeatures};
use ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
use routing::scoring::Score;
-use routing::network_graph::{DirectedChannelInfoWithUpdate, EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
+use routing::network_graph::{DirectedChannelInfoWithUpdate, EffectiveCapacity, NetworkGraph, ReadOnlyNetworkGraph, NodeId, RoutingFees};
use util::ser::{Writeable, Readable};
use util::logger::{Level, Logger};
+use util::chacha20::ChaCha20;
use io;
use prelude::*;
/// Maximum total CTLV difference we allow for a full payment path.
pub const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008;
+/// The median hop CLTV expiry delta currently seen in the network.
+const MEDIAN_HOP_CLTV_EXPIRY_DELTA: u32 = 40;
+
/// The recipient of a payment.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct PaymentParameters {
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct RouteHint(pub Vec<RouteHintHop>);
-
impl Writeable for RouteHint {
fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
(self.0.len() as u64).write(writer)?;
/// so that we can choose cheaper paths (as per Dijkstra's algorithm).
/// Fee values should be updated only in the context of the whole path, see update_value_and_recompute_fees.
/// These fee values are useful to choose hops as we traverse the graph "payee-to-payer".
-#[derive(Clone, Debug)]
+#[derive(Clone)]
struct PathBuildingHop<'a> {
// Note that this should be dropped in favor of loading it from CandidateRouteHop, but doing so
// is a larger refactor and will require careful performance analysis.
value_contribution_msat: u64,
}
+impl<'a> core::fmt::Debug for PathBuildingHop<'a> {
+ fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+ f.debug_struct("PathBuildingHop")
+ .field("node_id", &self.node_id)
+ .field("short_channel_id", &self.candidate.short_channel_id())
+ .field("total_fee_msat", &self.total_fee_msat)
+ .field("next_hops_fee_msat", &self.next_hops_fee_msat)
+ .field("hop_use_fee_msat", &self.hop_use_fee_msat)
+ .field("total_fee_msat - (next_hops_fee_msat + hop_use_fee_msat)", &(&self.total_fee_msat - (&self.next_hops_fee_msat + &self.hop_use_fee_msat)))
+ .field("path_penalty_msat", &self.path_penalty_msat)
+ .field("path_htlc_minimum_msat", &self.path_htlc_minimum_msat)
+ .field("cltv_expiry_delta", &self.candidate.cltv_expiry_delta())
+ .finish()
+ }
+}
+
// Instantiated with a list of hops with correct data in them collected during path finding,
// an instance of this struct should be further modified only via given methods.
#[derive(Clone)]
/// [`Event::PaymentPathFailed`]: crate::util::events::Event::PaymentPathFailed
pub fn find_route<L: Deref, S: Score>(
our_node_pubkey: &PublicKey, route_params: &RouteParameters, network: &NetworkGraph,
- first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S
+ first_hops: Option<&[&ChannelDetails]>, logger: L, scorer: &S, random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger {
- get_route(
- our_node_pubkey, &route_params.payment_params, network, first_hops, route_params.final_value_msat,
- route_params.final_cltv_expiry_delta, logger, scorer
- )
+ let network_graph = network.read_only();
+ match get_route(
+ our_node_pubkey, &route_params.payment_params, &network_graph, first_hops, route_params.final_value_msat,
+ route_params.final_cltv_expiry_delta, logger, scorer, random_seed_bytes
+ ) {
+ Ok(mut route) => {
+ add_random_cltv_offset(&mut route, &route_params.payment_params, &network_graph, random_seed_bytes);
+ Ok(route)
+ },
+ Err(err) => Err(err),
+ }
}
pub(crate) fn get_route<L: Deref, S: Score>(
- our_node_pubkey: &PublicKey, payment_params: &PaymentParameters, network: &NetworkGraph,
- first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32,
- logger: L, scorer: &S
+ our_node_pubkey: &PublicKey, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph,
+ first_hops: Option<&[&ChannelDetails]>, final_value_msat: u64, final_cltv_expiry_delta: u32,
+ logger: L, scorer: &S, _random_seed_bytes: &[u8; 32]
) -> Result<Route, LightningError>
where L::Target: Logger {
let payee_node_id = NodeId::from_pubkey(&payment_params.payee_pubkey);
// - OR if we could not construct a new path. Any next attempt will fail too.
// Otherwise, repeat step 2.
// 4. See if we managed to collect paths which aggregately are able to transfer target value
- // (not recommended value). If yes, proceed. If not, fail routing.
- // 5. Randomly combine paths into routes having enough to fulfill the payment. (TODO: knapsack)
- // 6. Of all the found paths, select only those with the lowest total fee.
- // 7. The last path in every selected route is likely to be more than we need.
+ // (not recommended value).
+ // 5. If yes, proceed. If not, fail routing.
+ // 6. Randomly combine paths into routes having enough to fulfill the payment. (TODO: knapsack)
+ // 7. Of all the found paths, select only those with the lowest total fee.
+ // 8. The last path in every selected route is likely to be more than we need.
// Reduce its value-to-transfer and recompute fees.
- // 8. Choose the best route by the lowest total fee.
+ // 9. Choose the best route by the lowest total fee.
// As for the actual search algorithm,
// we do a payee-to-payer pseudo-Dijkstra's sorting by each node's distance from the payee
// to use as the A* heuristic beyond just the cost to get one node further than the current
// one.
- let network_graph = network.read_only();
let network_channels = network_graph.channels();
let network_nodes = network_graph.nodes();
node_info.features.supports_basic_mpp()
} else { false }
} else { false };
- log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP", our_node_pubkey,
- payment_params.payee_pubkey, if allow_mpp { "with" } else { "without" });
+ log_trace!(logger, "Searching for a route from payer {} to payee {} {} MPP and {} first hops {}overriding the network graph", our_node_pubkey,
+ payment_params.payee_pubkey, if allow_mpp { "with" } else { "without" },
+ first_hops.map(|hops| hops.len()).unwrap_or(0), if first_hops.is_some() { "" } else { "not " });
// Step (1).
// Prepare the data we'll use for payee-to-payer search by
let contributes_sufficient_value = available_value_contribution_msat >= minimal_value_contribution_msat;
// Do not consider candidates that exceed the maximum total cltv expiry limit.
- let max_total_cltv_expiry_delta = payment_params.max_total_cltv_expiry_delta;
+ // In order to already account for some of the privacy enhancing random CLTV
+ // expiry delta offset we add on top later, we subtract a rough estimate
+ // (2*MEDIAN_HOP_CLTV_EXPIRY_DELTA) here.
+ let max_total_cltv_expiry_delta = payment_params.max_total_cltv_expiry_delta
+ .checked_sub(2*MEDIAN_HOP_CLTV_EXPIRY_DELTA)
+ .unwrap_or(payment_params.max_total_cltv_expiry_delta);
let hop_total_cltv_delta = ($next_hops_cltv_delta as u32)
.checked_add($candidate.cltv_expiry_delta())
.unwrap_or(u32::max_value());
// semi-dummy record just to compute the fees to reach the source node.
// This will affect our decision on selecting short_channel_id
// as a way to reach the $dest_node_id.
- let mut fee_base_msat = u32::max_value();
- let mut fee_proportional_millionths = u32::max_value();
+ let mut fee_base_msat = 0;
+ let mut fee_proportional_millionths = 0;
if let Some(Some(fees)) = network_nodes.get(&$src_node_id).map(|node| node.lowest_inbound_channel_fees) {
fee_base_msat = fees.base_msat;
fee_proportional_millionths = fees.proportional_millionths;
ordered_hops.last_mut().unwrap().1 = NodeFeatures::empty();
}
} else {
- // We should be able to fill in features for everything except the last
- // hop, if the last hop was provided via a BOLT 11 invoice (though we
- // should be able to extend it further as BOLT 11 does have feature
- // flags for the last hop node itself).
- assert!(ordered_hops.last().unwrap().0.node_id == payee_node_id);
+ // We can fill in features for everything except hops which were
+ // provided via the invoice we're paying. We could guess based on the
+ // recipient's features but for now we simply avoid guessing at all.
}
}
ordered_hops.last_mut().unwrap().0.fee_msat = value_contribution_msat;
ordered_hops.last_mut().unwrap().0.hop_use_fee_msat = 0;
- log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: {:?}",
- ordered_hops.len(), value_contribution_msat, ordered_hops);
+ log_trace!(logger, "Found a path back to us from the target with {} hops contributing up to {} msat: \n {:#?}",
+ ordered_hops.len(), value_contribution_msat, ordered_hops.iter().map(|h| &(h.0)).collect::<Vec<&PathBuildingHop>>());
let mut payment_path = PaymentPath {hops: ordered_hops};
Ok(route)
}
+// When an adversarial intermediary node observes a payment, it may be able to infer its
+// destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network
+// graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the
+// payment path by adding a randomized 'shadow route' offset to the final hop.
+fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters, network_graph: &ReadOnlyNetworkGraph, random_seed_bytes: &[u8; 32]) {
+ let network_channels = network_graph.channels();
+ let network_nodes = network_graph.nodes();
+
+ for path in route.paths.iter_mut() {
+ let mut shadow_ctlv_expiry_delta_offset: u32 = 0;
+
+ // Choose the last publicly known node as the starting point for the random walk
+ if let Some(starting_hop) = path.iter().rev().find(|h| network_nodes.contains_key(&NodeId::from_pubkey(&h.pubkey))) {
+ let mut cur_node_id = NodeId::from_pubkey(&starting_hop.pubkey);
+
+ // Init PRNG with path nonce
+ let mut path_nonce = [0u8; 12];
+ path_nonce.copy_from_slice(&cur_node_id.as_slice()[..12]);
+ let mut prng = ChaCha20::new(random_seed_bytes, &path_nonce);
+ let mut random_path_bytes = [0u8; ::core::mem::size_of::<usize>()];
+
+ // Pick a random path length in [1 .. 3]
+ prng.process_in_place(&mut random_path_bytes);
+ let random_walk_length = usize::from_be_bytes(random_path_bytes).wrapping_rem(3).wrapping_add(1);
+
+ for _random_hop in 0..random_walk_length {
+ if let Some(cur_node) = network_nodes.get(&cur_node_id) {
+ // Randomly choose the next hop
+ prng.process_in_place(&mut random_path_bytes);
+ if let Some(random_channel) = usize::from_be_bytes(random_path_bytes).checked_rem(cur_node.channels.len())
+ .and_then(|index| cur_node.channels.get(index))
+ .and_then(|id| network_channels.get(id)) {
+ random_channel.as_directed_from(&cur_node_id).map(|(dir_info, next_id)| {
+ dir_info.direction().map(|channel_update_info|
+ shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
+ .checked_add(channel_update_info.cltv_expiry_delta.into())
+ .unwrap_or(shadow_ctlv_expiry_delta_offset));
+ cur_node_id = *next_id;
+ });
+ }
+ }
+ }
+ } else {
+ // If the entire path is private, choose a random offset from multiples of
+ // MEDIAN_HOP_CLTV_EXPIRY_DELTA
+ let mut prng = ChaCha20::new(random_seed_bytes, &[0u8; 8]);
+ let mut random_bytes = [0u8; 4];
+ prng.process_in_place(&mut random_bytes);
+ let random_walk_length = u32::from_be_bytes(random_bytes).wrapping_rem(3).wrapping_add(1);
+ shadow_ctlv_expiry_delta_offset = random_walk_length * MEDIAN_HOP_CLTV_EXPIRY_DELTA;
+ }
+
+ // Limit the total offset to reduce the worst-case locked liquidity timevalue
+ const MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET: u32 = 3*144;
+ shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, MAX_SHADOW_CLTV_EXPIRY_DELTA_OFFSET);
+
+ // Limit the offset so we never exceed the max_total_cltv_expiry_delta. To improve plausibility,
+ // we choose the limit to be the largest possible multiple of MEDIAN_HOP_CLTV_EXPIRY_DELTA.
+ let path_total_cltv_expiry_delta: u32 = path.iter().map(|h| h.cltv_expiry_delta).sum();
+ let mut max_path_offset = payment_params.max_total_cltv_expiry_delta - path_total_cltv_expiry_delta;
+ max_path_offset = cmp::max(
+ max_path_offset - (max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA),
+ max_path_offset % MEDIAN_HOP_CLTV_EXPIRY_DELTA);
+ shadow_ctlv_expiry_delta_offset = cmp::min(shadow_ctlv_expiry_delta_offset, max_path_offset);
+
+ // Add 'shadow' CLTV offset to the final hop
+ if let Some(last_hop) = path.last_mut() {
+ last_hop.cltv_expiry_delta = last_hop.cltv_expiry_delta
+ .checked_add(shadow_ctlv_expiry_delta_offset).unwrap_or(last_hop.cltv_expiry_delta);
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
- use routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters, Score};
use routing::network_graph::{NetworkGraph, NetGraphMsgHandler, NodeId};
- use routing::router::{get_route, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees};
+ use routing::router::{get_route, add_random_cltv_offset, PaymentParameters, Route, RouteHint, RouteHintHop, RouteHop, RoutingFees, DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA};
+ use routing::scoring::Score;
use chain::transaction::OutPoint;
+ use chain::keysinterface::KeysInterface;
use ln::features::{ChannelFeatures, InitFeatures, InvoiceFeatures, NodeFeatures};
use ln::msgs::{ErrorAction, LightningError, OptionalField, UnsignedChannelAnnouncement, ChannelAnnouncement, RoutingMessageHandler,
NodeAnnouncement, UnsignedNodeAnnouncement, ChannelUpdate, UnsignedChannelUpdate};
use ln::channelmanager;
use util::test_utils;
+ use util::chacha20::ChaCha20;
use util::ser::Writeable;
#[cfg(c_bindings)]
use util::ser::Writer;
},
funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
short_channel_id,
+ inbound_scid_alias: None,
channel_value_satoshis: 0,
user_channel_id: 0,
balance_msat: 0,
fn get_nodes(secp_ctx: &Secp256k1<All>) -> (SecretKey, PublicKey, Vec<SecretKey>, Vec<PublicKey>) {
let privkeys: Vec<SecretKey> = (2..10).map(|i| {
- SecretKey::from_slice(&hex::decode(format!("{:02}", i).repeat(32)).unwrap()[..]).unwrap()
+ SecretKey::from_slice(&hex::decode(format!("{:02x}", i).repeat(32)).unwrap()[..]).unwrap()
}).collect();
let pubkeys = privkeys.iter().map(|secret| PublicKey::from_secret_key(&secp_ctx, secret)).collect();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Simple route to 2 via 1
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, None, 0, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 0, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Cannot send a payment of 0 msat");
} else { panic!(); }
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Simple route to 2 via 1
let our_chans = vec![get_channel_details(Some(2), our_id, InitFeatures::from_le_bytes(vec![0b11]), 100000)];
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) =
+ get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "First hop cannot have our_node_pubkey as a destination.");
} else { panic!(); }
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
}
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Simple route to 2 via 1
});
// Not possible to send 199_999_999, because the minimum on channel=2 is 200_000_000.
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, None, 199_999_999, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
});
// A payment above the minimum should pass
- let route = get_route(&our_id, &payment_params, &network_graph, None, 199_999_999, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 199_999_999, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
}
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// A route to node#2 via two paths.
// One path allows transferring 35-40 sats, another one also allows 35-40 sats.
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph, None, 60_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
// Overpay fees to hit htlc_minimum_msat.
let overpaid_fees = route.paths[0][0].fee_msat + route.paths[1][0].fee_msat;
// TODO: this could be better balanced to overpay 10k and not 15k.
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph, None, 60_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 60_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
// Fine to overpay for htlc_minimum_msat if it allows us to save fee.
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0][0].short_channel_id, 12);
let fees = route.paths[0][0].fee_msat;
assert_eq!(fees, 5_000);
- let route = get_route(&our_id, &payment_params, &network_graph, None, 50_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
// Not fine to overpay for htlc_minimum_msat if it requires paying more than fee on
// the other channel.
assert_eq!(route.paths.len(), 1);
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// // Disable channels 4 and 12 by flags=2
update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[1], UnsignedChannelUpdate {
});
// If all the channels require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
assert_eq!(route.paths[0][0].pubkey, nodes[7]);
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Disable nodes 1, 2, and 8 by requiring unknown feature bits
let mut unknown_features = NodeFeatures::known();
add_or_update_node(&net_graph_msg_handler, &secp_ctx, &privkeys[7], unknown_features.clone(), 1);
// If all nodes require some features we don't understand, route should fail
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a path to the given destination");
} else { panic!(); }
// If we specify a channel to node7, that overrides our local channel view and that gets used
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
assert_eq!(route.paths[0][0].pubkey, nodes[7]);
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Route to 1 via 2 and 3 because our channel to 1 is disabled
let payment_params = PaymentParameters::from_node_id(nodes[0]);
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 3);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
// If we specify a channel to node7, that overrides our local channel view and that gets used
let payment_params = PaymentParameters::from_node_id(nodes[2]);
let our_chans = vec![get_channel_details(Some(42), nodes[7].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
assert_eq!(route.paths[0][0].pubkey, nodes[7]);
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Simple test across 2, 3, 5, and 4 via a last_hop channel
// Tests the behaviour when the RouteHint contains a suboptimal hop.
invalid_last_hops.push(invalid_last_hop);
{
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(invalid_last_hops);
- if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer) {
+ if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Route hint cannot have the payee as the source.");
} else { panic!(); }
}
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops_multi_private_channels(&nodes));
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 5);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(empty_last_hop(&nodes));
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Test handling of an empty RouteHint passed in Invoice.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 5);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
assert_eq!(route.paths[0][4].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
}
- fn multi_hint_last_hops(nodes: &Vec<PublicKey>) -> Vec<RouteHint> {
+ /// Builds a trivial last-hop hint that passes through the two nodes given, with channel 0xff00
+ /// and 0xff01.
+ fn multi_hop_last_hops_hint(hint_hops: [PublicKey; 2]) -> Vec<RouteHint> {
let zero_fees = RoutingFees {
base_msat: 0,
proportional_millionths: 0,
};
vec![RouteHint(vec![RouteHintHop {
- src_node_id: nodes[2],
- short_channel_id: 5,
+ src_node_id: hint_hops[0],
+ short_channel_id: 0xff00,
fees: RoutingFees {
base_msat: 100,
proportional_millionths: 0,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}, RouteHintHop {
- src_node_id: nodes[3],
- short_channel_id: 8,
+ src_node_id: hint_hops[1],
+ short_channel_id: 0xff01,
fees: zero_fees,
cltv_expiry_delta: (8 << 4) | 1,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
- }]), RouteHint(vec![RouteHintHop {
- src_node_id: nodes[5],
- short_channel_id: 10,
- fees: zero_fees,
- cltv_expiry_delta: (10 << 4) | 1,
- htlc_minimum_msat: None,
- htlc_maximum_msat: None,
}])]
}
fn multi_hint_last_hops_test() {
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
- let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(multi_hint_last_hops(&nodes));
+ let last_hops = multi_hop_last_hops_hint([nodes[2], nodes[3]]);
+ let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops.clone());
let scorer = test_utils::TestScorer::with_penalty(0);
- // Test through channels 2, 3, 5, 8.
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ // Test through channels 2, 3, 0xff00, 0xff01.
// Test shows that multiple hop hints are considered.
// Disabling channels 6 & 7 by flags=2
excess_data: Vec::new()
});
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 4);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(4));
assert_eq!(route.paths[0][2].pubkey, nodes[3]);
- assert_eq!(route.paths[0][2].short_channel_id, 5);
+ assert_eq!(route.paths[0][2].short_channel_id, last_hops[0].0[0].short_channel_id);
assert_eq!(route.paths[0][2].fee_msat, 0);
assert_eq!(route.paths[0][2].cltv_expiry_delta, 129);
assert_eq!(route.paths[0][2].node_features.le_flags(), &id_to_feature_flags(4));
- assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new());
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
assert_eq!(route.paths[0][3].pubkey, nodes[6]);
- assert_eq!(route.paths[0][3].short_channel_id, 8);
+ assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id);
+ assert_eq!(route.paths[0][3].fee_msat, 100);
+ assert_eq!(route.paths[0][3].cltv_expiry_delta, 42);
+ assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+ }
+
+ #[test]
+ fn private_multi_hint_last_hops_test() {
+ let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
+ let (_, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
+
+ let non_announced_privkey = SecretKey::from_slice(&hex::decode(format!("{:02x}", 0xf0).repeat(32)).unwrap()[..]).unwrap();
+ let non_announced_pubkey = PublicKey::from_secret_key(&secp_ctx, &non_announced_privkey);
+
+ let last_hops = multi_hop_last_hops_hint([nodes[2], non_announced_pubkey]);
+ let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops.clone());
+ let scorer = test_utils::TestScorer::with_penalty(0);
+ // Test through channels 2, 3, 0xff00, 0xff01.
+ // Test shows that multiple hop hints are considered.
+
+ // Disabling channels 6 & 7 by flags=2
+ update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
+ chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ short_channel_id: 6,
+ timestamp: 2,
+ flags: 2, // to disable
+ cltv_expiry_delta: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: OptionalField::Absent,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+ update_channel(&net_graph_msg_handler, &secp_ctx, &privkeys[2], UnsignedChannelUpdate {
+ chain_hash: genesis_block(Network::Testnet).header.block_hash(),
+ short_channel_id: 7,
+ timestamp: 2,
+ flags: 2, // to disable
+ cltv_expiry_delta: 0,
+ htlc_minimum_msat: 0,
+ htlc_maximum_msat: OptionalField::Absent,
+ fee_base_msat: 0,
+ fee_proportional_millionths: 0,
+ excess_data: Vec::new()
+ });
+
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &[42u8; 32]).unwrap();
+ assert_eq!(route.paths[0].len(), 4);
+
+ assert_eq!(route.paths[0][0].pubkey, nodes[1]);
+ assert_eq!(route.paths[0][0].short_channel_id, 2);
+ assert_eq!(route.paths[0][0].fee_msat, 200);
+ assert_eq!(route.paths[0][0].cltv_expiry_delta, 65);
+ assert_eq!(route.paths[0][0].node_features.le_flags(), &id_to_feature_flags(2));
+ assert_eq!(route.paths[0][0].channel_features.le_flags(), &id_to_feature_flags(2));
+
+ assert_eq!(route.paths[0][1].pubkey, nodes[2]);
+ assert_eq!(route.paths[0][1].short_channel_id, 4);
+ assert_eq!(route.paths[0][1].fee_msat, 100);
+ assert_eq!(route.paths[0][1].cltv_expiry_delta, 81);
+ assert_eq!(route.paths[0][1].node_features.le_flags(), &id_to_feature_flags(3));
+ assert_eq!(route.paths[0][1].channel_features.le_flags(), &id_to_feature_flags(4));
+
+ assert_eq!(route.paths[0][2].pubkey, non_announced_pubkey);
+ assert_eq!(route.paths[0][2].short_channel_id, last_hops[0].0[0].short_channel_id);
+ assert_eq!(route.paths[0][2].fee_msat, 0);
+ assert_eq!(route.paths[0][2].cltv_expiry_delta, 129);
+ assert_eq!(route.paths[0][2].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
+ assert_eq!(route.paths[0][2].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
+
+ assert_eq!(route.paths[0][3].pubkey, nodes[6]);
+ assert_eq!(route.paths[0][3].short_channel_id, last_hops[0].0[1].short_channel_id);
assert_eq!(route.paths[0][3].fee_msat, 100);
assert_eq!(route.paths[0][3].cltv_expiry_delta, 42);
assert_eq!(route.paths[0][3].node_features.le_flags(), &Vec::<u8>::new()); // We dont pass flags in from invoices yet
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops_with_public_channel(&nodes));
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// This test shows that public routes can be present in the invoice
// which would be handled in the same manner.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 5);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
let (secp_ctx, network_graph, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// Simple test with outbound channel to 4 to test that last_hops and first_hops connect
let our_chans = vec![get_channel_details(Some(42), nodes[3].clone(), InitFeatures::from_le_bytes(vec![0b11]), 250_000_000)];
let mut last_hops = last_hops(&nodes);
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops.clone());
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 2);
assert_eq!(route.paths[0][0].pubkey, nodes[3]);
// Revert to via 6 as the fee on 8 goes up
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops);
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 4);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
assert_eq!(route.paths[0][3].channel_features.le_flags(), &Vec::<u8>::new()); // We can't learn any flags from invoices, sadly
// ...but still use 8 for larger payments as 6 has a variable feerate
- let route = get_route(&our_id, &payment_params, &network_graph, None, 2000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 2000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths[0].len(), 5);
assert_eq!(route.paths[0][0].pubkey, nodes[1]);
let payment_params = PaymentParameters::from_node_id(target_node_id).with_route_hints(vec![last_hops]);
let our_chans = vec![get_channel_details(Some(42), middle_node_id, InitFeatures::from_le_bytes(vec![0b11]), outbound_capacity_msat)];
let scorer = test_utils::TestScorer::with_penalty(0);
- get_route(&source_node_id, &payment_params, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()), Some(&our_chans.iter().collect::<Vec<_>>()), route_val, 42, &test_utils::TestLogger::new(), &scorer)
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ get_route(&source_node_id, &payment_params, &NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()).read_only(),
+ Some(&our_chans.iter().collect::<Vec<_>>()), route_val, 42, &test_utils::TestLogger::new(), &scorer, &random_seed_bytes)
}
#[test]
let (secp_ctx, network_graph, mut net_graph_msg_handler, chain_monitor, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
// We will use a simple single-path route from
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 250_000_001, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 250_000_001, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 250_000_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.len(), 2);
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_001, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_001, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&our_chans.iter().collect::<Vec<_>>()), 200_000_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.len(), 2);
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 15_001, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 15_001, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 15_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.len(), 2);
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 15_001, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 15_001, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 15_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 15_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.len(), 2);
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 10_001, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 10_001, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route an exact amount we have should be fine.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 10_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 10_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let path = route.paths.last().unwrap();
assert_eq!(path.len(), 2);
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(InvoiceFeatures::known());
// Path via {node7, node2, node4} is channels {12, 13, 6, 11}.
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 60_000, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 60_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 49 sats (just a bit below the capacity).
- let route = get_route(&our_id, &payment_params, &network_graph, None, 49_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 49_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph, None, 50_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]);
// Path via node0 is channels {1, 3}. Limit them to 100 and 50 sats (total limit 50).
});
{
- let route = get_route(&our_id, &payment_params, &network_graph, None, 50_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 50_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 300_000, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 250 sats (just a bit below the capacity).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 250_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 250_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route an exact amount is also fine
- let route = get_route(&our_id, &payment_params, &network_graph, None, 290_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 290_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 350_000, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 350_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 300 sats (exact amount we can route).
// Our algorithm should provide us with these 3 paths, 100 sats each.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 300_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 300_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(InvoiceFeatures::known());
// This test checks that if we have two cheaper paths and one more expensive path,
{
// Now, attempt to route 180 sats.
// Our algorithm should provide us with these 2 paths.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 180_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 180_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_value_transferred_msat = 0;
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[3]).with_features(InvoiceFeatures::known());
// We need a route consisting of 2 paths:
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 210_000, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 210_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 200 sats (exact amount we can route).
- let route = get_route(&our_id, &payment_params, &network_graph, None, 200_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 200_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(PublicKey::from_slice(&[02; 33]).unwrap()).with_features(InvoiceFeatures::known())
.with_route_hints(vec![RouteHint(vec![RouteHintHop {
src_node_id: nodes[2],
// Get a route for 100 sats and check that we found the MPP route no problem and didn't
// overpay at all.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 100_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
// Paths are somewhat randomly ordered, but:
// * the first is channel 2 (1 msat fee) -> channel 4 -> channel 42
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
// We need a route consisting of 3 paths:
{
// Attempt to route more than available results in a failure.
if let Err(LightningError{err, action: ErrorAction::IgnoreError}) = get_route(
- &our_id, &payment_params, &network_graph, None, 150_000, 42, Arc::clone(&logger), &scorer) {
+ &our_id, &payment_params, &network_graph.read_only(), None, 150_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes) {
assert_eq!(err, "Failed to find a sufficient route to the given destination");
} else { panic!(); }
}
{
// Now, attempt to route 125 sats (just a bit below the capacity of 3 channels).
// Our algorithm should provide us with these 3 paths.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 125_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 125_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 3);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
{
// Attempt to route without the last small cheap channel
- let route = get_route(&our_id, &payment_params, &network_graph, None, 90_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
let mut total_amount_paid_msat = 0;
for path in &route.paths {
// "previous hop" being set to node 3, creating a loop in the path.
let secp_ctx = Secp256k1::new();
let logger = Arc::new(test_utils::TestLogger::new());
- let network_graph = Arc::new(NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()));
- let net_graph_msg_handler = NetGraphMsgHandler::new(Arc::clone(&network_graph), None, Arc::clone(&logger));
+ let network = Arc::new(NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash()));
+ let net_graph_msg_handler = NetGraphMsgHandler::new(Arc::clone(&network), None, Arc::clone(&logger));
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[6]);
add_channel(&net_graph_msg_handler, &secp_ctx, &our_privkey, &privkeys[1], ChannelFeatures::from_le_bytes(id_to_feature_flags(6)), 6);
{
// Now ensure the route flows simply over nodes 1 and 4 to 6.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 10_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network.read_only(), None, 10_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), 3);
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, _, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]);
// We modify the graph to set the htlc_maximum of channel 2 to below the value we wish to
{
// Now, attempt to route 90 sats, which is exactly 90 sats at the last hop, plus the
// 200% fee charged channel 13 in the 1-to-2 direction.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 90_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), 2);
let (secp_ctx, network_graph, net_graph_msg_handler, _, logger) = build_graph();
let (our_privkey, our_id, privkeys, nodes) = get_nodes(&secp_ctx);
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let payment_params = PaymentParameters::from_node_id(nodes[2]).with_features(InvoiceFeatures::known());
// We modify the graph to set the htlc_minimum of channel 2 and 4 as needed - channel 2
// Now, attempt to route 90 sats, hitting the htlc_minimum on channel 4, but
// overshooting the htlc_maximum on channel 2. Thus, we should pick the (absurdly
// expensive) channels 12-13 path.
- let route = get_route(&our_id, &payment_params, &network_graph, None, 90_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 90_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), 2);
let network_graph = NetworkGraph::new(genesis_block(Network::Testnet).header.block_hash());
let scorer = test_utils::TestScorer::with_penalty(0);
let payment_params = PaymentParameters::from_node_id(nodes[0]).with_features(InvoiceFeatures::known());
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
{
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&[
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], InitFeatures::known(), 200_000),
&get_channel_details(Some(2), nodes[0], InitFeatures::known(), 10_000),
- ]), 100_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
assert_eq!(route.paths[0].len(), 1);
assert_eq!(route.paths[0][0].fee_msat, 100_000);
}
{
- let route = get_route(&our_id, &payment_params, &network_graph, Some(&[
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), Some(&[
&get_channel_details(Some(3), nodes[0], InitFeatures::known(), 50_000),
&get_channel_details(Some(2), nodes[0], InitFeatures::known(), 50_000),
- ]), 100_000, 42, Arc::clone(&logger), &scorer).unwrap();
+ ]), 100_000, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 2);
assert_eq!(route.paths[0].len(), 1);
assert_eq!(route.paths[1].len(), 1);
// Without penalizing each hop 100 msats, a longer path with lower fees is chosen.
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = get_route(
- &our_id, &payment_params, &network_graph, None, 100, 42,
- Arc::clone(&logger), &scorer
+ &our_id, &payment_params, &network_graph.read_only(), None, 100, 42,
+ Arc::clone(&logger), &scorer, &random_seed_bytes
).unwrap();
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
// from nodes[2] rather than channel 6, 11, and 8, even though the longer path is cheaper.
let scorer = test_utils::TestScorer::with_penalty(100);
let route = get_route(
- &our_id, &payment_params, &network_graph, None, 100, 42,
- Arc::clone(&logger), &scorer
+ &our_id, &payment_params, &network_graph.read_only(), None, 100, 42,
+ Arc::clone(&logger), &scorer, &random_seed_bytes
).unwrap();
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
#[test]
fn avoids_routing_through_bad_channels_and_nodes() {
- let (secp_ctx, network_graph, _, _, logger) = build_graph();
+ let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes));
+ let network_graph = network.read_only();
// A path to nodes[6] exists when no penalties are applied to any channel.
let scorer = test_utils::TestScorer::with_penalty(0);
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = get_route(
&our_id, &payment_params, &network_graph, None, 100, 42,
- Arc::clone(&logger), &scorer
+ Arc::clone(&logger), &scorer, &random_seed_bytes
).unwrap();
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
let scorer = BadChannelScorer { short_channel_id: 6 };
let route = get_route(
&our_id, &payment_params, &network_graph, None, 100, 42,
- Arc::clone(&logger), &scorer
+ Arc::clone(&logger), &scorer, &random_seed_bytes
).unwrap();
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
let scorer = BadNodeScorer { node_id: NodeId::from_pubkey(&nodes[2]) };
match get_route(
&our_id, &payment_params, &network_graph, None, 100, 42,
- Arc::clone(&logger), &scorer
+ Arc::clone(&logger), &scorer, &random_seed_bytes
) {
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
#[test]
fn limits_total_cltv_delta() {
- let (secp_ctx, network_graph, _, _, logger) = build_graph();
+ let (secp_ctx, network, _, _, logger) = build_graph();
let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let network_graph = network.read_only();
let scorer = test_utils::TestScorer::with_penalty(0);
let feasible_max_total_cltv_delta = 1008;
let feasible_payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes))
.with_max_total_cltv_expiry_delta(feasible_max_total_cltv_delta);
- let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer).unwrap();
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let route = get_route(&our_id, &feasible_payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
let path = route.paths[0].iter().map(|hop| hop.short_channel_id).collect::<Vec<_>>();
assert_ne!(path.len(), 0);
let fail_max_total_cltv_delta = 23;
let fail_payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes))
.with_max_total_cltv_expiry_delta(fail_max_total_cltv_delta);
- match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer)
+ match get_route(&our_id, &fail_payment_params, &network_graph, None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes)
{
Err(LightningError { err, .. } ) => {
assert_eq!(err, "Failed to find a path to the given destination");
}
}
+ #[test]
+ fn adds_and_limits_cltv_offset() {
+ let (secp_ctx, network_graph, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+
+ let scorer = test_utils::TestScorer::with_penalty(0);
+
+ let payment_params = PaymentParameters::from_node_id(nodes[6]).with_route_hints(last_hops(&nodes));
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+ let route = get_route(&our_id, &payment_params, &network_graph.read_only(), None, 100, 42, Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
+ assert_eq!(route.paths.len(), 1);
+
+ let cltv_expiry_deltas_before = route.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+
+ // Check whether the offset added to the last hop by default is in [1 .. DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA]
+ let mut route_default = route.clone();
+ add_random_cltv_offset(&mut route_default, &payment_params, &network_graph.read_only(), &random_seed_bytes);
+ let cltv_expiry_deltas_default = route_default.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+ assert_eq!(cltv_expiry_deltas_before.split_last().unwrap().1, cltv_expiry_deltas_default.split_last().unwrap().1);
+ assert!(cltv_expiry_deltas_default.last() > cltv_expiry_deltas_before.last());
+ assert!(cltv_expiry_deltas_default.last().unwrap() <= &DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA);
+
+ // Check that no offset is added when we restrict the max_total_cltv_expiry_delta
+ let mut route_limited = route.clone();
+ let limited_max_total_cltv_expiry_delta = cltv_expiry_deltas_before.iter().sum();
+ let limited_payment_params = payment_params.with_max_total_cltv_expiry_delta(limited_max_total_cltv_expiry_delta);
+ add_random_cltv_offset(&mut route_limited, &limited_payment_params, &network_graph.read_only(), &random_seed_bytes);
+ let cltv_expiry_deltas_limited = route_limited.paths[0].iter().map(|h| h.cltv_expiry_delta).collect::<Vec<u32>>();
+ assert_eq!(cltv_expiry_deltas_before, cltv_expiry_deltas_limited);
+ }
+
+ #[test]
+ fn adds_plausible_cltv_offset() {
+ let (secp_ctx, network, _, _, logger) = build_graph();
+ let (_, our_id, _, nodes) = get_nodes(&secp_ctx);
+ let network_graph = network.read_only();
+ let network_nodes = network_graph.nodes();
+ let network_channels = network_graph.channels();
+ let scorer = test_utils::TestScorer::with_penalty(0);
+ let payment_params = PaymentParameters::from_node_id(nodes[3]);
+ let keys_manager = test_utils::TestKeysInterface::new(&[4u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
+
+ let mut route = get_route(&our_id, &payment_params, &network_graph, None, 100, 0,
+ Arc::clone(&logger), &scorer, &random_seed_bytes).unwrap();
+ add_random_cltv_offset(&mut route, &payment_params, &network_graph, &random_seed_bytes);
+
+ let mut path_plausibility = vec![];
+
+ for p in route.paths {
+ // 1. Select random observation point
+ let mut prng = ChaCha20::new(&random_seed_bytes, &[0u8; 12]);
+ let mut random_bytes = [0u8; ::core::mem::size_of::<usize>()];
+
+ prng.process_in_place(&mut random_bytes);
+ let random_path_index = usize::from_be_bytes(random_bytes).wrapping_rem(p.len());
+ let observation_point = NodeId::from_pubkey(&p.get(random_path_index).unwrap().pubkey);
+
+ // 2. Calculate what CLTV expiry delta we would observe there
+ let observed_cltv_expiry_delta: u32 = p[random_path_index..].iter().map(|h| h.cltv_expiry_delta).sum();
+
+ // 3. Starting from the observation point, find candidate paths
+ let mut candidates: VecDeque<(NodeId, Vec<u32>)> = VecDeque::new();
+ candidates.push_back((observation_point, vec![]));
+
+ let mut found_plausible_candidate = false;
+
+ 'candidate_loop: while let Some((cur_node_id, cur_path_cltv_deltas)) = candidates.pop_front() {
+ if let Some(remaining) = observed_cltv_expiry_delta.checked_sub(cur_path_cltv_deltas.iter().sum::<u32>()) {
+ if remaining == 0 || remaining.wrapping_rem(40) == 0 || remaining.wrapping_rem(144) == 0 {
+ found_plausible_candidate = true;
+ break 'candidate_loop;
+ }
+ }
+
+ if let Some(cur_node) = network_nodes.get(&cur_node_id) {
+ for channel_id in &cur_node.channels {
+ if let Some(channel_info) = network_channels.get(&channel_id) {
+ if let Some((dir_info, next_id)) = channel_info.as_directed_from(&cur_node_id) {
+ if let Some(channel_update_info) = dir_info.direction() {
+ let next_cltv_expiry_delta = channel_update_info.cltv_expiry_delta as u32;
+ if cur_path_cltv_deltas.iter().sum::<u32>()
+ .saturating_add(next_cltv_expiry_delta) <= observed_cltv_expiry_delta {
+ let mut new_path_cltv_deltas = cur_path_cltv_deltas.clone();
+ new_path_cltv_deltas.push(next_cltv_expiry_delta);
+ candidates.push_back((*next_id, new_path_cltv_deltas));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ path_plausibility.push(found_plausible_candidate);
+ }
+ assert!(path_plausibility.iter().all(|x| *x));
+ }
+
#[cfg(not(feature = "no-std"))]
pub(super) fn random_init_seed() -> u64 {
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
#[test]
#[cfg(not(feature = "no-std"))]
fn generate_routes() {
+ use routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters};
+
let mut d = match super::test_utils::get_route_file() {
Ok(f) => f,
Err(e) => {
},
};
let graph = NetworkGraph::read(&mut d).unwrap();
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut seed = random_init_seed() as usize;
let amt = seed as u64 % 200_000_000;
let params = ProbabilisticScoringParameters::default();
let scorer = ProbabilisticScorer::new(params, &graph);
- if get_route(src, &payment_params, &graph, None, amt, 42, &test_utils::TestLogger::new(), &scorer).is_ok() {
+ if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &test_utils::TestLogger::new(), &scorer, &random_seed_bytes).is_ok() {
continue 'load_endpoints;
}
}
#[test]
#[cfg(not(feature = "no-std"))]
fn generate_routes_mpp() {
+ use routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters};
+
let mut d = match super::test_utils::get_route_file() {
Ok(f) => f,
Err(e) => {
},
};
let graph = NetworkGraph::read(&mut d).unwrap();
+ let keys_manager = test_utils::TestKeysInterface::new(&[0u8; 32], Network::Testnet);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut seed = random_init_seed() as usize;
let amt = seed as u64 % 200_000_000;
let params = ProbabilisticScoringParameters::default();
let scorer = ProbabilisticScorer::new(params, &graph);
- if get_route(src, &payment_params, &graph, None, amt, 42, &test_utils::TestLogger::new(), &scorer).is_ok() {
+ if get_route(src, &payment_params, &graph.read_only(), None, amt, 42, &test_utils::TestLogger::new(), &scorer, &random_seed_bytes).is_ok() {
continue 'load_endpoints;
}
}
use bitcoin::hashes::Hash;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
use chain::transaction::OutPoint;
+ use chain::keysinterface::{KeysManager,KeysInterface};
use ln::channelmanager::{ChannelCounterparty, ChannelDetails};
use ln::features::{InitFeatures, InvoiceFeatures};
use routing::scoring::{FixedPenaltyScorer, ProbabilisticScorer, ProbabilisticScoringParameters, Scorer};
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0
}),
short_channel_id: Some(1),
+ inbound_scid_alias: None,
channel_value_satoshis: 10_000_000,
user_channel_id: 0,
balance_msat: 10_000_000,
) {
let nodes = graph.read_only().nodes().clone();
let payer = payer_pubkey();
+ let keys_manager = KeysManager::new(&[0u8; 32], 42, 42);
+ let random_seed_bytes = keys_manager.get_secure_random_bytes();
// First, get 100 (source, destination) pairs for which route-getting actually succeeds...
let mut routes = Vec::new();
let params = PaymentParameters::from_node_id(dst).with_features(features.clone());
let first_hop = first_hop(src);
let amt = seed as u64 % 1_000_000;
- if let Ok(route) = get_route(&payer, ¶ms, &graph, Some(&[&first_hop]), amt, 42, &DummyLogger{}, &scorer) {
+ if let Ok(route) = get_route(&payer, ¶ms, &graph.read_only(), Some(&[&first_hop]), amt, 42, &DummyLogger{}, &scorer, &random_seed_bytes) {
routes.push(route);
route_endpoints.push((first_hop, params, amt));
continue 'load_endpoints;
let mut idx = 0;
bench.iter(|| {
let (first_hop, params, amt) = &route_endpoints[idx % route_endpoints.len()];
- assert!(get_route(&payer, params, &graph, Some(&[first_hop]), *amt, 42, &DummyLogger{}, &scorer).is_ok());
+ assert!(get_route(&payer, params, &graph.read_only(), Some(&[first_hop]), *amt, 42, &DummyLogger{}, &scorer, &random_seed_bytes).is_ok());
idx += 1;
});
}
//! # use lightning::routing::network_graph::NetworkGraph;
//! # use lightning::routing::router::{RouteParameters, find_route};
//! # use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters, Scorer, ScoringParameters};
+//! # use lightning::chain::keysinterface::{KeysManager, KeysInterface};
//! # use lightning::util::logger::{Logger, Record};
//! # use secp256k1::key::PublicKey;
//! #
//! ..ProbabilisticScoringParameters::default()
//! };
//! let scorer = ProbabilisticScorer::new(params, &network_graph);
+//! # let random_seed_bytes = [42u8; 32];
//!
-//! let route = find_route(&payer, &route_params, &network_graph, None, &logger, &scorer);
+//! let route = find_route(&payer, &route_params, &network_graph, None, &logger, &scorer, &random_seed_bytes);
//! # }
//! ```
//!
}
}
+#[derive(Clone)]
/// [`Score`] implementation that uses a fixed penalty.
pub struct FixedPenaltyScorer {
penalty_msat: u64,
/// [`Score`] implementation.
///
/// (C-not exported) generally all users should use the [`Scorer`] type alias.
-#[doc(hidden)]
pub struct ScorerUsingTime<T: Time> {
params: ScoringParameters,
// TODO: Remove entries of closed channels.
channel_failures: HashMap<u64, ChannelFailure<T>>,
}
+#[derive(Clone)]
/// Parameters for configuring [`Scorer`].
pub struct ScoringParameters {
/// A fixed penalty in msats to apply to each channel.
/// Probabilistic [`Score`] implementation.
///
/// (C-not exported) generally all users should use the [`ProbabilisticScorer`] type alias.
-#[doc(hidden)]
pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time> {
params: ProbabilisticScoringParameters,
network_graph: G,
}
impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiquidity<L, T, U> {
- /// Returns the success probability of routing the given HTLC `amount_msat` through the channel
- /// in this direction.
- fn success_probability(&self, amount_msat: u64) -> f64 {
+ /// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
+ /// direction.
+ fn penalty_msat(&self, amount_msat: u64, liquidity_penalty_multiplier_msat: u64) -> u64 {
let max_liquidity_msat = self.max_liquidity_msat();
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat);
if amount_msat > max_liquidity_msat {
- 0.0
+ u64::max_value()
} else if amount_msat <= min_liquidity_msat {
- 1.0
+ 0
} else {
let numerator = max_liquidity_msat + 1 - amount_msat;
let denominator = max_liquidity_msat + 1 - min_liquidity_msat;
- numerator as f64 / denominator as f64
- }.max(0.01) // Lower bound the success probability to ensure some channel is selected.
+ approx::negative_log10_times_1024(numerator, denominator)
+ .saturating_mul(liquidity_penalty_multiplier_msat) / 1024
+ }
+ // Upper bound the penalty to ensure some channel is selected.
+ .min(2 * liquidity_penalty_multiplier_msat)
}
/// Returns the lower bound of the channel liquidity balance in this direction.
) -> u64 {
let liquidity_penalty_multiplier_msat = self.params.liquidity_penalty_multiplier_msat;
let liquidity_offset_half_life = self.params.liquidity_offset_half_life;
- let success_probability = self.channel_liquidities
+ self.channel_liquidities
.get(&short_channel_id)
.unwrap_or(&ChannelLiquidity::new())
.as_directed(source, target, capacity_msat, liquidity_offset_half_life)
- .success_probability(amount_msat);
- // NOTE: If success_probability is ever changed to return 0.0, log10 is undefined so return
- // u64::max_value instead.
- debug_assert!(success_probability > core::f64::EPSILON);
- (-(success_probability.log10()) * liquidity_penalty_multiplier_msat as f64) as u64
+ .penalty_msat(amount_msat, liquidity_penalty_multiplier_msat)
}
fn payment_path_failed(&mut self, path: &[&RouteHop], short_channel_id: u64) {
}
}
+mod approx {
+ const BITS: u32 = 64;
+ const HIGHEST_BIT: u32 = BITS - 1;
+ const LOWER_BITS: u32 = 4;
+ const LOWER_BITS_BOUND: u64 = 1 << LOWER_BITS;
+ const LOWER_BITMASK: u64 = (1 << LOWER_BITS) - 1;
+
+ /// Look-up table for `log10(x) * 1024` where row `i` is used for each `x` having `i` as the
+ /// most significant bit. The next 4 bits of `x`, if applicable, are used for the second index.
+ const LOG10_TIMES_1024: [[u16; LOWER_BITS_BOUND as usize]; BITS as usize] = [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [308, 308, 308, 308, 308, 308, 308, 308, 489, 489, 489, 489, 489, 489, 489, 489],
+ [617, 617, 617, 617, 716, 716, 716, 716, 797, 797, 797, 797, 865, 865, 865, 865],
+ [925, 925, 977, 977, 1024, 1024, 1066, 1066, 1105, 1105, 1141, 1141, 1174, 1174, 1204, 1204],
+ [1233, 1260, 1285, 1309, 1332, 1354, 1375, 1394, 1413, 1431, 1449, 1466, 1482, 1497, 1513, 1527],
+ [1541, 1568, 1594, 1618, 1641, 1662, 1683, 1703, 1722, 1740, 1757, 1774, 1790, 1806, 1821, 1835],
+ [1850, 1876, 1902, 1926, 1949, 1970, 1991, 2011, 2030, 2048, 2065, 2082, 2098, 2114, 2129, 2144],
+ [2158, 2185, 2210, 2234, 2257, 2279, 2299, 2319, 2338, 2356, 2374, 2390, 2407, 2422, 2437, 2452],
+ [2466, 2493, 2518, 2542, 2565, 2587, 2608, 2627, 2646, 2665, 2682, 2699, 2715, 2731, 2746, 2760],
+ [2774, 2801, 2827, 2851, 2874, 2895, 2916, 2936, 2955, 2973, 2990, 3007, 3023, 3039, 3054, 3068],
+ [3083, 3110, 3135, 3159, 3182, 3203, 3224, 3244, 3263, 3281, 3298, 3315, 3331, 3347, 3362, 3377],
+ [3391, 3418, 3443, 3467, 3490, 3512, 3532, 3552, 3571, 3589, 3607, 3623, 3640, 3655, 3670, 3685],
+ [3699, 3726, 3751, 3775, 3798, 3820, 3841, 3860, 3879, 3898, 3915, 3932, 3948, 3964, 3979, 3993],
+ [4007, 4034, 4060, 4084, 4107, 4128, 4149, 4169, 4188, 4206, 4223, 4240, 4256, 4272, 4287, 4301],
+ [4316, 4343, 4368, 4392, 4415, 4436, 4457, 4477, 4496, 4514, 4531, 4548, 4564, 4580, 4595, 4610],
+ [4624, 4651, 4676, 4700, 4723, 4745, 4765, 4785, 4804, 4822, 4840, 4857, 4873, 4888, 4903, 4918],
+ [4932, 4959, 4984, 5009, 5031, 5053, 5074, 5093, 5112, 5131, 5148, 5165, 5181, 5197, 5212, 5226],
+ [5240, 5267, 5293, 5317, 5340, 5361, 5382, 5402, 5421, 5439, 5456, 5473, 5489, 5505, 5520, 5534],
+ [5549, 5576, 5601, 5625, 5648, 5670, 5690, 5710, 5729, 5747, 5764, 5781, 5797, 5813, 5828, 5843],
+ [5857, 5884, 5909, 5933, 5956, 5978, 5998, 6018, 6037, 6055, 6073, 6090, 6106, 6121, 6136, 6151],
+ [6165, 6192, 6217, 6242, 6264, 6286, 6307, 6326, 6345, 6364, 6381, 6398, 6414, 6430, 6445, 6459],
+ [6473, 6500, 6526, 6550, 6573, 6594, 6615, 6635, 6654, 6672, 6689, 6706, 6722, 6738, 6753, 6767],
+ [6782, 6809, 6834, 6858, 6881, 6903, 6923, 6943, 6962, 6980, 6998, 7014, 7030, 7046, 7061, 7076],
+ [7090, 7117, 7142, 7166, 7189, 7211, 7231, 7251, 7270, 7288, 7306, 7323, 7339, 7354, 7369, 7384],
+ [7398, 7425, 7450, 7475, 7497, 7519, 7540, 7560, 7578, 7597, 7614, 7631, 7647, 7663, 7678, 7692],
+ [7706, 7733, 7759, 7783, 7806, 7827, 7848, 7868, 7887, 7905, 7922, 7939, 7955, 7971, 7986, 8001],
+ [8015, 8042, 8067, 8091, 8114, 8136, 8156, 8176, 8195, 8213, 8231, 8247, 8263, 8279, 8294, 8309],
+ [8323, 8350, 8375, 8399, 8422, 8444, 8464, 8484, 8503, 8521, 8539, 8556, 8572, 8587, 8602, 8617],
+ [8631, 8658, 8684, 8708, 8730, 8752, 8773, 8793, 8811, 8830, 8847, 8864, 8880, 8896, 8911, 8925],
+ [8939, 8966, 8992, 9016, 9039, 9060, 9081, 9101, 9120, 9138, 9155, 9172, 9188, 9204, 9219, 9234],
+ [9248, 9275, 9300, 9324, 9347, 9369, 9389, 9409, 9428, 9446, 9464, 9480, 9497, 9512, 9527, 9542],
+ [9556, 9583, 9608, 9632, 9655, 9677, 9698, 9717, 9736, 9754, 9772, 9789, 9805, 9820, 9835, 9850],
+ [9864, 9891, 9917, 9941, 9963, 9985, 10006, 10026, 10044, 10063, 10080, 10097, 10113, 10129, 10144, 10158],
+ [10172, 10199, 10225, 10249, 10272, 10293, 10314, 10334, 10353, 10371, 10388, 10405, 10421, 10437, 10452, 10467],
+ [10481, 10508, 10533, 10557, 10580, 10602, 10622, 10642, 10661, 10679, 10697, 10713, 10730, 10745, 10760, 10775],
+ [10789, 10816, 10841, 10865, 10888, 10910, 10931, 10950, 10969, 10987, 11005, 11022, 11038, 11053, 11068, 11083],
+ [11097, 11124, 11150, 11174, 11196, 11218, 11239, 11259, 11277, 11296, 11313, 11330, 11346, 11362, 11377, 11391],
+ [11405, 11432, 11458, 11482, 11505, 11526, 11547, 11567, 11586, 11604, 11621, 11638, 11654, 11670, 11685, 11700],
+ [11714, 11741, 11766, 11790, 11813, 11835, 11855, 11875, 11894, 11912, 11930, 11946, 11963, 11978, 11993, 12008],
+ [12022, 12049, 12074, 12098, 12121, 12143, 12164, 12183, 12202, 12220, 12238, 12255, 12271, 12286, 12301, 12316],
+ [12330, 12357, 12383, 12407, 12429, 12451, 12472, 12492, 12511, 12529, 12546, 12563, 12579, 12595, 12610, 12624],
+ [12638, 12665, 12691, 12715, 12738, 12759, 12780, 12800, 12819, 12837, 12854, 12871, 12887, 12903, 12918, 12933],
+ [12947, 12974, 12999, 13023, 13046, 13068, 13088, 13108, 13127, 13145, 13163, 13179, 13196, 13211, 13226, 13241],
+ [13255, 13282, 13307, 13331, 13354, 13376, 13397, 13416, 13435, 13453, 13471, 13488, 13504, 13519, 13535, 13549],
+ [13563, 13590, 13616, 13640, 13662, 13684, 13705, 13725, 13744, 13762, 13779, 13796, 13812, 13828, 13843, 13857],
+ [13871, 13898, 13924, 13948, 13971, 13992, 14013, 14033, 14052, 14070, 14087, 14104, 14120, 14136, 14151, 14166],
+ [14180, 14207, 14232, 14256, 14279, 14301, 14321, 14341, 14360, 14378, 14396, 14412, 14429, 14444, 14459, 14474],
+ [14488, 14515, 14540, 14564, 14587, 14609, 14630, 14649, 14668, 14686, 14704, 14721, 14737, 14752, 14768, 14782],
+ [14796, 14823, 14849, 14873, 14895, 14917, 14938, 14958, 14977, 14995, 15012, 15029, 15045, 15061, 15076, 15090],
+ [15104, 15131, 15157, 15181, 15204, 15225, 15246, 15266, 15285, 15303, 15320, 15337, 15353, 15369, 15384, 15399],
+ [15413, 15440, 15465, 15489, 15512, 15534, 15554, 15574, 15593, 15611, 15629, 15645, 15662, 15677, 15692, 15707],
+ [15721, 15748, 15773, 15797, 15820, 15842, 15863, 15882, 15901, 15919, 15937, 15954, 15970, 15985, 16001, 16015],
+ [16029, 16056, 16082, 16106, 16128, 16150, 16171, 16191, 16210, 16228, 16245, 16262, 16278, 16294, 16309, 16323],
+ [16337, 16364, 16390, 16414, 16437, 16458, 16479, 16499, 16518, 16536, 16553, 16570, 16586, 16602, 16617, 16632],
+ [16646, 16673, 16698, 16722, 16745, 16767, 16787, 16807, 16826, 16844, 16862, 16878, 16895, 16910, 16925, 16940],
+ [16954, 16981, 17006, 17030, 17053, 17075, 17096, 17115, 17134, 17152, 17170, 17187, 17203, 17218, 17234, 17248],
+ [17262, 17289, 17315, 17339, 17361, 17383, 17404, 17424, 17443, 17461, 17478, 17495, 17511, 17527, 17542, 17556],
+ [17571, 17597, 17623, 17647, 17670, 17691, 17712, 17732, 17751, 17769, 17786, 17803, 17819, 17835, 17850, 17865],
+ [17879, 17906, 17931, 17955, 17978, 18000, 18020, 18040, 18059, 18077, 18095, 18111, 18128, 18143, 18158, 18173],
+ [18187, 18214, 18239, 18263, 18286, 18308, 18329, 18348, 18367, 18385, 18403, 18420, 18436, 18452, 18467, 18481],
+ [18495, 18522, 18548, 18572, 18595, 18616, 18637, 18657, 18676, 18694, 18711, 18728, 18744, 18760, 18775, 18789],
+ [18804, 18830, 18856, 18880, 18903, 18924, 18945, 18965, 18984, 19002, 19019, 19036, 19052, 19068, 19083, 19098],
+ [19112, 19139, 19164, 19188, 19211, 19233, 19253, 19273, 19292, 19310, 19328, 19344, 19361, 19376, 19391, 19406],
+ [19420, 19447, 19472, 19496, 19519, 19541, 19562, 19581, 19600, 19619, 19636, 19653, 19669, 19685, 19700, 19714],
+ ];
+
+ /// Approximate `log10(numerator / denominator) * 1024` using a look-up table.
+ #[inline]
+ pub fn negative_log10_times_1024(numerator: u64, denominator: u64) -> u64 {
+ // Multiply the -1 through to avoid needing to use signed numbers.
+ (log10_times_1024(denominator) - log10_times_1024(numerator)) as u64
+ }
+
+ #[inline]
+ fn log10_times_1024(x: u64) -> u16 {
+ debug_assert_ne!(x, 0);
+ let most_significant_bit = HIGHEST_BIT - x.leading_zeros();
+ let lower_bits = (x >> most_significant_bit.saturating_sub(LOWER_BITS)) & LOWER_BITMASK;
+ LOG10_TIMES_1024[most_significant_bit as usize][lower_bits as usize]
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ #[test]
+ fn prints_negative_log10_times_1024_lookup_table() {
+ for msb in 0..BITS {
+ for i in 0..LOWER_BITS_BOUND {
+ let x = ((LOWER_BITS_BOUND + i) << (HIGHEST_BIT - LOWER_BITS)) >> (HIGHEST_BIT - msb);
+ let log10_times_1024 = ((x as f64).log10() * 1024.0).round() as u16;
+ assert_eq!(log10_times_1024, LOG10_TIMES_1024[msb as usize][i as usize]);
+
+ if i % LOWER_BITS_BOUND == 0 {
+ print!("\t\t[{}, ", log10_times_1024);
+ } else if i % LOWER_BITS_BOUND == LOWER_BITS_BOUND - 1 {
+ println!("{}],", log10_times_1024);
+ } else {
+ print!("{}, ", log10_times_1024);
+ }
+ }
+ }
+ }
+ }
+}
+
impl<G: Deref<Target = NetworkGraph>, T: Time> Writeable for ProbabilisticScorerUsingTime<G, T> {
#[inline]
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
let source = source_node_id();
let target = target_node_id();
- assert_eq!(scorer.channel_penalty_msat(42, 100, 100_000, &source, &target), 0);
- assert_eq!(scorer.channel_penalty_msat(42, 1_000, 100_000, &source, &target), 4);
- assert_eq!(scorer.channel_penalty_msat(42, 10_000, 100_000, &source, &target), 45);
- assert_eq!(scorer.channel_penalty_msat(42, 100_000, 100_000, &source, &target), 2_000);
+ assert_eq!(scorer.channel_penalty_msat(42, 1_024, 1_024_000, &source, &target), 0);
+ assert_eq!(scorer.channel_penalty_msat(42, 10_240, 1_024_000, &source, &target), 14);
+ assert_eq!(scorer.channel_penalty_msat(42, 102_400, 1_024_000, &source, &target), 43);
+ assert_eq!(scorer.channel_penalty_msat(42, 1_024_000, 1_024_000, &source, &target), 2_000);
- assert_eq!(scorer.channel_penalty_msat(42, 125, 1_000, &source, &target), 57);
- assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 124);
- assert_eq!(scorer.channel_penalty_msat(42, 375, 1_000, &source, &target), 203);
- assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300);
- assert_eq!(scorer.channel_penalty_msat(42, 625, 1_000, &source, &target), 425);
- assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 600);
- assert_eq!(scorer.channel_penalty_msat(42, 875, 1_000, &source, &target), 900);
+ assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 58);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125);
+ assert_eq!(scorer.channel_penalty_msat(42, 374, 1_024, &source, &target), 204);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 301);
+ assert_eq!(scorer.channel_penalty_msat(42, 640, 1_024, &source, &target), 426);
+ assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 602);
+ assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 903);
}
#[test]
let target = target_node_id();
let path = payment_path_for_amount(500);
- assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 124);
+ assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128);
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300);
- assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 600);
+ assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 601);
scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 43);
let target = target_node_id();
let path = payment_path_for_amount(500);
- assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 124);
+ assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128);
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300);
- assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 600);
+ assert_eq!(scorer.channel_penalty_msat(42, 750, 1_000, &source, &target), 601);
scorer.payment_path_failed(&path.iter().collect::<Vec<_>>(), 42);
let recipient = recipient_node_id();
let path = payment_path_for_amount(500);
- assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 124);
- assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 124);
- assert_eq!(scorer.channel_penalty_msat(43, 250, 1_000, &target, &recipient), 124);
+ assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 128);
+ assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 128);
+ assert_eq!(scorer.channel_penalty_msat(43, 250, 1_000, &target, &recipient), 128);
scorer.payment_path_successful(&path.iter().collect::<Vec<_>>());
- assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 124);
+ assert_eq!(scorer.channel_penalty_msat(41, 250, 1_000, &sender, &source), 128);
assert_eq!(scorer.channel_penalty_msat(42, 250, 1_000, &source, &target), 300);
assert_eq!(scorer.channel_penalty_msat(43, 250, 1_000, &target, &recipient), 300);
}
scorer.payment_path_failed(&payment_path_for_amount(128).iter().collect::<Vec<_>>(), 43);
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 0);
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 92);
- assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_424);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 97);
+ assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_409);
assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 2_000);
SinceEpoch::advance(Duration::from_secs(9));
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 0);
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 92);
- assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_424);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 97);
+ assert_eq!(scorer.channel_penalty_msat(42, 768, 1_024, &source, &target), 1_409);
assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 2_000);
SinceEpoch::advance(Duration::from_secs(1));
assert_eq!(scorer.channel_penalty_msat(42, 64, 1_024, &source, &target), 0);
assert_eq!(scorer.channel_penalty_msat(42, 128, 1_024, &source, &target), 34);
- assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 1_812);
+ assert_eq!(scorer.channel_penalty_msat(42, 896, 1_024, &source, &target), 1_773);
assert_eq!(scorer.channel_penalty_msat(42, 960, 1_024, &source, &target), 2_000);
// Fully decay liquidity lower bound.
let mut scorer = ProbabilisticScorer::new(params, &network_graph);
let source = source_node_id();
let target = target_node_id();
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 124);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125);
scorer.payment_path_failed(&payment_path_for_amount(512).iter().collect::<Vec<_>>(), 42);
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 281);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 274);
// An unchecked right shift 64 bits or more in DirectedChannelLiquidity::decayed_offset_msat
// would cause an overflow.
SinceEpoch::advance(Duration::from_secs(10 * 64));
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 124);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125);
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 124);
+ assert_eq!(scorer.channel_penalty_msat(42, 256, 1_024, &source, &target), 125);
}
#[test]
let source = source_node_id();
let target = target_node_id();
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 300);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 301);
// More knowledge gives higher confidence (256, 768), meaning a lower penalty.
scorer.payment_path_failed(&payment_path_for_amount(768).iter().collect::<Vec<_>>(), 42);
scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 281);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 274);
// Decaying knowledge gives less confidence (128, 896), meaning a higher penalty.
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 293);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 301);
// Reducing the upper bound gives more confidence (128, 832) that the payment amount (512)
// is closer to the upper bound, meaning a higher penalty.
scorer.payment_path_successful(&payment_path_for_amount(64).iter().collect::<Vec<_>>());
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 333);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 342);
// Increasing the lower bound gives more confidence (256, 832) that the payment amount (512)
// is closer to the lower bound, meaning a lower penalty.
scorer.payment_path_failed(&payment_path_for_amount(256).iter().collect::<Vec<_>>(), 43);
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 247);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 255);
// Further decaying affects the lower bound more than the upper bound (128, 928).
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 280);
+ assert_eq!(scorer.channel_penalty_msat(42, 512, 1_024, &source, &target), 284);
}
#[test]
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 2_000);
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 475);
+ assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 472);
scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300);
let mut serialized_scorer = io::Cursor::new(&serialized_scorer);
let deserialized_scorer =
<ProbabilisticScorer>::read(&mut serialized_scorer, (params, &network_graph)).unwrap();
- assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 475);
+ assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 472);
scorer.payment_path_failed(&payment_path_for_amount(250).iter().collect::<Vec<_>>(), 43);
assert_eq!(scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 300);
SinceEpoch::advance(Duration::from_secs(10));
- assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 367);
+ assert_eq!(deserialized_scorer.channel_penalty_msat(42, 500, 1_000, &source, &target), 371);
}
}
/// Note that this does *not* indicate that all paths for an MPP payment have failed, see
/// [`Event::PaymentFailed`] and [`all_paths_failed`].
///
- /// [`all_paths_failed`]: Self::all_paths_failed
+ /// [`all_paths_failed`]: Self::PaymentPathFailed::all_paths_failed
PaymentPathFailed {
/// The id returned by [`ChannelManager::send_payment`] and used with
/// [`ChannelManager::retry_payment`] and [`ChannelManager::abandon_payment`].
}
/// LDK has multiple reasons to generate fake short channel ids:
-/// 1) zero-conf channels that don't have a confirmed channel id yet
+/// 1) outbound SCID aliases we use for private channels
/// 2) phantom node payments, to get an scid for the phantom node's phantom channel
pub(crate) mod fake_scid {
use bitcoin::hash_types::BlockHash;
/// receipt, and handle the HTLC accordingly. The namespace identifier is encrypted when encoded
/// into the fake scid.
#[derive(Copy, Clone)]
- pub(super) enum Namespace {
+ pub(crate) enum Namespace {
Phantom,
- // Coming soon: a variant for the zero-conf scid namespace
+ OutboundAlias,
}
impl Namespace {
/// between segwit activation and the current best known height, and the tx index and output
/// index are also selected from a "reasonable" range. We add this logic because it makes it
/// non-obvious at a glance that the scid is fake, e.g. if it appears in invoice route hints.
- pub(super) fn get_fake_scid<Signer: Sign, K: Deref>(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], keys_manager: &K) -> u64
+ pub(crate) fn get_fake_scid<Signer: Sign, K: Deref>(&self, highest_seen_blockheight: u32, genesis_hash: &BlockHash, fake_scid_rand_bytes: &[u8; 32], keys_manager: &K) -> u64
where K::Target: KeysInterface<Signer = Signer>,
{
// Ensure we haven't created a namespace that doesn't fit into the 3 bits we've allocated for
let rand_bytes = keys_manager.get_secure_random_bytes();
let segwit_activation_height = segwit_activation_height(genesis_hash);
- let mut valid_block_range = if highest_seen_blockheight > segwit_activation_height {
- highest_seen_blockheight - segwit_activation_height
- } else {
- 1
- };
+ let mut blocks_since_segwit_activation = highest_seen_blockheight.saturating_sub(segwit_activation_height);
+
// We want to ensure that this fake channel won't conflict with any transactions we haven't
// seen yet, in case `highest_seen_blockheight` is updated before we get full information
// about transactions confirmed in the given block.
- if valid_block_range > BLOCKS_PER_MONTH { valid_block_range -= BLOCKS_PER_MONTH; }
+ blocks_since_segwit_activation = blocks_since_segwit_activation.saturating_sub(BLOCKS_PER_MONTH);
let rand_for_height = u32::from_be_bytes(rand_bytes[..4].try_into().unwrap());
- let fake_scid_height = segwit_activation_height + rand_for_height % valid_block_range;
+ let fake_scid_height = segwit_activation_height + rand_for_height % (blocks_since_segwit_activation + 1);
let rand_for_tx_index = u32::from_be_bytes(rand_bytes[4..8].try_into().unwrap());
let fake_scid_tx_index = rand_for_tx_index % MAX_TX_INDEX;
}
}
- pub fn get_phantom_scid<Signer: Sign, K: Deref>(fake_scid_rand_bytes: &[u8; 32], highest_seen_blockheight: u32, genesis_hash: &BlockHash, keys_manager: &K) -> u64
- where K::Target: KeysInterface<Signer = Signer>,
- {
- let namespace = Namespace::Phantom;
- namespace.get_fake_scid(highest_seen_blockheight, genesis_hash, fake_scid_rand_bytes, keys_manager)
- }
-
fn segwit_activation_height(genesis: &BlockHash) -> u32 {
const MAINNET_GENESIS_STR: &'static str = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
if BlockHash::from_hex(MAINNET_GENESIS_STR).unwrap() == *genesis {
pub struct TestLogger {
level: Level,
+ #[cfg(feature = "std")]
id: String,
+ #[cfg(not(feature = "std"))]
+ _id: String,
pub lines: Mutex<HashMap<(String, String), usize>>,
}
pub fn with_id(id: String) -> TestLogger {
TestLogger {
level: Level::Trace,
+ #[cfg(feature = "std")]
id,
+ #[cfg(not(feature = "std"))]
+ _id: id,
lines: Mutex::new(HashMap::new())
}
}
pub struct TestKeysInterface {
pub backing: keysinterface::PhantomKeysManager,
- pub override_session_priv: Mutex<Option<[u8; 32]>>,
- pub override_channel_id_priv: Mutex<Option<[u8; 32]>>,
+ pub override_random_bytes: Mutex<Option<[u8; 32]>>,
pub disable_revocation_policy_check: bool,
enforcement_states: Mutex<HashMap<[u8;32], Arc<Mutex<EnforcementState>>>>,
expectations: Mutex<Option<VecDeque<OnGetShutdownScriptpubkey>>>,
}
fn get_secure_random_bytes(&self) -> [u8; 32] {
- let override_channel_id = self.override_channel_id_priv.lock().unwrap();
- let override_session_key = self.override_session_priv.lock().unwrap();
- if override_channel_id.is_some() && override_session_key.is_some() {
- panic!("We don't know which override key to use!");
- }
- if let Some(key) = &*override_channel_id {
- return *key;
- }
- if let Some(key) = &*override_session_key {
- return *key;
+ let override_random_bytes = self.override_random_bytes.lock().unwrap();
+ if let Some(bytes) = &*override_random_bytes {
+ return *bytes;
}
self.backing.get_secure_random_bytes()
}
let now = Duration::from_secs(genesis_block(network).header.time as u64);
Self {
backing: keysinterface::PhantomKeysManager::new(seed, now.as_secs(), now.subsec_nanos(), seed),
- override_session_priv: Mutex::new(None),
- override_channel_id_priv: Mutex::new(None),
+ override_random_bytes: Mutex::new(None),
disable_revocation_policy_check: false,
enforcement_states: Mutex::new(HashMap::new()),
expectations: Mutex::new(None),
--- /dev/null
+[package]
+name = "no-std-check"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+default = ["lightning/no-std", "lightning-invoice/no-std"]
+
+[dependencies]
+lightning = { path = "../lightning", default-features = false }
+lightning-invoice = { path = "../lightning-invoice", default-features = false }