Merge pull request #2697 from jkczyz/2023-10-offer-functional-tests

Functional tests for BOLT 12 Offers payment flow
This commit is contained in:
Matt Corallo 2024-01-24 23:14:01 +00:00 committed by GitHub
commit 76fff953bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1155 additions and 124 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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;

View 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()));
}

View file

@ -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());

View file

@ -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();

View file

@ -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,

View file

@ -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()
}

View file

@ -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

View file

@ -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,

View file

@ -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);

View file

@ -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;

View 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;

View file

@ -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() {

View file

@ -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")] {

View file

@ -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));