mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +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)
|
||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
let builder = refund.respond_using_derived_keys(
|
||||
payment_paths, payment_hash, expanded_key, entropy
|
||||
)?;
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
let created_at = Duration::from_secs(
|
||||
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(
|
||||
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(
|
||||
self.highest_seen_timestamp.load(Ordering::Acquire) as u64
|
||||
);
|
||||
|
||||
if invoice_request.keys.is_some() {
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
let builder = invoice_request.respond_using_derived_keys(
|
||||
payment_paths, payment_hash
|
||||
);
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
let builder = invoice_request.respond_using_derived_keys_no_std(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
|
@ -9251,9 +9251,9 @@ where
|
|||
Err(error) => Some(OffersMessage::InvoiceError(error.into())),
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
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(
|
||||
payment_paths, payment_hash, created_at
|
||||
);
|
||||
|
@ -12497,7 +12497,7 @@ pub mod bench {
|
|||
use bitcoin::blockdata::locktime::absolute::LockTime;
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
use bitcoin::{Block, Transaction, TxOut};
|
||||
use bitcoin::{Transaction, TxOut};
|
||||
|
||||
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 logger_a = test_utils::TestLogger::with_id("node a".to_owned());
|
||||
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();
|
||||
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)]
|
||||
impl<T: sealed::UnknownFeature> Features<T> {
|
||||
pub(crate) fn unknown() -> Self {
|
||||
|
|
|
@ -11,27 +11,29 @@
|
|||
//! nodes for functional tests.
|
||||
|
||||
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, chainmonitor::Persist};
|
||||
use crate::sign::EntropySource;
|
||||
use crate::chain::channelmonitor::ChannelMonitor;
|
||||
use crate::chain::transaction::OutPoint;
|
||||
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
|
||||
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource};
|
||||
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::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
|
||||
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
|
||||
use crate::ln::features::InitFeatures;
|
||||
use crate::ln::msgs;
|
||||
use crate::ln::msgs::{ChannelMessageHandler,RoutingMessageHandler};
|
||||
use crate::util::test_channel_signer::TestChannelSigner;
|
||||
use crate::util::scid_utils;
|
||||
use crate::util::test_utils;
|
||||
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
|
||||
use crate::util::errors::APIError;
|
||||
use crate::ln::msgs::{ChannelMessageHandler, OnionMessageHandler, RoutingMessageHandler};
|
||||
use crate::ln::peer_handler::IgnoringMessageHandler;
|
||||
use crate::onion_message::messenger::OnionMessenger;
|
||||
use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate};
|
||||
use crate::routing::router::{self, PaymentParameters, Route, RouteParameters};
|
||||
use crate::sign::{EntropySource, RandomBytes};
|
||||
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
|
||||
use crate::util::ser::{ReadableArgs, Writeable};
|
||||
use crate::util::errors::APIError;
|
||||
#[cfg(test)]
|
||||
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::locktime::absolute::LockTime;
|
||||
|
@ -43,13 +45,14 @@ use bitcoin::network::constants::Network;
|
|||
use bitcoin::pow::CompactTarget;
|
||||
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::prelude::*;
|
||||
use core::cell::RefCell;
|
||||
use alloc::rc::Rc;
|
||||
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
|
||||
use core::mem;
|
||||
use core::iter::repeat;
|
||||
|
||||
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
|
||||
|
||||
|
@ -388,6 +391,7 @@ pub struct NodeCfg<'a> {
|
|||
pub tx_broadcaster: &'a test_utils::TestBroadcaster,
|
||||
pub fee_estimator: &'a test_utils::TestFeeEstimator,
|
||||
pub router: test_utils::TestRouter<'a>,
|
||||
pub message_router: test_utils::TestMessageRouter<'a>,
|
||||
pub chain_monitor: test_utils::TestChainMonitor<'a>,
|
||||
pub keys_manager: &'a test_utils::TestKeysInterface,
|
||||
pub logger: &'a test_utils::TestLogger,
|
||||
|
@ -407,6 +411,26 @@ type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager<
|
|||
&'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 chain_source: &'chan_mon_cfg test_utils::TestChainSource,
|
||||
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 keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface,
|
||||
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 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],
|
||||
|
@ -432,6 +457,14 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
|
|||
&'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")]
|
||||
impl<'a, 'b, 'c> std::panic::UnwindSafe for Node<'a, 'b, 'c> {}
|
||||
#[cfg(feature = "std")]
|
||||
|
@ -599,7 +632,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> {
|
|||
node_signer: self.keys_manager,
|
||||
signer_provider: self.keys_manager,
|
||||
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,
|
||||
tx_broadcaster: &broadcaster,
|
||||
logger: &self.logger,
|
||||
|
@ -1054,6 +1087,7 @@ macro_rules! reload_node {
|
|||
|
||||
$new_channelmanager = _reload_node(&$node, $new_config, &chanman_encoded, $monitors_encoded);
|
||||
$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) => {
|
||||
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,
|
||||
tx_broadcaster: &chanmon_cfgs[i].tx_broadcaster,
|
||||
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,
|
||||
keys_manager: &chanmon_cfgs[i].keys_manager,
|
||||
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()));
|
||||
|
||||
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 wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
|
||||
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,
|
||||
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_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,
|
||||
blocks: Arc::clone(&cfgs[i].tx_broadcaster.blocks),
|
||||
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 j in (i+1)..node_count {
|
||||
nodes[i].node.peer_connected(&nodes[j].node.get_our_node_id(), &msgs::Init {
|
||||
features: nodes[j].override_init_features.borrow().clone().unwrap_or_else(|| nodes[j].node.init_features()),
|
||||
let node_id_i = nodes[i].node.get_our_node_id();
|
||||
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,
|
||||
remote_network_address: None,
|
||||
}, true).unwrap();
|
||||
nodes[j].node.peer_connected(&nodes[i].node.get_our_node_id(), &msgs::Init {
|
||||
features: nodes[i].override_init_features.borrow().clone().unwrap_or_else(|| nodes[i].node.init_features()),
|
||||
};
|
||||
let init_j = msgs::Init {
|
||||
features: nodes[j].init_features(&node_id_i),
|
||||
networks: 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 network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &chanmon_cfgs[0].logger));
|
||||
let scorer = RwLock::new(test_utils::TestScorer::new());
|
||||
let router = test_utils::TestRouter::new(network_graph.clone(), &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 router = test_utils::TestRouter::new(network_graph.clone(), &chanmon_cfgs[0].logger, &scorer);
|
||||
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);
|
||||
node_cfgs.remove(0);
|
||||
node_cfgs.insert(0, node);
|
||||
|
|
|
@ -79,6 +79,9 @@ mod shutdown_tests;
|
|||
#[cfg(all(test, async_signing))]
|
||||
#[allow(unused_mut)]
|
||||
mod async_signer_tests;
|
||||
#[cfg(test)]
|
||||
#[allow(unused_mut)]
|
||||
mod offers_tests;
|
||||
|
||||
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::logger::Logger;
|
||||
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::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
|
||||
/// were retried along a route from a single call to [`Router::find_route_with_id`].
|
||||
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;
|
||||
/// 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),
|
||||
}
|
||||
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
impl_writeable_tlv_based_enum!(Retry,
|
||||
;
|
||||
(0, Attempts)
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
impl_writeable_tlv_based_enum!(Retry,
|
||||
;
|
||||
(0, Attempts),
|
||||
|
@ -314,10 +314,10 @@ impl Retry {
|
|||
(Retry::Attempts(max_retry_count), PaymentAttempts { 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, .. }) =>
|
||||
*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, .. }) =>
|
||||
*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.
|
||||
pub(crate) count: u32,
|
||||
/// 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,
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
phantom: core::marker::PhantomData<T>,
|
||||
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "no-std", test)))]
|
||||
type ConfiguredTime = crate::util::time::MonotonicTime;
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
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;
|
||||
|
||||
impl<T: Time> PaymentAttemptsUsingTime<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
PaymentAttemptsUsingTime {
|
||||
count: 0,
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
first_attempted_at: T::now(),
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
phantom: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
@ -371,9 +371,9 @@ impl<T: Time> PaymentAttemptsUsingTime<T> {
|
|||
|
||||
impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
return write!(f, "attempts: {}", self.count);
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
return write!(
|
||||
f,
|
||||
"attempts: {}, duration: {}s",
|
||||
|
@ -1888,7 +1888,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||
|
||||
|
@ -1932,7 +1932,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||
|
||||
|
@ -1971,7 +1971,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
|
||||
|
||||
|
@ -2178,7 +2178,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 pending_events = Mutex::new(VecDeque::new());
|
||||
|
@ -2229,7 +2229,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 pending_events = Mutex::new(VecDeque::new());
|
||||
|
@ -2288,7 +2288,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 pending_events = Mutex::new(VecDeque::new());
|
||||
|
@ -2347,7 +2347,7 @@ mod tests {
|
|||
let logger = test_utils::TestLogger::new();
|
||||
let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger));
|
||||
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 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::routing::gossip::NodeId;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::{SystemTime, Instant, Duration};
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
use crate::util::time::tests::SinceEpoch;
|
||||
use {
|
||||
crate::util::time::tests::SinceEpoch,
|
||||
std::time::{SystemTime, Instant, Duration},
|
||||
};
|
||||
|
||||
#[test]
|
||||
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();
|
||||
assert_eq!(msg_events.len(), 0);
|
||||
} 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.
|
||||
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();
|
||||
|
|
|
@ -24,14 +24,10 @@ use crate::ln::ChannelId;
|
|||
use crate::ln::features::{InitFeatures, NodeFeatures};
|
||||
use crate::ln::msgs;
|
||||
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::ln::peer_channel_encryptor::{PeerChannelEncryptor, NextNoiseStep, MessageBuf, MSG_BUF_ALLOC_SIZE};
|
||||
use crate::ln::wire;
|
||||
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::offers::{OffersMessage, OffersMessageHandler};
|
||||
use crate::onion_message::packet::OnionMessageContents;
|
||||
|
@ -52,6 +48,8 @@ use core::convert::Infallible;
|
|||
use std::error;
|
||||
#[cfg(not(c_bindings))]
|
||||
use {
|
||||
crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager},
|
||||
crate::onion_message::messenger::{SimpleArcOnionMessenger, SimpleRefOnionMessenger},
|
||||
crate::routing::gossip::{NetworkGraph, P2PGossipSync},
|
||||
crate::sign::KeysManager,
|
||||
crate::sync::Arc,
|
||||
|
|
|
@ -339,6 +339,11 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
|
|||
self
|
||||
}
|
||||
|
||||
pub(crate) fn clear_paths(mut self) -> Self {
|
||||
self.offer.paths = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub(super) fn build_unchecked(self) -> Offer {
|
||||
self.build_without_checks()
|
||||
}
|
||||
|
|
|
@ -297,6 +297,11 @@ impl<'a, T: secp256k1::Signing> RefundBuilder<'a, T> {
|
|||
|
||||
#[cfg(test)]
|
||||
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 {
|
||||
self.refund.features = features;
|
||||
self
|
||||
|
|
|
@ -20,8 +20,6 @@ use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs
|
|||
use crate::blinded_path::utils;
|
||||
use crate::events::{Event, EventHandler, EventsProvider};
|
||||
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::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress};
|
||||
use crate::ln::onion_utils;
|
||||
|
@ -42,6 +40,7 @@ use crate::prelude::*;
|
|||
#[cfg(not(c_bindings))]
|
||||
use {
|
||||
crate::sign::KeysManager,
|
||||
crate::ln::channelmanager::{SimpleArcChannelManager, SimpleRefChannelManager},
|
||||
crate::ln::peer_handler::IgnoringMessageHandler,
|
||||
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`.
|
||||
///
|
||||
/// See [`OnionMessenger`] for example usage.
|
||||
|
@ -814,6 +818,14 @@ where
|
|||
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>(
|
||||
&self, response: Option<T>, reply_path: Option<BlindedPath>, log_suffix: fmt::Arguments
|
||||
) {
|
||||
|
@ -899,9 +911,7 @@ where
|
|||
CMH::Target: CustomOnionMessageHandler,
|
||||
{
|
||||
fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &OnionMessage) {
|
||||
match peel_onion_message(
|
||||
msg, &self.secp_ctx, &*self.node_signer, &*self.logger, &*self.custom_handler
|
||||
) {
|
||||
match self.peel_onion_message(msg) {
|
||||
Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
|
||||
log_trace!(
|
||||
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,
|
||||
// so we'll just set the removal time here to the current UNIX time on the very next invocation
|
||||
// of this function.
|
||||
#[cfg(feature = "no-std")]
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
let mut tracked_time = Some(current_time_unix);
|
||||
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));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
pub(super) fn random_init_seed() -> u64 {
|
||||
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
|
||||
use core::hash::{BuildHasher, Hasher};
|
||||
|
@ -6946,7 +6946,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
fn generate_routes() {
|
||||
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
||||
|
||||
|
@ -6967,7 +6967,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
fn generate_routes_mpp() {
|
||||
use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters};
|
||||
|
||||
|
@ -6988,7 +6988,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
fn generate_large_mpp_routes() {
|
||||
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 {
|
||||
use super::*;
|
||||
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> {
|
||||
type ScoreUpdate = T;
|
||||
type ScoreLookUp = T;
|
||||
|
|
|
@ -822,11 +822,8 @@ pub struct InMemorySigner {
|
|||
channel_value_satoshis: u64,
|
||||
/// Key derivation parameters.
|
||||
channel_keys_id: [u8; 32],
|
||||
/// Seed from which all randomness produced is derived from.
|
||||
rand_bytes_unique_start: [u8; 32],
|
||||
/// Tracks the number of times we've produced randomness to ensure we don't return the same
|
||||
/// bytes twice.
|
||||
rand_bytes_index: AtomicCounter,
|
||||
/// A source of random bytes.
|
||||
entropy_source: RandomBytes,
|
||||
}
|
||||
|
||||
impl PartialEq for InMemorySigner {
|
||||
|
@ -857,8 +854,7 @@ impl Clone for InMemorySigner {
|
|||
channel_parameters: self.channel_parameters.clone(),
|
||||
channel_value_satoshis: self.channel_value_satoshis,
|
||||
channel_keys_id: self.channel_keys_id,
|
||||
rand_bytes_unique_start: self.get_secure_random_bytes(),
|
||||
rand_bytes_index: AtomicCounter::new(),
|
||||
entropy_source: RandomBytes::new(self.get_secure_random_bytes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -892,8 +888,7 @@ impl InMemorySigner {
|
|||
holder_channel_pubkeys,
|
||||
channel_parameters: None,
|
||||
channel_keys_id,
|
||||
rand_bytes_unique_start,
|
||||
rand_bytes_index: AtomicCounter::new(),
|
||||
entropy_source: RandomBytes::new(rand_bytes_unique_start),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1069,10 +1064,7 @@ impl InMemorySigner {
|
|||
|
||||
impl EntropySource for InMemorySigner {
|
||||
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
||||
let index = self.rand_bytes_index.get_increment();
|
||||
let mut nonce = [0u8; 16];
|
||||
nonce[..8].copy_from_slice(&index.to_be_bytes());
|
||||
ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce)
|
||||
self.entropy_source.get_secure_random_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1350,8 +1342,7 @@ impl<ES: Deref> ReadableArgs<ES> for InMemorySigner where ES::Target: EntropySou
|
|||
holder_channel_pubkeys,
|
||||
channel_parameters: counterparty_channel_data,
|
||||
channel_keys_id: keys_id,
|
||||
rand_bytes_unique_start: entropy_source.get_secure_random_bytes(),
|
||||
rand_bytes_index: AtomicCounter::new(),
|
||||
entropy_source: RandomBytes::new(entropy_source.get_secure_random_bytes()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1379,8 +1370,7 @@ pub struct KeysManager {
|
|||
channel_master_key: ExtendedPrivKey,
|
||||
channel_child_index: AtomicUsize,
|
||||
|
||||
rand_bytes_unique_start: [u8; 32],
|
||||
rand_bytes_index: AtomicCounter,
|
||||
entropy_source: RandomBytes,
|
||||
|
||||
seed: [u8; 32],
|
||||
starting_time_secs: u64,
|
||||
|
@ -1449,8 +1439,7 @@ impl KeysManager {
|
|||
channel_master_key,
|
||||
channel_child_index: AtomicUsize::new(0),
|
||||
|
||||
rand_bytes_unique_start,
|
||||
rand_bytes_index: AtomicCounter::new(),
|
||||
entropy_source: RandomBytes::new(rand_bytes_unique_start),
|
||||
|
||||
seed: *seed,
|
||||
starting_time_secs,
|
||||
|
@ -1631,10 +1620,7 @@ impl KeysManager {
|
|||
|
||||
impl EntropySource for KeysManager {
|
||||
fn get_secure_random_bytes(&self) -> [u8; 32] {
|
||||
let index = self.rand_bytes_index.get_increment();
|
||||
let mut nonce = [0u8; 16];
|
||||
nonce[..8].copy_from_slice(&index.to_be_bytes());
|
||||
ChaCha20::get_single_block(&self.rand_bytes_unique_start, &nonce)
|
||||
self.entropy_source.get_secure_random_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
#[test]
|
||||
pub fn dyn_sign() {
|
||||
|
|
|
@ -32,10 +32,10 @@ use crate::ln::msgs::LightningError;
|
|||
use crate::ln::script::ShutdownScript;
|
||||
use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice};
|
||||
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::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::sync::RwLock;
|
||||
use crate::util::config::UserConfig;
|
||||
|
@ -104,14 +104,29 @@ impl chaininterface::FeeEstimator for TestFeeEstimator {
|
|||
}
|
||||
|
||||
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 next_routes: Mutex<VecDeque<(RouteParameters, Result<Route, LightningError>)>>,
|
||||
pub scorer: &'a RwLock<TestScorer>,
|
||||
}
|
||||
|
||||
impl<'a> TestRouter<'a> {
|
||||
pub fn new(network_graph: Arc<NetworkGraph<&'a TestLogger>>, scorer: &'a RwLock<TestScorer>) -> Self {
|
||||
Self { network_graph, next_routes: Mutex::new(VecDeque::new()), scorer }
|
||||
pub fn new(
|
||||
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>) {
|
||||
|
@ -185,38 +200,36 @@ impl<'a> Router for TestRouter<'a> {
|
|||
}
|
||||
return find_route_res;
|
||||
}
|
||||
let logger = TestLogger::new();
|
||||
find_route(
|
||||
payer, params, &self.network_graph, first_hops, &logger,
|
||||
&ScorerAccountingForInFlightHtlcs::new(self.scorer.read().unwrap(), &inflight_htlcs), &Default::default(),
|
||||
&[42; 32]
|
||||
)
|
||||
|
||||
self.router.find_route(payer, params, first_hops, inflight_htlcs)
|
||||
}
|
||||
|
||||
fn create_blinded_payment_paths<
|
||||
ES: EntropySource + ?Sized, T: secp256k1::Signing + secp256k1::Verification
|
||||
>(
|
||||
&self, _recipient: PublicKey, _first_hops: Vec<ChannelDetails>, _tlvs: ReceiveTlvs,
|
||||
_amount_msats: u64, _entropy_source: &ES, _secp_ctx: &Secp256k1<T>
|
||||
&self, recipient: PublicKey, first_hops: Vec<ChannelDetails>, tlvs: ReceiveTlvs,
|
||||
amount_msats: u64, entropy_source: &ES, secp_ctx: &Secp256k1<T>
|
||||
) -> 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> {
|
||||
fn find_path(
|
||||
&self, _sender: PublicKey, _peers: Vec<PublicKey>, _destination: Destination
|
||||
&self, sender: PublicKey, peers: Vec<PublicKey>, destination: Destination
|
||||
) -> Result<OnionMessagePath, ()> {
|
||||
unreachable!()
|
||||
self.router.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>
|
||||
&self, recipient: PublicKey, peers: Vec<PublicKey>, entropy_source: &ES,
|
||||
secp_ctx: &Secp256k1<T>
|
||||
) -> 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 {}
|
||||
|
||||
impl EntropySource for OnlyReadsKeysInterface {
|
||||
|
@ -1390,6 +1430,9 @@ impl ScoreUpdate for TestScorer {
|
|||
fn time_passed(&mut self, _duration_since_epoch: Duration) {}
|
||||
}
|
||||
|
||||
#[cfg(c_bindings)]
|
||||
impl crate::routing::scoring::Score for TestScorer {}
|
||||
|
||||
impl Drop for TestScorer {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "std")] {
|
||||
|
|
|
@ -59,15 +59,15 @@ impl Sub<Duration> for Eternity {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
pub struct MonotonicTime(std::time::Instant);
|
||||
|
||||
/// 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`).
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
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 {
|
||||
fn now() -> Self {
|
||||
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 {
|
||||
type Output = Self;
|
||||
|
||||
|
@ -177,7 +177,7 @@ pub mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no-std"))]
|
||||
#[cfg(feature = "std")]
|
||||
fn monotonic_time_subtracts() {
|
||||
let now = super::MonotonicTime::now();
|
||||
assert!(now.elapsed() < Duration::from_secs(10));
|
||||
|
|
Loading…
Add table
Reference in a new issue