mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 23:08:36 +01:00
Merge pull request #2697 from jkczyz/2023-10-offer-functional-tests
Functional tests for BOLT 12 Offers payment flow
This commit is contained in:
commit
76fff953bd
18 changed files with 1155 additions and 124 deletions
|
@ -7787,15 +7787,15 @@ where
|
||||||
let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
|
let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
|
||||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
let builder = refund.respond_using_derived_keys(
|
let builder = refund.respond_using_derived_keys(
|
||||||
payment_paths, payment_hash, expanded_key, entropy
|
payment_paths, payment_hash, expanded_key, entropy
|
||||||
)?;
|
)?;
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
let created_at = Duration::from_secs(
|
let created_at = Duration::from_secs(
|
||||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||||
);
|
);
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
let builder = refund.respond_using_derived_keys_no_std(
|
let builder = refund.respond_using_derived_keys_no_std(
|
||||||
payment_paths, payment_hash, created_at, expanded_key, entropy
|
payment_paths, payment_hash, created_at, expanded_key, entropy
|
||||||
)?;
|
)?;
|
||||||
|
@ -9232,17 +9232,17 @@ where
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
let created_at = Duration::from_secs(
|
let created_at = Duration::from_secs(
|
||||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||||
);
|
);
|
||||||
|
|
||||||
if invoice_request.keys.is_some() {
|
if invoice_request.keys.is_some() {
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
let builder = invoice_request.respond_using_derived_keys(
|
let builder = invoice_request.respond_using_derived_keys(
|
||||||
payment_paths, payment_hash
|
payment_paths, payment_hash
|
||||||
);
|
);
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
let builder = invoice_request.respond_using_derived_keys_no_std(
|
let builder = invoice_request.respond_using_derived_keys_no_std(
|
||||||
payment_paths, payment_hash, created_at
|
payment_paths, payment_hash, created_at
|
||||||
);
|
);
|
||||||
|
@ -9251,9 +9251,9 @@ where
|
||||||
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
|
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
let builder = invoice_request.respond_with(payment_paths, payment_hash);
|
let builder = invoice_request.respond_with(payment_paths, payment_hash);
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
let builder = invoice_request.respond_with_no_std(
|
let builder = invoice_request.respond_with_no_std(
|
||||||
payment_paths, payment_hash, created_at
|
payment_paths, payment_hash, created_at
|
||||||
);
|
);
|
||||||
|
@ -12497,7 +12497,7 @@ pub mod bench {
|
||||||
use bitcoin::blockdata::locktime::absolute::LockTime;
|
use bitcoin::blockdata::locktime::absolute::LockTime;
|
||||||
use bitcoin::hashes::Hash;
|
use bitcoin::hashes::Hash;
|
||||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||||
use bitcoin::{Block, Transaction, TxOut};
|
use bitcoin::{Transaction, TxOut};
|
||||||
|
|
||||||
use crate::sync::{Arc, Mutex, RwLock};
|
use crate::sync::{Arc, Mutex, RwLock};
|
||||||
|
|
||||||
|
@ -12537,7 +12537,7 @@ pub mod bench {
|
||||||
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
|
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
|
||||||
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
|
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &scorer);
|
let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &logger_a, &scorer);
|
||||||
|
|
||||||
let mut config: UserConfig = Default::default();
|
let mut config: UserConfig = Default::default();
|
||||||
config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
|
config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253);
|
||||||
|
|
|
@ -932,6 +932,13 @@ impl<T: sealed::AnchorsZeroFeeHtlcTx> Features<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: sealed::RouteBlinding> Features<T> {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn clear_route_blinding(&mut self) {
|
||||||
|
<T as sealed::RouteBlinding>::clear_bits(&mut self.flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl<T: sealed::UnknownFeature> Features<T> {
|
impl<T: sealed::UnknownFeature> Features<T> {
|
||||||
pub(crate) fn unknown() -> Self {
|
pub(crate) fn unknown() -> Self {
|
||||||
|
|
|
@ -11,27 +11,29 @@
|
||||||
//! nodes for functional tests.
|
//! nodes for functional tests.
|
||||||
|
|
||||||
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist};
|
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist};
|
||||||
use crate::sign::EntropySource;
|
|
||||||
use crate::chain::channelmonitor::ChannelMonitor;
|
use crate::chain::channelmonitor::ChannelMonitor;
|
||||||
use crate::chain::transaction::OutPoint;
|
use crate::chain::transaction::OutPoint;
|
||||||
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
|
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
|
||||||
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource};
|
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource};
|
||||||
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
|
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
|
||||||
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
|
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
|
||||||
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
|
|
||||||
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
|
|
||||||
use crate::ln::features::InitFeatures;
|
use crate::ln::features::InitFeatures;
|
||||||
use crate::ln::msgs;
|
use crate::ln::msgs;
|
||||||
use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler};
|
use crate::ln::msgs::{ChannelMessageHandler, OnionMessageHandler, RoutingMessageHandler};
|
||||||
use crate::util::test_channel_signer::TestChannelSigner;
|
use crate::ln::peer_handler::IgnoringMessageHandler;
|
||||||
use crate::util::scid_utils;
|
use crate::onion_message::messenger::OnionMessenger;
|
||||||
use crate::util::test_utils;
|
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
|
||||||
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
|
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
|
||||||
use crate::util::errors::APIError;
|
use crate::sign::{EntropySource, RandomBytes};
|
||||||
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
|
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
|
||||||
use crate::util::ser::{ReadableArgs, Writeable};
|
use crate::util::errors::APIError;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::util::logger::Logger;
|
use crate::util::logger::Logger;
|
||||||
|
use crate::util::scid_utils;
|
||||||
|
use crate::util::test_channel_signer::TestChannelSigner;
|
||||||
|
use crate::util::test_utils;
|
||||||
|
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
|
||||||
|
use crate::util::ser::{ReadableArgs, Writeable};
|
||||||
|
|
||||||
use bitcoin::blockdata::block::{Block, Header, Version};
|
use bitcoin::blockdata::block::{Block, Header, Version};
|
||||||
use bitcoin::blockdata::locktime::absolute::LockTime;
|
use bitcoin::blockdata::locktime::absolute::LockTime;
|
||||||
|
@ -43,13 +45,14 @@ use bitcoin::network::constants::Network;
|
||||||
use bitcoin::pow::CompactTarget;
|
use bitcoin::pow::CompactTarget;
|
||||||
use bitcoin::secp256k1::{PublicKey, SecretKey};
|
use bitcoin::secp256k1::{PublicKey, SecretKey};
|
||||||
|
|
||||||
|
use alloc::rc::Rc;
|
||||||
|
use core::cell::RefCell;
|
||||||
|
use core::iter::repeat;
|
||||||
|
use core::mem;
|
||||||
|
use core::ops::Deref;
|
||||||
use crate::io;
|
use crate::io;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use core::cell::RefCell;
|
|
||||||
use alloc::rc::Rc;
|
|
||||||
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
|
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
|
||||||
use core::mem;
|
|
||||||
use core::iter::repeat;
|
|
||||||
|
|
||||||
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
|
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
|
||||||
|
|
||||||
|
@ -388,6 +391,7 @@ pub struct NodeCfg<'a> {
|
||||||
pub tx_broadcaster: &'a test_utils::TestBroadcaster,
|
pub tx_broadcaster: &'a test_utils::TestBroadcaster,
|
||||||
pub fee_estimator: &'a test_utils::TestFeeEstimator,
|
pub fee_estimator: &'a test_utils::TestFeeEstimator,
|
||||||
pub router: test_utils::TestRouter<'a>,
|
pub router: test_utils::TestRouter<'a>,
|
||||||
|
pub message_router: test_utils::TestMessageRouter<'a>,
|
||||||
pub chain_monitor: test_utils::TestChainMonitor<'a>,
|
pub chain_monitor: test_utils::TestChainMonitor<'a>,
|
||||||
pub keys_manager: &'a test_utils::TestKeysInterface,
|
pub keys_manager: &'a test_utils::TestKeysInterface,
|
||||||
pub logger: &'a test_utils::TestLogger,
|
pub logger: &'a test_utils::TestLogger,
|
||||||
|
@ -407,6 +411,26 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager<
|
||||||
&'chan_mon_cfg test_utils::TestLogger,
|
&'chan_mon_cfg test_utils::TestLogger,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger<
|
||||||
|
DedicatedEntropy,
|
||||||
|
&'node_cfg test_utils::TestKeysInterface,
|
||||||
|
&'chan_mon_cfg test_utils::TestLogger,
|
||||||
|
&'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>,
|
||||||
|
&'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
|
||||||
|
IgnoringMessageHandler,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// For use with [`OnionMessenger`] otherwise `test_restored_packages_retry` will fail. This is
|
||||||
|
/// because that test uses older serialized data produced by calling [`EntropySource`] in a specific
|
||||||
|
/// manner. Using the same [`EntropySource`] with [`OnionMessenger`] would introduce another call,
|
||||||
|
/// causing the produced data to no longer match.
|
||||||
|
pub struct DedicatedEntropy(RandomBytes);
|
||||||
|
|
||||||
|
impl Deref for DedicatedEntropy {
|
||||||
|
type Target = RandomBytes;
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
|
pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
|
||||||
pub chain_source: &'chan_mon_cfg test_utils::TestChainSource,
|
pub chain_source: &'chan_mon_cfg test_utils::TestChainSource,
|
||||||
pub tx_broadcaster: &'chan_mon_cfg test_utils::TestBroadcaster,
|
pub tx_broadcaster: &'chan_mon_cfg test_utils::TestBroadcaster,
|
||||||
|
@ -415,6 +439,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
|
||||||
pub chain_monitor: &'node_cfg test_utils::TestChainMonitor<'chan_mon_cfg>,
|
pub chain_monitor: &'node_cfg test_utils::TestChainMonitor<'chan_mon_cfg>,
|
||||||
pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface,
|
pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface,
|
||||||
pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
|
pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
|
||||||
|
pub onion_messenger: TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg>,
|
||||||
pub network_graph: &'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>,
|
pub network_graph: &'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>,
|
||||||
pub gossip_sync: P2PGossipSync<&'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, &'chan_mon_cfg test_utils::TestChainSource, &'chan_mon_cfg test_utils::TestLogger>,
|
pub gossip_sync: P2PGossipSync<&'node_cfg NetworkGraph<&'chan_mon_cfg test_utils::TestLogger>, &'chan_mon_cfg test_utils::TestChainSource, &'chan_mon_cfg test_utils::TestLogger>,
|
||||||
pub node_seed: [u8; 32],
|
pub node_seed: [u8; 32],
|
||||||
|
@ -432,6 +457,14 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
|
||||||
&'chan_mon_cfg test_utils::TestLogger,
|
&'chan_mon_cfg test_utils::TestLogger,
|
||||||
>,
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
|
||||||
|
pub fn init_features(&self, peer_node_id: &PublicKey) -> InitFeatures {
|
||||||
|
self.override_init_features.borrow().clone()
|
||||||
|
.unwrap_or_else(|| self.node.init_features() | self.onion_messenger.provided_init_features(peer_node_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {}
|
impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {}
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
@ -599,7 +632,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {
|
||||||
node_signer: self.keys_manager,
|
node_signer: self.keys_manager,
|
||||||
signer_provider: self.keys_manager,
|
signer_provider: self.keys_manager,
|
||||||
fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
|
fee_estimator: &test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) },
|
||||||
router: &test_utils::TestRouter::new(Arc::new(network_graph), &scorer),
|
router: &test_utils::TestRouter::new(Arc::new(network_graph), &self.logger, &scorer),
|
||||||
chain_monitor: self.chain_monitor,
|
chain_monitor: self.chain_monitor,
|
||||||
tx_broadcaster: &broadcaster,
|
tx_broadcaster: &broadcaster,
|
||||||
logger: &self.logger,
|
logger: &self.logger,
|
||||||
|
@ -1054,6 +1087,7 @@ macro_rules! reload_node {
|
||||||
|
|
||||||
$new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded);
|
$new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded);
|
||||||
$node.node = &$new_channelmanager;
|
$node.node = &$new_channelmanager;
|
||||||
|
$node.onion_messenger.set_offers_handler(&$new_channelmanager);
|
||||||
};
|
};
|
||||||
($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => {
|
($node: expr, $chanman_encoded: expr, $monitors_encoded: expr, $persister: ident, $new_chain_monitor: ident, $new_channelmanager: ident) => {
|
||||||
reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager);
|
reload_node!($node, $crate::util::config::UserConfig::default(), $chanman_encoded, $monitors_encoded, $persister, $new_chain_monitor, $new_channelmanager);
|
||||||
|
@ -2897,7 +2931,8 @@ pub fn create_node_cfgs_with_persisters<'a>(node_count: usize, chanmon_cfgs: &'a
|
||||||
logger: &chanmon_cfgs[i].logger,
|
logger: &chanmon_cfgs[i].logger,
|
||||||
tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster,
|
tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster,
|
||||||
fee_estimator: &chanmon_cfgs[i].fee_estimator,
|
fee_estimator: &chanmon_cfgs[i].fee_estimator,
|
||||||
router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].scorer),
|
router: test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[i].logger, &chanmon_cfgs[i].scorer),
|
||||||
|
message_router: test_utils::TestMessageRouter::new(network_graph.clone()),
|
||||||
chain_monitor,
|
chain_monitor,
|
||||||
keys_manager: &chanmon_cfgs[i].keys_manager,
|
keys_manager: &chanmon_cfgs[i].keys_manager,
|
||||||
node_seed: seed,
|
node_seed: seed,
|
||||||
|
@ -2951,6 +2986,11 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
|
||||||
let connect_style = Rc::new(RefCell::new(ConnectStyle::random_style()));
|
let connect_style = Rc::new(RefCell::new(ConnectStyle::random_style()));
|
||||||
|
|
||||||
for i in 0..node_count {
|
for i in 0..node_count {
|
||||||
|
let dedicated_entropy = DedicatedEntropy(RandomBytes::new([i as u8; 32]));
|
||||||
|
let onion_messenger = OnionMessenger::new(
|
||||||
|
dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &cfgs[i].message_router,
|
||||||
|
&chan_mgrs[i], IgnoringMessageHandler {},
|
||||||
|
);
|
||||||
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
|
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
|
||||||
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
|
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
|
||||||
nodes.push(Node{
|
nodes.push(Node{
|
||||||
|
@ -2958,7 +2998,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
|
||||||
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
|
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
|
||||||
chain_monitor: &cfgs[i].chain_monitor, keys_manager: &cfgs[i].keys_manager,
|
chain_monitor: &cfgs[i].chain_monitor, keys_manager: &cfgs[i].keys_manager,
|
||||||
node: &chan_mgrs[i], network_graph: cfgs[i].network_graph.as_ref(), gossip_sync,
|
node: &chan_mgrs[i], network_graph: cfgs[i].network_graph.as_ref(), gossip_sync,
|
||||||
node_seed: cfgs[i].node_seed, network_chan_count: chan_count.clone(),
|
node_seed: cfgs[i].node_seed, onion_messenger, network_chan_count: chan_count.clone(),
|
||||||
network_payment_count: payment_count.clone(), logger: cfgs[i].logger,
|
network_payment_count: payment_count.clone(), logger: cfgs[i].logger,
|
||||||
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
|
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
|
||||||
connect_style: Rc::clone(&connect_style),
|
connect_style: Rc::clone(&connect_style),
|
||||||
|
@ -2973,16 +3013,24 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
|
||||||
|
|
||||||
for i in 0..node_count {
|
for i in 0..node_count {
|
||||||
for j in (i+1)..node_count {
|
for j in (i+1)..node_count {
|
||||||
nodes[i].node.peer_connected(&nodes[j].node.get_our_node_id(), &msgs::Init {
|
let node_id_i = nodes[i].node.get_our_node_id();
|
||||||
features: nodes[j].override_init_features.borrow().clone().unwrap_or_else(|| nodes[j].node.init_features()),
|
let node_id_j = nodes[j].node.get_our_node_id();
|
||||||
|
|
||||||
|
let init_i = msgs::Init {
|
||||||
|
features: nodes[i].init_features(&node_id_j),
|
||||||
networks: None,
|
networks: None,
|
||||||
remote_network_address: None,
|
remote_network_address: None,
|
||||||
}, true).unwrap();
|
};
|
||||||
nodes[j].node.peer_connected(&nodes[i].node.get_our_node_id(), &msgs::Init {
|
let init_j = msgs::Init {
|
||||||
features: nodes[i].override_init_features.borrow().clone().unwrap_or_else(|| nodes[i].node.init_features()),
|
features: nodes[j].init_features(&node_id_i),
|
||||||
networks: None,
|
networks: None,
|
||||||
remote_network_address: None,
|
remote_network_address: None,
|
||||||
}, false).unwrap();
|
};
|
||||||
|
|
||||||
|
nodes[i].node.peer_connected(&node_id_j, &init_j, true).unwrap();
|
||||||
|
nodes[j].node.peer_connected(&node_id_i, &init_i, false).unwrap();
|
||||||
|
nodes[i].onion_messenger.peer_connected(&node_id_j, &init_j, true).unwrap();
|
||||||
|
nodes[j].onion_messenger.peer_connected(&node_id_i, &init_i, false).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5533,8 +5533,9 @@ fn test_key_derivation_params() {
|
||||||
let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &chanmon_cfgs[0].persister, &keys_manager);
|
let chain_monitor = test_utils::TestChainMonitor::new(Some(&chanmon_cfgs[0].chain_source), &chanmon_cfgs[0].tx_broadcaster, &chanmon_cfgs[0].logger, &chanmon_cfgs[0].fee_estimator, &chanmon_cfgs[0].persister, &keys_manager);
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph.clone(), &scorer);
|
let router = test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[0].logger, &scorer);
|
||||||
let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, tx_broadcaster: &chanmon_cfgs[0].tx_broadcaster, fee_estimator: &chanmon_cfgs[0].fee_estimator, router, chain_monitor, keys_manager: &keys_manager, network_graph, node_seed: seed, override_init_features: alloc::rc::Rc::new(core::cell::RefCell::new(None)) };
|
let message_router = test_utils::TestMessageRouter::new(network_graph.clone());
|
||||||
|
let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, tx_broadcaster: &chanmon_cfgs[0].tx_broadcaster, fee_estimator: &chanmon_cfgs[0].fee_estimator, router, message_router, chain_monitor, keys_manager: &keys_manager, network_graph, node_seed: seed, override_init_features: alloc::rc::Rc::new(core::cell::RefCell::new(None)) };
|
||||||
let mut node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
let mut node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
||||||
node_cfgs.remove(0);
|
node_cfgs.remove(0);
|
||||||
node_cfgs.insert(0, node);
|
node_cfgs.insert(0, node);
|
||||||
|
|
|
@ -79,6 +79,9 @@ mod shutdown_tests;
|
||||||
#[cfg(all(test, async_signing))]
|
#[cfg(all(test, async_signing))]
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
mod async_signer_tests;
|
mod async_signer_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
mod offers_tests;
|
||||||
|
|
||||||
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
|
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
|
||||||
|
|
||||||
|
|
894
lightning/src/ln/offers_tests.rs
Normal file
894
lightning/src/ln/offers_tests.rs
Normal file
|
@ -0,0 +1,894 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Functional tests for the BOLT 12 Offers payment flow.
|
||||||
|
//!
|
||||||
|
//! [`ChannelManager`] provides utilities to create [`Offer`]s and [`Refund`]s along with utilities
|
||||||
|
//! to initiate and request payment for them, respectively. It also manages the payment flow via
|
||||||
|
//! implementing [`OffersMessageHandler`]. This module tests that functionality, including the
|
||||||
|
//! resulting [`Event`] generation.
|
||||||
|
//!
|
||||||
|
//! Two-node success tests use an announced channel:
|
||||||
|
//!
|
||||||
|
//! Alice --- Bob
|
||||||
|
//!
|
||||||
|
//! While two-node failure tests use an unannounced channel:
|
||||||
|
//!
|
||||||
|
//! Alice ... Bob
|
||||||
|
//!
|
||||||
|
//! Six-node tests use unannounced channels for the sender and recipient and announced channels for
|
||||||
|
//! the rest of the network.
|
||||||
|
//!
|
||||||
|
//! nodes[4]
|
||||||
|
//! / \
|
||||||
|
//! / \
|
||||||
|
//! / \
|
||||||
|
//! Alice ... Bob -------- Charlie ... David
|
||||||
|
//! \ /
|
||||||
|
//! \ /
|
||||||
|
//! \ /
|
||||||
|
//! nodes[5]
|
||||||
|
//!
|
||||||
|
//! Unnamed nodes are needed to ensure unannounced nodes can create two-hop blinded paths.
|
||||||
|
//!
|
||||||
|
//! Nodes without channels are disconnected and connected as needed to ensure that deterministic
|
||||||
|
//! blinded paths are used.
|
||||||
|
|
||||||
|
use core::time::Duration;
|
||||||
|
use crate::blinded_path::BlindedPath;
|
||||||
|
use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
|
||||||
|
use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self};
|
||||||
|
use crate::ln::functional_test_utils::*;
|
||||||
|
use crate::ln::msgs::{ChannelMessageHandler, Init, OnionMessage, OnionMessageHandler};
|
||||||
|
use crate::offers::invoice::Bolt12Invoice;
|
||||||
|
use crate::offers::invoice_error::InvoiceError;
|
||||||
|
use crate::offers::invoice_request::InvoiceRequest;
|
||||||
|
use crate::offers::parse::Bolt12SemanticError;
|
||||||
|
use crate::onion_message::messenger::PeeledOnion;
|
||||||
|
use crate::onion_message::offers::OffersMessage;
|
||||||
|
use crate::onion_message::packet::ParsedOnionMessageContents;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
macro_rules! expect_recent_payment {
|
||||||
|
($node: expr, $payment_state: path, $payment_id: expr) => {
|
||||||
|
match $node.node.list_recent_payments().first() {
|
||||||
|
Some(&$payment_state { payment_id: actual_payment_id, .. }) => {
|
||||||
|
assert_eq!($payment_id, actual_payment_id);
|
||||||
|
},
|
||||||
|
Some(_) => panic!("Unexpected recent payment state"),
|
||||||
|
None => panic!("No recent payments"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>) {
|
||||||
|
let node_id_a = node_a.node.get_our_node_id();
|
||||||
|
let node_id_b = node_b.node.get_our_node_id();
|
||||||
|
|
||||||
|
let init_a = Init {
|
||||||
|
features: node_a.init_features(&node_id_b),
|
||||||
|
networks: None,
|
||||||
|
remote_network_address: None,
|
||||||
|
};
|
||||||
|
let init_b = Init {
|
||||||
|
features: node_b.init_features(&node_id_a),
|
||||||
|
networks: None,
|
||||||
|
remote_network_address: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
node_a.node.peer_connected(&node_id_b, &init_b, true).unwrap();
|
||||||
|
node_b.node.peer_connected(&node_id_a, &init_a, false).unwrap();
|
||||||
|
node_a.onion_messenger.peer_connected(&node_id_b, &init_b, true).unwrap();
|
||||||
|
node_b.onion_messenger.peer_connected(&node_id_a, &init_a, false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnect_peers<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, peers: &[&Node<'a, 'b, 'c>]) {
|
||||||
|
for node_b in peers {
|
||||||
|
node_a.node.peer_disconnected(&node_b.node.get_our_node_id());
|
||||||
|
node_b.node.peer_disconnected(&node_a.node.get_our_node_id());
|
||||||
|
node_a.onion_messenger.peer_disconnected(&node_b.node.get_our_node_id());
|
||||||
|
node_b.onion_messenger.peer_disconnected(&node_a.node.get_our_node_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn route_bolt12_payment<'a, 'b, 'c>(
|
||||||
|
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
|
||||||
|
) {
|
||||||
|
// Monitor added when handling the invoice onion message.
|
||||||
|
check_added_monitors(node, 1);
|
||||||
|
|
||||||
|
let mut events = node.node.get_and_clear_pending_msg_events();
|
||||||
|
assert_eq!(events.len(), 1);
|
||||||
|
let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events);
|
||||||
|
|
||||||
|
// Use a fake payment_hash and bypass checking for the PaymentClaimable event since the
|
||||||
|
// invoice contains the payment_hash but it was encrypted inside an onion message.
|
||||||
|
let amount_msats = invoice.amount_msats();
|
||||||
|
let payment_hash = invoice.payment_hash();
|
||||||
|
do_pass_along_path(
|
||||||
|
node, path, amount_msats, payment_hash, None, ev, false, false, None, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) {
|
||||||
|
let recipient = &path[path.len() - 1];
|
||||||
|
match get_event!(recipient, Event::PaymentClaimable) {
|
||||||
|
Event::PaymentClaimable {
|
||||||
|
purpose: PaymentPurpose::InvoicePayment {
|
||||||
|
payment_preimage: Some(payment_preimage), ..
|
||||||
|
}, ..
|
||||||
|
} => claim_payment(node, path, payment_preimage),
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_invoice_request<'a, 'b, 'c>(
|
||||||
|
node: &Node<'a, 'b, 'c>, message: &OnionMessage
|
||||||
|
) -> (InvoiceRequest, Option<BlindedPath>) {
|
||||||
|
match node.onion_messenger.peel_onion_message(message) {
|
||||||
|
Ok(PeeledOnion::Receive(message, _, reply_path)) => match message {
|
||||||
|
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
|
||||||
|
OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path),
|
||||||
|
OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
|
||||||
|
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
|
||||||
|
},
|
||||||
|
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
|
||||||
|
},
|
||||||
|
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
|
||||||
|
Err(e) => panic!("Failed to process onion message {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Bolt12Invoice {
|
||||||
|
match node.onion_messenger.peel_onion_message(message) {
|
||||||
|
Ok(PeeledOnion::Receive(message, _, _)) => match message {
|
||||||
|
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
|
||||||
|
OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request),
|
||||||
|
OffersMessage::Invoice(invoice) => invoice,
|
||||||
|
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
|
||||||
|
},
|
||||||
|
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
|
||||||
|
},
|
||||||
|
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
|
||||||
|
Err(e) => panic!("Failed to process onion message {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_invoice_error<'a, 'b, 'c>(
|
||||||
|
node: &Node<'a, 'b, 'c>, message: &OnionMessage
|
||||||
|
) -> InvoiceError {
|
||||||
|
match node.onion_messenger.peel_onion_message(message) {
|
||||||
|
Ok(PeeledOnion::Receive(message, _, _)) => match message {
|
||||||
|
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
|
||||||
|
OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request),
|
||||||
|
OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
|
||||||
|
OffersMessage::InvoiceError(error) => error,
|
||||||
|
},
|
||||||
|
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
|
||||||
|
},
|
||||||
|
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
|
||||||
|
Err(e) => panic!("Failed to process onion message {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used
|
||||||
|
/// rather than exposing a node's pubkey.
|
||||||
|
#[test]
|
||||||
|
fn creates_and_pays_for_offer_using_two_hop_blinded_path() {
|
||||||
|
let mut accept_forward_cfg = test_default_channel_config();
|
||||||
|
accept_forward_cfg.accept_forwards_to_priv_channels = true;
|
||||||
|
|
||||||
|
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
|
||||||
|
features.set_onion_messages_optional();
|
||||||
|
features.set_route_blinding_optional();
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
|
||||||
|
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
|
||||||
|
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(
|
||||||
|
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
|
||||||
|
);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
let charlie_id = charlie.node.get_our_node_id();
|
||||||
|
let david_id = david.node.get_our_node_id();
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
assert_ne!(offer.signing_pubkey(), alice_id);
|
||||||
|
assert!(!offer.paths().is_empty());
|
||||||
|
for path in offer.paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, bob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
|
||||||
|
.unwrap();
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
connect_peers(david, bob);
|
||||||
|
|
||||||
|
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
|
||||||
|
|
||||||
|
connect_peers(alice, charlie);
|
||||||
|
|
||||||
|
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
|
||||||
|
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
|
||||||
|
|
||||||
|
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
|
||||||
|
assert_eq!(invoice_request.amount_msats(), None);
|
||||||
|
assert_ne!(invoice_request.payer_id(), david_id);
|
||||||
|
assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
|
||||||
|
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
|
||||||
|
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(david, &onion_message);
|
||||||
|
assert_eq!(invoice.amount_msats(), 10_000_000);
|
||||||
|
assert_ne!(invoice.signing_pubkey(), alice_id);
|
||||||
|
assert!(!invoice.payment_paths().is_empty());
|
||||||
|
for (_, path) in invoice.payment_paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, bob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(david, &[charlie, bob, alice]);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that a refund can be paid through blinded paths and that ephemeral pubkeys are used
|
||||||
|
/// rather than exposing a node's pubkey.
|
||||||
|
#[test]
|
||||||
|
fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
|
||||||
|
let mut accept_forward_cfg = test_default_channel_config();
|
||||||
|
accept_forward_cfg.accept_forwards_to_priv_channels = true;
|
||||||
|
|
||||||
|
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
|
||||||
|
features.set_onion_messages_optional();
|
||||||
|
features.set_route_blinding_optional();
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
|
||||||
|
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
|
||||||
|
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(
|
||||||
|
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
|
||||||
|
);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
let charlie_id = charlie.node.get_our_node_id();
|
||||||
|
let david_id = david.node.get_our_node_id();
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
let refund = david.node
|
||||||
|
.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build().unwrap();
|
||||||
|
assert_eq!(refund.amount_msats(), 10_000_000);
|
||||||
|
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));
|
||||||
|
assert_ne!(refund.payer_id(), david_id);
|
||||||
|
assert!(!refund.paths().is_empty());
|
||||||
|
for path in refund.paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, charlie_id);
|
||||||
|
}
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
alice.node.request_refund_payment(&refund).unwrap();
|
||||||
|
|
||||||
|
connect_peers(alice, charlie);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
|
||||||
|
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
|
||||||
|
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(david, &onion_message);
|
||||||
|
assert_eq!(invoice.amount_msats(), 10_000_000);
|
||||||
|
assert_ne!(invoice.signing_pubkey(), alice_id);
|
||||||
|
assert!(!invoice.payment_paths().is_empty());
|
||||||
|
for (_, path) in invoice.payment_paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, bob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
route_bolt12_payment(david, &[charlie, bob, alice], &invoice);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(david, &[charlie, bob, alice]);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that an offer can be paid through a one-hop blinded path and that ephemeral pubkeys are
|
||||||
|
/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the
|
||||||
|
/// introduction node of the blinded path.
|
||||||
|
#[test]
|
||||||
|
fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let alice = &nodes[0];
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob = &nodes[1];
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
assert_ne!(offer.signing_pubkey(), alice_id);
|
||||||
|
assert!(!offer.paths().is_empty());
|
||||||
|
for path in offer.paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, alice_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
|
||||||
|
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
|
||||||
|
|
||||||
|
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
|
||||||
|
assert_eq!(invoice_request.amount_msats(), None);
|
||||||
|
assert_ne!(invoice_request.payer_id(), bob_id);
|
||||||
|
assert_eq!(reply_path.unwrap().introduction_node_id, bob_id);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(bob, &onion_message);
|
||||||
|
assert_eq!(invoice.amount_msats(), 10_000_000);
|
||||||
|
assert_ne!(invoice.signing_pubkey(), alice_id);
|
||||||
|
assert!(!invoice.payment_paths().is_empty());
|
||||||
|
for (_, path) in invoice.payment_paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, alice_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
route_bolt12_payment(bob, &[alice], &invoice);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(bob, &[alice]);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that a refund can be paid through a one-hop blinded path and that ephemeral pubkeys are
|
||||||
|
/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the
|
||||||
|
/// introduction node of the blinded path.
|
||||||
|
#[test]
|
||||||
|
fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let alice = &nodes[0];
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob = &nodes[1];
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
let refund = bob.node
|
||||||
|
.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build().unwrap();
|
||||||
|
assert_eq!(refund.amount_msats(), 10_000_000);
|
||||||
|
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));
|
||||||
|
assert_ne!(refund.payer_id(), bob_id);
|
||||||
|
assert!(!refund.paths().is_empty());
|
||||||
|
for path in refund.paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, bob_id);
|
||||||
|
}
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
alice.node.request_refund_payment(&refund).unwrap();
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(bob, &onion_message);
|
||||||
|
assert_eq!(invoice.amount_msats(), 10_000_000);
|
||||||
|
assert_ne!(invoice.signing_pubkey(), alice_id);
|
||||||
|
assert!(!invoice.payment_paths().is_empty());
|
||||||
|
for (_, path) in invoice.payment_paths() {
|
||||||
|
assert_eq!(path.introduction_node_id, alice_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
route_bolt12_payment(bob, &[alice], &invoice);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(bob, &[alice]);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that an invoice for an offer without any blinded paths can be requested. Note that while
|
||||||
|
/// the requested is sent directly using the node's pubkey, the response and the payment still use
|
||||||
|
/// blinded paths as required by the spec.
|
||||||
|
#[test]
|
||||||
|
fn pays_for_offer_without_blinded_paths() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let alice = &nodes[0];
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob = &nodes[1];
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.clear_paths()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
assert_eq!(offer.signing_pubkey(), alice_id);
|
||||||
|
assert!(offer.paths().is_empty());
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
|
||||||
|
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(bob, &onion_message);
|
||||||
|
route_bolt12_payment(bob, &[alice], &invoice);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(bob, &[alice]);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that a refund without any blinded paths can be paid. Note that while the invoice is sent
|
||||||
|
/// directly using the node's pubkey, the payment still use blinded paths as required by the spec.
|
||||||
|
#[test]
|
||||||
|
fn pays_for_refund_without_blinded_paths() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let alice = &nodes[0];
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob = &nodes[1];
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
let refund = bob.node
|
||||||
|
.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.clear_paths()
|
||||||
|
.build().unwrap();
|
||||||
|
assert_eq!(refund.payer_id(), bob_id);
|
||||||
|
assert!(refund.paths().is_empty());
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
alice.node.request_refund_payment(&refund).unwrap();
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice = extract_invoice(bob, &onion_message);
|
||||||
|
route_bolt12_payment(bob, &[alice], &invoice);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(bob, &[alice]);
|
||||||
|
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fails creating an offer when a blinded path cannot be created without exposing the node's id.
|
||||||
|
#[test]
|
||||||
|
fn fails_creating_offer_without_blinded_paths() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
match nodes[0].node.create_offer_builder("coffee".to_string()) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fails creating a refund when a blinded path cannot be created without exposing the node's id.
|
||||||
|
#[test]
|
||||||
|
fn fails_creating_refund_without_blinded_paths() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
|
||||||
|
match nodes[0].node.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(nodes[0].node.list_recent_payments().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fails creating an invoice request when a blinded reply path cannot be created without exposing
|
||||||
|
/// the node's id.
|
||||||
|
#[test]
|
||||||
|
fn fails_creating_invoice_request_without_blinded_reply_path() {
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
|
||||||
|
match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(nodes[0].node.list_recent_payments().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_creating_invoice_request_with_duplicate_payment_id() {
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(6, &node_cfgs, &[None, None, None, None, None, None]);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, _bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
assert!(
|
||||||
|
david.node.pay_for_offer(
|
||||||
|
&offer, None, None, None, payment_id, Retry::Attempts(0), None
|
||||||
|
).is_ok()
|
||||||
|
);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId),
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_creating_refund_with_duplicate_payment_id() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
assert!(
|
||||||
|
nodes[0].node.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
).is_ok()
|
||||||
|
);
|
||||||
|
expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
match nodes[0].node.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId),
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_sending_invoice_without_blinded_payment_paths_for_offer() {
|
||||||
|
let mut accept_forward_cfg = test_default_channel_config();
|
||||||
|
accept_forward_cfg.accept_forwards_to_priv_channels = true;
|
||||||
|
|
||||||
|
// Clearing route_blinding prevents forming any payment paths since the node is unannounced.
|
||||||
|
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
|
||||||
|
features.set_onion_messages_optional();
|
||||||
|
features.clear_route_blinding();
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
|
||||||
|
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
|
||||||
|
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(
|
||||||
|
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
|
||||||
|
);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
let charlie_id = charlie.node.get_our_node_id();
|
||||||
|
let david_id = david.node.get_our_node_id();
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let offer = alice.node
|
||||||
|
.create_offer_builder("coffee".to_string()).unwrap()
|
||||||
|
.amount_msats(10_000_000)
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
connect_peers(david, bob);
|
||||||
|
|
||||||
|
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
|
||||||
|
|
||||||
|
connect_peers(alice, charlie);
|
||||||
|
|
||||||
|
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
|
||||||
|
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
|
||||||
|
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
|
||||||
|
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice_error = extract_invoice_error(david, &onion_message);
|
||||||
|
assert_eq!(invoice_error, InvoiceError::from(Bolt12SemanticError::MissingPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_sending_invoice_without_blinded_payment_paths_for_refund() {
|
||||||
|
let mut accept_forward_cfg = test_default_channel_config();
|
||||||
|
accept_forward_cfg.accept_forwards_to_priv_channels = true;
|
||||||
|
|
||||||
|
// Clearing route_blinding prevents forming any payment paths since the node is unannounced.
|
||||||
|
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
|
||||||
|
features.set_onion_messages_optional();
|
||||||
|
features.clear_route_blinding();
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
|
||||||
|
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
|
||||||
|
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(
|
||||||
|
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
|
||||||
|
);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
let refund = david.node
|
||||||
|
.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build().unwrap();
|
||||||
|
|
||||||
|
match alice.node.request_refund_payment(&refund) {
|
||||||
|
Ok(_) => panic!("Expected error"),
|
||||||
|
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_paying_invoice_more_than_once() {
|
||||||
|
let mut accept_forward_cfg = test_default_channel_config();
|
||||||
|
accept_forward_cfg.accept_forwards_to_priv_channels = true;
|
||||||
|
|
||||||
|
let mut features = channelmanager::provided_init_features(&accept_forward_cfg);
|
||||||
|
features.set_onion_messages_optional();
|
||||||
|
features.set_route_blinding_optional();
|
||||||
|
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(6);
|
||||||
|
let node_cfgs = create_node_cfgs(6, &chanmon_cfgs);
|
||||||
|
|
||||||
|
*node_cfgs[1].override_init_features.borrow_mut() = Some(features);
|
||||||
|
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(
|
||||||
|
6, &node_cfgs, &[None, Some(accept_forward_cfg), None, None, None, None]
|
||||||
|
);
|
||||||
|
let nodes = create_network(6, &node_cfgs, &node_chanmgrs);
|
||||||
|
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
|
||||||
|
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 1, 5, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 10_000_000, 1_000_000_000);
|
||||||
|
create_announced_chan_between_nodes_with_value(&nodes, 2, 5, 10_000_000, 1_000_000_000);
|
||||||
|
|
||||||
|
let (alice, bob, charlie, david) = (&nodes[0], &nodes[1], &nodes[2], &nodes[3]);
|
||||||
|
let alice_id = alice.node.get_our_node_id();
|
||||||
|
let bob_id = bob.node.get_our_node_id();
|
||||||
|
let charlie_id = charlie.node.get_our_node_id();
|
||||||
|
let david_id = david.node.get_our_node_id();
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie, david, &nodes[4], &nodes[5]]);
|
||||||
|
disconnect_peers(david, &[bob, &nodes[4], &nodes[5]]);
|
||||||
|
|
||||||
|
let absolute_expiry = Duration::from_secs(u64::MAX);
|
||||||
|
let payment_id = PaymentId([1; 32]);
|
||||||
|
let refund = david.node
|
||||||
|
.create_refund_builder(
|
||||||
|
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build().unwrap();
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);
|
||||||
|
|
||||||
|
// Alice sends the first invoice
|
||||||
|
alice.node.request_refund_payment(&refund).unwrap();
|
||||||
|
|
||||||
|
connect_peers(alice, charlie);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
|
||||||
|
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
|
||||||
|
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
|
||||||
|
|
||||||
|
// David pays the first invoice
|
||||||
|
let invoice1 = extract_invoice(david, &onion_message);
|
||||||
|
|
||||||
|
route_bolt12_payment(david, &[charlie, bob, alice], &invoice1);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Pending, payment_id);
|
||||||
|
|
||||||
|
claim_bolt12_payment(david, &[charlie, bob, alice]);
|
||||||
|
expect_recent_payment!(david, RecentPaymentDetails::Fulfilled, payment_id);
|
||||||
|
|
||||||
|
disconnect_peers(alice, &[charlie]);
|
||||||
|
|
||||||
|
// Alice sends the second invoice
|
||||||
|
alice.node.request_refund_payment(&refund).unwrap();
|
||||||
|
|
||||||
|
connect_peers(alice, charlie);
|
||||||
|
connect_peers(david, bob);
|
||||||
|
|
||||||
|
let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap();
|
||||||
|
charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = charlie.onion_messenger.next_onion_message_for_peer(david_id).unwrap();
|
||||||
|
david.onion_messenger.handle_onion_message(&charlie_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice2 = extract_invoice(david, &onion_message);
|
||||||
|
assert_eq!(invoice1.payer_metadata(), invoice2.payer_metadata());
|
||||||
|
|
||||||
|
// David sends an error instead of paying the second invoice
|
||||||
|
let onion_message = david.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
|
||||||
|
bob.onion_messenger.handle_onion_message(&david_id, &onion_message);
|
||||||
|
|
||||||
|
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
|
||||||
|
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
|
||||||
|
|
||||||
|
let invoice_error = extract_invoice_error(alice, &onion_message);
|
||||||
|
assert_eq!(invoice_error, InvoiceError::from_string("DuplicateInvoice".to_string()));
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters
|
||||||
use crate::util::errors::APIError;
|
use crate::util::errors::APIError;
|
||||||
use crate::util::logger::Logger;
|
use crate::util::logger::Logger;
|
||||||
use crate::util::time::Time;
|
use crate::util::time::Time;
|
||||||
#[cfg(all(not(feature = "no-std"), test))]
|
#[cfg(all(feature = "std", test))]
|
||||||
use crate::util::time::tests::SinceEpoch;
|
use crate::util::time::tests::SinceEpoch;
|
||||||
use crate::util::ser::ReadableArgs;
|
use crate::util::ser::ReadableArgs;
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ pub enum Retry {
|
||||||
/// retry, and may retry multiple failed HTLCs at once if they failed around the same time and
|
/// retry, and may retry multiple failed HTLCs at once if they failed around the same time and
|
||||||
/// were retried along a route from a single call to [`Router::find_route_with_id`].
|
/// were retried along a route from a single call to [`Router::find_route_with_id`].
|
||||||
Attempts(u32),
|
Attempts(u32),
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
/// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made;
|
/// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made;
|
||||||
/// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time.
|
/// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time.
|
||||||
///
|
///
|
||||||
|
@ -295,13 +295,13 @@ pub enum Retry {
|
||||||
Timeout(core::time::Duration),
|
Timeout(core::time::Duration),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
impl_writeable_tlv_based_enum!(Retry,
|
impl_writeable_tlv_based_enum!(Retry,
|
||||||
;
|
;
|
||||||
(0, Attempts)
|
(0, Attempts)
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
impl_writeable_tlv_based_enum!(Retry,
|
impl_writeable_tlv_based_enum!(Retry,
|
||||||
;
|
;
|
||||||
(0, Attempts),
|
(0, Attempts),
|
||||||
|
@ -314,10 +314,10 @@ impl Retry {
|
||||||
(Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => {
|
(Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => {
|
||||||
max_retry_count > count
|
max_retry_count > count
|
||||||
},
|
},
|
||||||
#[cfg(all(not(feature = "no-std"), not(test)))]
|
#[cfg(all(feature = "std", not(test)))]
|
||||||
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
|
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
|
||||||
*max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at),
|
*max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at),
|
||||||
#[cfg(all(not(feature = "no-std"), test))]
|
#[cfg(all(feature = "std", test))]
|
||||||
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
|
(Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) =>
|
||||||
*max_duration >= SinceEpoch::now().duration_since(*first_attempted_at),
|
*max_duration >= SinceEpoch::now().duration_since(*first_attempted_at),
|
||||||
}
|
}
|
||||||
|
@ -343,27 +343,27 @@ pub(crate) struct PaymentAttemptsUsingTime<T: Time> {
|
||||||
/// it means the result of the first attempt is not known yet.
|
/// it means the result of the first attempt is not known yet.
|
||||||
pub(crate) count: u32,
|
pub(crate) count: u32,
|
||||||
/// This field is only used when retry is `Retry::Timeout` which is only build with feature std
|
/// This field is only used when retry is `Retry::Timeout` which is only build with feature std
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
first_attempted_at: T,
|
first_attempted_at: T,
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
phantom: core::marker::PhantomData<T>,
|
phantom: core::marker::PhantomData<T>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "no-std", test)))]
|
#[cfg(not(feature = "std"))]
|
||||||
type ConfiguredTime = crate::util::time::MonotonicTime;
|
|
||||||
#[cfg(feature = "no-std")]
|
|
||||||
type ConfiguredTime = crate::util::time::Eternity;
|
type ConfiguredTime = crate::util::time::Eternity;
|
||||||
#[cfg(all(not(feature = "no-std"), test))]
|
#[cfg(all(feature = "std", not(test)))]
|
||||||
|
type ConfiguredTime = crate::util::time::MonotonicTime;
|
||||||
|
#[cfg(all(feature = "std", test))]
|
||||||
type ConfiguredTime = SinceEpoch;
|
type ConfiguredTime = SinceEpoch;
|
||||||
|
|
||||||
impl<T: Time> PaymentAttemptsUsingTime<T> {
|
impl<T: Time> PaymentAttemptsUsingTime<T> {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
PaymentAttemptsUsingTime {
|
PaymentAttemptsUsingTime {
|
||||||
count: 0,
|
count: 0,
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
first_attempted_at: T::now(),
|
first_attempted_at: T::now(),
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
phantom: core::marker::PhantomData,
|
phantom: core::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -371,9 +371,9 @@ impl<T: Time> PaymentAttemptsUsingTime<T> {
|
||||||
|
|
||||||
impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
|
impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
return write!(f, "attempts: {}", self.count);
|
return write!(f, "attempts: {}", self.count);
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
return write!(
|
return write!(
|
||||||
f,
|
f,
|
||||||
"attempts: {}, duration: {}s",
|
"attempts: {}, duration: {}s",
|
||||||
|
@ -1888,7 +1888,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let secp_ctx = Secp256k1::new();
|
let secp_ctx = Secp256k1::new();
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
|
@ -1932,7 +1932,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let secp_ctx = Secp256k1::new();
|
let secp_ctx = Secp256k1::new();
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
|
@ -1971,7 +1971,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let secp_ctx = Secp256k1::new();
|
let secp_ctx = Secp256k1::new();
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
|
@ -2178,7 +2178,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
let pending_events = Mutex::new(VecDeque::new());
|
let pending_events = Mutex::new(VecDeque::new());
|
||||||
|
@ -2229,7 +2229,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
let pending_events = Mutex::new(VecDeque::new());
|
let pending_events = Mutex::new(VecDeque::new());
|
||||||
|
@ -2288,7 +2288,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
let pending_events = Mutex::new(VecDeque::new());
|
let pending_events = Mutex::new(VecDeque::new());
|
||||||
|
@ -2347,7 +2347,7 @@ mod tests {
|
||||||
let logger = test_utils::TestLogger::new();
|
let logger = test_utils::TestLogger::new();
|
||||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||||
let router = test_utils::TestRouter::new(network_graph, &scorer);
|
let router = test_utils::TestRouter::new(network_graph, &logger, &scorer);
|
||||||
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||||
|
|
||||||
let pending_events = Mutex::new(VecDeque::new());
|
let pending_events = Mutex::new(VecDeque::new());
|
||||||
|
|
|
@ -42,10 +42,12 @@ use crate::prelude::*;
|
||||||
use crate::ln::functional_test_utils;
|
use crate::ln::functional_test_utils;
|
||||||
use crate::ln::functional_test_utils::*;
|
use crate::ln::functional_test_utils::*;
|
||||||
use crate::routing::gossip::NodeId;
|
use crate::routing::gossip::NodeId;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::time::{SystemTime, Instant, Duration};
|
use {
|
||||||
#[cfg(not(feature = "no-std"))]
|
crate::util::time::tests::SinceEpoch,
|
||||||
use crate::util::time::tests::SinceEpoch;
|
std::time::{SystemTime, Instant, Duration},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mpp_failure() {
|
fn mpp_failure() {
|
||||||
|
@ -2313,7 +2315,7 @@ fn do_automatic_retries(test: AutoRetry) {
|
||||||
let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
|
let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||||
assert_eq!(msg_events.len(), 0);
|
assert_eq!(msg_events.len(), 0);
|
||||||
} else if test == AutoRetry::FailTimeout {
|
} else if test == AutoRetry::FailTimeout {
|
||||||
#[cfg(not(feature = "no-std"))] {
|
#[cfg(feature = "std")] {
|
||||||
// Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout.
|
// Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout.
|
||||||
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
|
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret),
|
||||||
PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap();
|
PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap();
|
||||||
|
|
|
@ -24,14 +24,10 @@ use crate::ln::ChannelId;
|
||||||
use crate::ln::features::{InitFeatures, NodeFeatures};
|
use crate::ln::features::{InitFeatures, NodeFeatures};
|
||||||
use crate::ln::msgs;
|
use crate::ln::msgs;
|
||||||
use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler};
|
use crate::ln::msgs::{ChannelMessageHandler, LightningError, SocketAddress, OnionMessageHandler, RoutingMessageHandler};
|
||||||
#[cfg(not(c_bindings))]
|
|
||||||
use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager};
|
|
||||||
use crate::util::ser::{VecWriter, Writeable, Writer};
|
use crate::util::ser::{VecWriter, Writeable, Writer};
|
||||||
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE};
|
use crate::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE};
|
||||||
use crate::ln::wire;
|
use crate::ln::wire;
|
||||||
use crate::ln::wire::{Encode, Type};
|
use crate::ln::wire::{Encode, Type};
|
||||||
#[cfg(not(c_bindings))]
|
|
||||||
use crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger};
|
|
||||||
use crate::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage};
|
use crate::onion_message::messenger::{CustomOnionMessageHandler, PendingOnionMessage};
|
||||||
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
|
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
|
||||||
use crate::onion_message::packet::OnionMessageContents;
|
use crate::onion_message::packet::OnionMessageContents;
|
||||||
|
@ -52,6 +48,8 @@ use core::convert::Infallible;
|
||||||
use std::error;
|
use std::error;
|
||||||
#[cfg(not(c_bindings))]
|
#[cfg(not(c_bindings))]
|
||||||
use {
|
use {
|
||||||
|
crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager},
|
||||||
|
crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger},
|
||||||
crate::routing::gossip::{NetworkGraph, P2PGossipSync},
|
crate::routing::gossip::{NetworkGraph, P2PGossipSync},
|
||||||
crate::sign::KeysManager,
|
crate::sign::KeysManager,
|
||||||
crate::sync::Arc,
|
crate::sync::Arc,
|
||||||
|
|
|
@ -339,6 +339,11 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_paths(mut self) -> Self {
|
||||||
|
self.offer.paths = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn build_unchecked(self) -> Offer {
|
pub(super) fn build_unchecked(self) -> Offer {
|
||||||
self.build_without_checks()
|
self.build_without_checks()
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,11 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
|
impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
|
||||||
|
pub(crate) fn clear_paths(mut self) -> Self {
|
||||||
|
self.refund.paths = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
|
fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
|
||||||
self.refund.features = features;
|
self.refund.features = features;
|
||||||
self
|
self
|
||||||
|
|
|
@ -20,8 +20,6 @@ use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs
|
||||||
use crate::blinded_path::utils;
|
use crate::blinded_path::utils;
|
||||||
use crate::events::{Event, EventHandler, EventsProvider};
|
use crate::events::{Event, EventHandler, EventsProvider};
|
||||||
use crate::sign::{EntropySource, NodeSigner, Recipient};
|
use crate::sign::{EntropySource, NodeSigner, Recipient};
|
||||||
#[cfg(not(c_bindings))]
|
|
||||||
use crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager};
|
|
||||||
use crate::ln::features::{InitFeatures, NodeFeatures};
|
use crate::ln::features::{InitFeatures, NodeFeatures};
|
||||||
use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress};
|
use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress};
|
||||||
use crate::ln::onion_utils;
|
use crate::ln::onion_utils;
|
||||||
|
@ -42,6 +40,7 @@ use crate::prelude::*;
|
||||||
#[cfg(not(c_bindings))]
|
#[cfg(not(c_bindings))]
|
||||||
use {
|
use {
|
||||||
crate::sign::KeysManager,
|
crate::sign::KeysManager,
|
||||||
|
crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager},
|
||||||
crate::ln::peer_handler::IgnoringMessageHandler,
|
crate::ln::peer_handler::IgnoringMessageHandler,
|
||||||
crate::sync::Arc,
|
crate::sync::Arc,
|
||||||
};
|
};
|
||||||
|
@ -714,6 +713,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn set_offers_handler(&mut self, offers_handler: OMH) {
|
||||||
|
self.offers_handler = offers_handler;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends an [`OnionMessage`] with the given `contents` to `destination`.
|
/// Sends an [`OnionMessage`] with the given `contents` to `destination`.
|
||||||
///
|
///
|
||||||
/// See [`OnionMessenger`] for example usage.
|
/// See [`OnionMessenger`] for example usage.
|
||||||
|
@ -814,6 +818,14 @@ where
|
||||||
self.enqueue_onion_message(path, contents, reply_path, format_args!(""))
|
self.enqueue_onion_message(path, contents, reply_path, format_args!(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn peel_onion_message(
|
||||||
|
&self, msg: &OnionMessage
|
||||||
|
) -> Result<PeeledOnion<<<CMH>::Target as CustomOnionMessageHandler>::CustomMessage>, ()> {
|
||||||
|
peel_onion_message(
|
||||||
|
msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_onion_message_response<T: OnionMessageContents>(
|
fn handle_onion_message_response<T: OnionMessageContents>(
|
||||||
&self, response: Option<T>, reply_path: Option<BlindedPath>, log_suffix: fmt::Arguments
|
&self, response: Option<T>, reply_path: Option<BlindedPath>, log_suffix: fmt::Arguments
|
||||||
) {
|
) {
|
||||||
|
@ -899,9 +911,7 @@ where
|
||||||
CMH::Target: CustomOnionMessageHandler,
|
CMH::Target: CustomOnionMessageHandler,
|
||||||
{
|
{
|
||||||
fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
|
fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
|
||||||
match peel_onion_message(
|
match self.peel_onion_message(msg) {
|
||||||
msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler
|
|
||||||
) {
|
|
||||||
Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
|
Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
|
||||||
log_trace!(
|
log_trace!(
|
||||||
self.logger,
|
self.logger,
|
||||||
|
|
|
@ -1845,7 +1845,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
|
||||||
// NOTE: In the case of no-std, we won't have access to the current UNIX time at the time of removal,
|
// NOTE: In the case of no-std, we won't have access to the current UNIX time at the time of removal,
|
||||||
// so we'll just set the removal time here to the current UNIX time on the very next invocation
|
// so we'll just set the removal time here to the current UNIX time on the very next invocation
|
||||||
// of this function.
|
// of this function.
|
||||||
#[cfg(feature = "no-std")]
|
#[cfg(not(feature = "std"))]
|
||||||
{
|
{
|
||||||
let mut tracked_time = Some(current_time_unix);
|
let mut tracked_time = Some(current_time_unix);
|
||||||
core::mem::swap(time, &mut tracked_time);
|
core::mem::swap(time, &mut tracked_time);
|
||||||
|
|
|
@ -6936,7 +6936,7 @@ mod tests {
|
||||||
(route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
|
(route.paths[1].hops[1].short_channel_id == 4 && route.paths[0].hops[1].short_channel_id == 13));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
pub(super) fn random_init_seed() -> u64 {
|
pub(super) fn random_init_seed() -> u64 {
|
||||||
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
|
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
|
||||||
use core::hash::{BuildHasher, Hasher};
|
use core::hash::{BuildHasher, Hasher};
|
||||||
|
@ -6946,7 +6946,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
fn generate_routes() {
|
fn generate_routes() {
|
||||||
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
||||||
|
|
||||||
|
@ -6967,7 +6967,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
fn generate_routes_mpp() {
|
fn generate_routes_mpp() {
|
||||||
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
||||||
|
|
||||||
|
@ -6988,7 +6988,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
fn generate_large_mpp_routes() {
|
fn generate_large_mpp_routes() {
|
||||||
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
||||||
|
|
||||||
|
@ -8324,7 +8324,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(any(test, ldk_bench), not(feature = "no-std")))]
|
#[cfg(all(any(test, ldk_bench), feature = "std"))]
|
||||||
pub(crate) mod bench_utils {
|
pub(crate) mod bench_utils {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
|
@ -251,7 +251,7 @@ impl<'a, T: Score + 'a> LockableScore<'a> for RefCell<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(c_bindings))]
|
#[cfg(any(not(c_bindings), feature = "_test_utils", test))]
|
||||||
impl<'a, T: Score + 'a> LockableScore<'a> for RwLock<T> {
|
impl<'a, T: Score + 'a> LockableScore<'a> for RwLock<T> {
|
||||||
type ScoreUpdate = T;
|
type ScoreUpdate = T;
|
||||||
type ScoreLookUp = T;
|
type ScoreLookUp = T;
|
||||||
|
|
|
@ -822,11 +822,8 @@ pub struct InMemorySigner {
|
||||||
channel_value_satoshis: u64,
|
channel_value_satoshis: u64,
|
||||||
/// Key derivation parameters.
|
/// Key derivation parameters.
|
||||||
channel_keys_id: [u8; 32],
|
channel_keys_id: [u8; 32],
|
||||||
/// Seed from which all randomness produced is derived from.
|
/// A source of random bytes.
|
||||||
rand_bytes_unique_start: [u8; 32],
|
entropy_source: RandomBytes,
|
||||||
/// Tracks the number of times we've produced randomness to ensure we don't return the same
|
|
||||||
/// bytes twice.
|
|
||||||
rand_bytes_index: AtomicCounter,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for InMemorySigner {
|
impl PartialEq for InMemorySigner {
|
||||||
|
@ -857,8 +854,7 @@ impl Clone for InMemorySigner {
|
||||||
channel_parameters: self.channel_parameters.clone(),
|
channel_parameters: self.channel_parameters.clone(),
|
||||||
channel_value_satoshis: self.channel_value_satoshis,
|
channel_value_satoshis: self.channel_value_satoshis,
|
||||||
channel_keys_id: self.channel_keys_id,
|
channel_keys_id: self.channel_keys_id,
|
||||||
rand_bytes_unique_start: self.get_secure_random_bytes(),
|
entropy_source: RandomBytes::new(self.get_secure_random_bytes()),
|
||||||
rand_bytes_index: AtomicCounter::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -892,8 +888,7 @@ impl InMemorySigner {
|
||||||
holder_channel_pubkeys,
|
holder_channel_pubkeys,
|
||||||
channel_parameters: None,
|
channel_parameters: None,
|
||||||
channel_keys_id,
|
channel_keys_id,
|
||||||
rand_bytes_unique_start,
|
entropy_source: RandomBytes::new(rand_bytes_unique_start),
|
||||||
rand_bytes_index: AtomicCounter::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1069,10 +1064,7 @@ impl InMemorySigner {
|
||||||
|
|
||||||
impl EntropySource for InMemorySigner {
|
impl EntropySource for InMemorySigner {
|
||||||
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
||||||
let index = self.rand_bytes_index.get_increment();
|
self.entropy_source.get_secure_random_bytes()
|
||||||
let mut nonce = [0u8; 16];
|
|
||||||
nonce[..8].copy_from_slice(&index.to_be_bytes());
|
|
||||||
ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1350,8 +1342,7 @@ impl<ES: Deref> ReadableArgs<ES> for InMemorySigner where ES::Target: EntropySou
|
||||||
holder_channel_pubkeys,
|
holder_channel_pubkeys,
|
||||||
channel_parameters: counterparty_channel_data,
|
channel_parameters: counterparty_channel_data,
|
||||||
channel_keys_id: keys_id,
|
channel_keys_id: keys_id,
|
||||||
rand_bytes_unique_start: entropy_source.get_secure_random_bytes(),
|
entropy_source: RandomBytes::new(entropy_source.get_secure_random_bytes()),
|
||||||
rand_bytes_index: AtomicCounter::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1379,8 +1370,7 @@ pub struct KeysManager {
|
||||||
channel_master_key: ExtendedPrivKey,
|
channel_master_key: ExtendedPrivKey,
|
||||||
channel_child_index: AtomicUsize,
|
channel_child_index: AtomicUsize,
|
||||||
|
|
||||||
rand_bytes_unique_start: [u8; 32],
|
entropy_source: RandomBytes,
|
||||||
rand_bytes_index: AtomicCounter,
|
|
||||||
|
|
||||||
seed: [u8; 32],
|
seed: [u8; 32],
|
||||||
starting_time_secs: u64,
|
starting_time_secs: u64,
|
||||||
|
@ -1449,8 +1439,7 @@ impl KeysManager {
|
||||||
channel_master_key,
|
channel_master_key,
|
||||||
channel_child_index: AtomicUsize::new(0),
|
channel_child_index: AtomicUsize::new(0),
|
||||||
|
|
||||||
rand_bytes_unique_start,
|
entropy_source: RandomBytes::new(rand_bytes_unique_start),
|
||||||
rand_bytes_index: AtomicCounter::new(),
|
|
||||||
|
|
||||||
seed: *seed,
|
seed: *seed,
|
||||||
starting_time_secs,
|
starting_time_secs,
|
||||||
|
@ -1631,10 +1620,7 @@ impl KeysManager {
|
||||||
|
|
||||||
impl EntropySource for KeysManager {
|
impl EntropySource for KeysManager {
|
||||||
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
||||||
let index = self.rand_bytes_index.get_increment();
|
self.entropy_source.get_secure_random_bytes()
|
||||||
let mut nonce = [0u8; 16];
|
|
||||||
nonce[..8].copy_from_slice(&index.to_be_bytes());
|
|
||||||
ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1888,6 +1874,35 @@ impl PhantomKeysManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An implementation of [`EntropySource`] using ChaCha20.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RandomBytes {
|
||||||
|
/// Seed from which all randomness produced is derived from.
|
||||||
|
seed: [u8; 32],
|
||||||
|
/// Tracks the number of times we've produced randomness to ensure we don't return the same
|
||||||
|
/// bytes twice.
|
||||||
|
index: AtomicCounter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RandomBytes {
|
||||||
|
/// Creates a new instance using the given seed.
|
||||||
|
pub fn new(seed: [u8; 32]) -> Self {
|
||||||
|
Self {
|
||||||
|
seed,
|
||||||
|
index: AtomicCounter::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntropySource for RandomBytes {
|
||||||
|
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
||||||
|
let index = self.index.get_increment();
|
||||||
|
let mut nonce = [0u8; 16];
|
||||||
|
nonce[..8].copy_from_slice(&index.to_be_bytes());
|
||||||
|
ChaCha20::get_single_block(&self.seed, &nonce)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that EcdsaChannelSigner can have a vtable
|
// Ensure that EcdsaChannelSigner can have a vtable
|
||||||
#[test]
|
#[test]
|
||||||
pub fn dyn_sign() {
|
pub fn dyn_sign() {
|
||||||
|
|
|
@ -32,10 +32,10 @@ use crate::ln::msgs::LightningError;
|
||||||
use crate::ln::script::ShutdownScript;
|
use crate::ln::script::ShutdownScript;
|
||||||
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||||
use crate::offers::invoice_request::UnsignedInvoiceRequest;
|
use crate::offers::invoice_request::UnsignedInvoiceRequest;
|
||||||
use crate::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath};
|
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
|
||||||
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
|
use crate::routing::gossip::{EffectiveCapacity, NetworkGraph, NodeId, RoutingFees};
|
||||||
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
|
use crate::routing::utxo::{UtxoLookup, UtxoLookupError, UtxoResult};
|
||||||
use crate::routing::router::{find_route, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs};
|
use crate::routing::router::{DefaultRouter, InFlightHtlcs, Path, Route, RouteParameters, RouteHintHop, Router, ScorerAccountingForInFlightHtlcs};
|
||||||
use crate::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp};
|
use crate::routing::scoring::{ChannelUsage, ScoreUpdate, ScoreLookUp};
|
||||||
use crate::sync::RwLock;
|
use crate::sync::RwLock;
|
||||||
use crate::util::config::UserConfig;
|
use crate::util::config::UserConfig;
|
||||||
|
@ -104,14 +104,29 @@ impl chaininterface::FeeEstimator for TestFeeEstimator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestRouter<'a> {
|
pub struct TestRouter<'a> {
|
||||||
|
pub router: DefaultRouter<
|
||||||
|
Arc<NetworkGraph<&'a TestLogger>>,
|
||||||
|
&'a TestLogger,
|
||||||
|
&'a RwLock<TestScorer>,
|
||||||
|
(),
|
||||||
|
TestScorer,
|
||||||
|
>,
|
||||||
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
|
pub network_graph: Arc<NetworkGraph<&'a TestLogger>>,
|
||||||
pub next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
|
pub next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
|
||||||
pub scorer: &'a RwLock<TestScorer>,
|
pub scorer: &'a RwLock<TestScorer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TestRouter<'a> {
|
impl<'a> TestRouter<'a> {
|
||||||
pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>, scorer: &'a RwLock<TestScorer>) -> Self {
|
pub fn new(
|
||||||
Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer }
|
network_graph: Arc<NetworkGraph<&'a TestLogger>>, logger: &'a TestLogger,
|
||||||
|
scorer: &'a RwLock<TestScorer>
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
router: DefaultRouter::new(network_graph.clone(), logger, [42u8; 32], scorer, ()),
|
||||||
|
network_graph,
|
||||||
|
next_routes: Mutex::new(VecDeque::new()),
|
||||||
|
scorer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_find_route(&self, query: RouteParameters, result: Result<Route, LightningError>) {
|
pub fn expect_find_route(&self, query: RouteParameters, result: Result<Route, LightningError>) {
|
||||||
|
@ -185,38 +200,36 @@ impl<'a> Router for TestRouter<'a> {
|
||||||
}
|
}
|
||||||
return find_route_res;
|
return find_route_res;
|
||||||
}
|
}
|
||||||
let logger = TestLogger::new();
|
|
||||||
find_route(
|
self.router.find_route(payer, params, first_hops, inflight_htlcs)
|
||||||
payer, params, &self.network_graph, first_hops, &logger,
|
|
||||||
&ScorerAccountingForInFlightHtlcs::new(self.scorer.read().unwrap(), &inflight_htlcs), &Default::default(),
|
|
||||||
&[42; 32]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_blinded_payment_paths<
|
fn create_blinded_payment_paths<
|
||||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||||
>(
|
>(
|
||||||
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
|
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
|
||||||
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
|
||||||
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
) -> Result<Vec<(BlindedPayInfo, BlindedPath)>, ()> {
|
||||||
unreachable!()
|
self.router.create_blinded_payment_paths(
|
||||||
|
recipient, first_hops, tlvs, amount_msats, entropy_source, secp_ctx
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageRouter for TestRouter<'a> {
|
impl<'a> MessageRouter for TestRouter<'a> {
|
||||||
fn find_path(
|
fn find_path(
|
||||||
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
|
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
|
||||||
) -> Result<OnionMessagePath, ()> {
|
) -> Result<OnionMessagePath, ()> {
|
||||||
unreachable!()
|
self.router.find_path(sender, peers, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_blinded_paths<
|
fn create_blinded_paths<
|
||||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||||
>(
|
>(
|
||||||
&self, _recipient: PublicKey, _peers: Vec<PublicKey>, _entropy_source: &ES,
|
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||||
_secp_ctx: &Secp256k1<T>
|
secp_ctx: &Secp256k1<T>
|
||||||
) -> Result<Vec<BlindedPath>, ()> {
|
) -> Result<Vec<BlindedPath>, ()> {
|
||||||
unreachable!()
|
self.router.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +244,33 @@ impl<'a> Drop for TestRouter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TestMessageRouter<'a> {
|
||||||
|
inner: DefaultMessageRouter<Arc<NetworkGraph<&'a TestLogger>>, &'a TestLogger>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestMessageRouter<'a> {
|
||||||
|
pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>) -> Self {
|
||||||
|
Self { inner: DefaultMessageRouter::new(network_graph) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MessageRouter for TestMessageRouter<'a> {
|
||||||
|
fn find_path(
|
||||||
|
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
|
||||||
|
) -> Result<OnionMessagePath, ()> {
|
||||||
|
self.inner.find_path(sender, peers, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_blinded_paths<
|
||||||
|
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||||
|
>(
|
||||||
|
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||||
|
secp_ctx: &Secp256k1<T>
|
||||||
|
) -> Result<Vec<BlindedPath>, ()> {
|
||||||
|
self.inner.create_blinded_paths(recipient, peers, entropy_source, secp_ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OnlyReadsKeysInterface {}
|
pub struct OnlyReadsKeysInterface {}
|
||||||
|
|
||||||
impl EntropySource for OnlyReadsKeysInterface {
|
impl EntropySource for OnlyReadsKeysInterface {
|
||||||
|
@ -1390,6 +1430,9 @@ impl ScoreUpdate for TestScorer {
|
||||||
fn time_passed(&mut self, _duration_since_epoch: Duration) {}
|
fn time_passed(&mut self, _duration_since_epoch: Duration) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(c_bindings)]
|
||||||
|
impl crate::routing::scoring::Score for TestScorer {}
|
||||||
|
|
||||||
impl Drop for TestScorer {
|
impl Drop for TestScorer {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
#[cfg(feature = "std")] {
|
#[cfg(feature = "std")] {
|
||||||
|
|
|
@ -59,15 +59,15 @@ impl Sub<Duration> for Eternity {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
pub struct MonotonicTime(std::time::Instant);
|
pub struct MonotonicTime(std::time::Instant);
|
||||||
|
|
||||||
/// The amount of time to shift `Instant` forward to prevent overflow when subtracting a `Duration`
|
/// The amount of time to shift `Instant` forward to prevent overflow when subtracting a `Duration`
|
||||||
/// from `Instant::now` on some operating systems (e.g., iOS representing `Instance` as `u64`).
|
/// from `Instant::now` on some operating systems (e.g., iOS representing `Instance` as `u64`).
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
const SHIFT: Duration = Duration::from_secs(10 * 365 * 24 * 60 * 60); // 10 years.
|
const SHIFT: Duration = Duration::from_secs(10 * 365 * 24 * 60 * 60); // 10 years.
|
||||||
|
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
impl Time for MonotonicTime {
|
impl Time for MonotonicTime {
|
||||||
fn now() -> Self {
|
fn now() -> Self {
|
||||||
let instant = std::time::Instant::now().checked_add(SHIFT).expect("Overflow on MonotonicTime instantiation");
|
let instant = std::time::Instant::now().checked_add(SHIFT).expect("Overflow on MonotonicTime instantiation");
|
||||||
|
@ -93,7 +93,7 @@ impl Time for MonotonicTime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
impl Sub<Duration> for MonotonicTime {
|
impl Sub<Duration> for MonotonicTime {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ pub mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(feature = "no-std"))]
|
#[cfg(feature = "std")]
|
||||||
fn monotonic_time_subtracts() {
|
fn monotonic_time_subtracts() {
|
||||||
let now = super::MonotonicTime::now();
|
let now = super::MonotonicTime::now();
|
||||||
assert!(now.elapsed() < Duration::from_secs(10));
|
assert!(now.elapsed() < Duration::from_secs(10));
|
||||||
|
|
Loading…
Add table
Reference in a new issue