//! Convenient utilities to create an invoice.
-use crate::{CreationError, Currency, Invoice, InvoiceBuilder, SignOrCreationError};
+use crate::{Bolt11Invoice, CreationError, Currency, InvoiceBuilder, SignOrCreationError};
-use crate::{prelude::*, Description, InvoiceDescription, Sha256};
+use crate::{prelude::*, Description, Bolt11InvoiceDescription, Sha256};
use bech32::ToBase32;
use bitcoin_hashes::Hash;
use lightning::chain;
use secp256k1::PublicKey;
use core::ops::Deref;
use core::time::Duration;
+use core::iter::Iterator;
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
/// See [`PhantomKeysManager`] for more information on phantom node payments.
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: String,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
let description = Description::new(description).map_err(SignOrCreationError::CreationError)?;
- let description = InvoiceDescription::Direct(&description,);
+ let description = Bolt11InvoiceDescription::Direct(&description,);
_create_phantom_invoice::<ES, NS, L>(
amt_msat, payment_hash, description, invoice_expiry_delta_secs, phantom_route_hints,
entropy_source, node_signer, logger, network, min_final_cltv_expiry_delta, duration_since_epoch,
amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, invoice_expiry_delta_secs: u32,
description_hash: Sha256, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
L::Target: Logger,
{
_create_phantom_invoice::<ES, NS, L>(
- amt_msat, payment_hash, InvoiceDescription::Hash(&description_hash),
+ amt_msat, payment_hash, Bolt11InvoiceDescription::Hash(&description_hash),
invoice_expiry_delta_secs, phantom_route_hints, entropy_source, node_signer, logger, network,
min_final_cltv_expiry_delta, duration_since_epoch,
)
}
+const MAX_CHANNEL_HINTS: usize = 3;
+
fn _create_phantom_invoice<ES: Deref, NS: Deref, L: Deref>(
- amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: InvoiceDescription,
+ amt_msat: Option<u64>, payment_hash: Option<PaymentHash>, description: Bolt11InvoiceDescription,
invoice_expiry_delta_secs: u32, phantom_route_hints: Vec<PhantomRouteHints>, entropy_source: ES,
node_signer: NS, logger: L, network: Currency, min_final_cltv_expiry_delta: Option<u16>, duration_since_epoch: Duration,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
}
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
// If we ever see performance here being too slow then we should probably take this ExpandedKey as a parameter instead.
};
log_trace!(logger, "Creating phantom invoice from {} participating nodes with payment hash {}",
- phantom_route_hints.len(), log_bytes!(payment_hash.0));
+ phantom_route_hints.len(), &payment_hash);
let mut invoice = invoice
.duration_since_epoch(duration_since_epoch)
invoice = invoice.amount_milli_satoshis(amt);
}
- for route_hint in select_phantom_hints(amt_msat, phantom_route_hints, logger) {
+
+ for route_hint in select_phantom_hints(amt_msat, phantom_route_hints, logger).take(MAX_CHANNEL_HINTS) {
invoice = invoice.private_route(route_hint);
}
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
///
/// [`PhantomKeysManager`]: lightning::sign::PhantomKeysManager
fn select_phantom_hints<L: Deref>(amt_msat: Option<u64>, phantom_route_hints: Vec<PhantomRouteHints>,
- logger: L) -> Vec<RouteHint>
+ logger: L) -> impl Iterator<Item = RouteHint>
where
L::Target: Logger,
{
- let mut phantom_hints: Vec<Vec<RouteHint>> = Vec::new();
+ let mut phantom_hints: Vec<_> = Vec::new();
for PhantomRouteHints { channels, phantom_scid, real_node_pubkey } in phantom_route_hints {
log_trace!(logger, "Generating phantom route hints for node {}",
log_pubkey!(real_node_pubkey));
- let mut route_hints = sort_and_filter_channels(channels, amt_msat, &logger);
+ let route_hints = sort_and_filter_channels(channels, amt_msat, &logger);
// If we have any public channel, the route hints from `sort_and_filter_channels` will be
// empty. In that case we create a RouteHint on which we will push a single hop with the
// phantom route into the invoice, and let the sender find the path to the `real_node_pubkey`
// node by looking at our public channels.
- if route_hints.is_empty() {
- route_hints.push(RouteHint(vec![]))
- }
- for route_hint in &mut route_hints {
- route_hint.0.push(RouteHintHop {
- src_node_id: real_node_pubkey,
- short_channel_id: phantom_scid,
- fees: RoutingFees {
- base_msat: 0,
- proportional_millionths: 0,
- },
- cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
- htlc_minimum_msat: None,
- htlc_maximum_msat: None,});
- }
+ let empty_route_hints = route_hints.len() == 0;
+ let mut have_pushed_empty = false;
+ let route_hints = route_hints
+ .chain(core::iter::from_fn(move || {
+ if empty_route_hints && !have_pushed_empty {
+ // set flag of having handled the empty route_hints and ensure empty vector
+ // returned only once
+ have_pushed_empty = true;
+ Some(RouteHint(Vec::new()))
+ } else {
+ None
+ }
+ }))
+ .map(move |mut hint| {
+ hint.0.push(RouteHintHop {
+ src_node_id: real_node_pubkey,
+ short_channel_id: phantom_scid,
+ fees: RoutingFees {
+ base_msat: 0,
+ proportional_millionths: 0,
+ },
+ cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
+ htlc_minimum_msat: None,
+ htlc_maximum_msat: None,
+ });
+ hint
+ });
phantom_hints.push(route_hints);
}
// the hints across our real nodes we add one hint from each in turn until no node has any hints
// left (if one node has more hints than any other, these will accumulate at the end of the
// vector).
- let mut invoice_hints: Vec<RouteHint> = Vec::new();
- let mut hint_idx = 0;
+ rotate_through_iterators(phantom_hints)
+}
- loop {
- let mut remaining_hints = false;
+/// Draw items iteratively from multiple iterators. The items are retrieved by index and
+/// rotates through the iterators - first the zero index then the first index then second index, etc.
+fn rotate_through_iterators<T, I: Iterator<Item = T>>(mut vecs: Vec<I>) -> impl Iterator<Item = T> {
+ let mut iterations = 0;
- for hints in phantom_hints.iter() {
- if invoice_hints.len() == 3 {
- return invoice_hints
+ core::iter::from_fn(move || {
+ let mut exhausted_iterators = 0;
+ loop {
+ if vecs.is_empty() {
+ return None;
}
-
- if hint_idx < hints.len() {
- invoice_hints.push(hints[hint_idx].clone());
- remaining_hints = true
+ let next_idx = iterations % vecs.len();
+ iterations += 1;
+ if let Some(item) = vecs[next_idx].next() {
+ return Some(item);
+ }
+ // exhausted_vectors increase when the "next_idx" vector is exhausted
+ exhausted_iterators += 1;
+ // The check for exhausted iterators gets reset to 0 after each yield of `Some()`
+ // The loop will return None when all of the nested iterators are exhausted
+ if exhausted_iterators == vecs.len() {
+ return None;
}
}
-
- if !remaining_hints {
- return invoice_hints
- }
-
- hint_idx +=1;
- }
+ })
}
#[cfg(feature = "std")]
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32,
min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Hash(&description_hash),
+ Bolt11InvoiceDescription::Hash(&description_hash),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
)
}
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
{
_create_invoice_from_channelmanager_and_duration_since_epoch(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription,
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
channelmanager, node_signer, logger, network, amt_msat,
- InvoiceDescription::Direct(
+ Bolt11InvoiceDescription::Direct(
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
),
duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret,
fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>, node_signer: NS, logger: L,
- network: Currency, amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
- invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret,
- min_final_cltv_expiry_delta: Option<u16>,
-) -> Result<Invoice, SignOrCreationError<()>>
+ network: Currency, amt_msat: Option<u64>, description: Bolt11InvoiceDescription,
+ duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, payment_hash: PaymentHash,
+ payment_secret: PaymentSecret, min_final_cltv_expiry_delta: Option<u16>,
+) -> Result<Bolt11Invoice, SignOrCreationError<()>>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
}
- log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0));
+ log_trace!(logger, "Creating invoice with payment hash {}", &payment_hash);
let invoice = match description {
- InvoiceDescription::Direct(description) => {
+ Bolt11InvoiceDescription::Direct(description) => {
InvoiceBuilder::new(network).description(description.0.clone())
}
- InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
+ Bolt11InvoiceDescription::Hash(hash) => InvoiceBuilder::new(network).description_hash(hash.0),
};
let mut invoice = invoice
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| node_signer.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
- Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
+ Ok(inv) => Ok(Bolt11Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}
/// * Sorted by lowest inbound capacity if an online channel with the minimum amount requested exists,
/// otherwise sort by highest inbound capacity to give the payment the best chance of succeeding.
fn sort_and_filter_channels<L: Deref>(
- channels: Vec<ChannelDetails>, min_inbound_capacity_msat: Option<u64>, logger: &L
-) -> Vec<RouteHint> where L::Target: Logger {
+ channels: Vec<ChannelDetails>,
+ min_inbound_capacity_msat: Option<u64>,
+ logger: &L,
+) -> impl ExactSizeIterator<Item = RouteHint>
+where
+ L::Target: Logger,
+{
let mut filtered_channels: HashMap<PublicKey, ChannelDetails> = HashMap::new();
let min_inbound_capacity = min_inbound_capacity_msat.unwrap_or(0);
let mut min_capacity_channel_exists = false;
let mut online_min_capacity_channel_exists = false;
let mut has_pub_unconf_chan = false;
+ let route_hint_from_channel = |channel: ChannelDetails| {
+ let forwarding_info = channel.counterparty.forwarding_info.as_ref().unwrap();
+ RouteHint(vec![RouteHintHop {
+ src_node_id: channel.counterparty.node_id,
+ short_channel_id: channel.get_inbound_payment_scid().unwrap(),
+ fees: RoutingFees {
+ base_msat: forwarding_info.fee_base_msat,
+ proportional_millionths: forwarding_info.fee_proportional_millionths,
+ },
+ cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
+ htlc_minimum_msat: channel.inbound_htlc_minimum_msat,
+ htlc_maximum_msat: channel.inbound_htlc_maximum_msat,}])
+ };
+
log_trace!(logger, "Considering {} channels for invoice route hints", channels.len());
for channel in channels.into_iter().filter(|chan| chan.is_channel_ready) {
if channel.get_inbound_payment_scid().is_none() || channel.counterparty.forwarding_info.is_none() {
// look at the public channels instead.
log_trace!(logger, "Not including channels in invoice route hints on account of public channel {}",
log_bytes!(channel.channel_id));
- return vec![]
+ return vec![].into_iter().take(MAX_CHANNEL_HINTS).map(route_hint_from_channel);
}
}
}
}
- let route_hint_from_channel = |channel: ChannelDetails| {
- let forwarding_info = channel.counterparty.forwarding_info.as_ref().unwrap();
- RouteHint(vec![RouteHintHop {
- src_node_id: channel.counterparty.node_id,
- short_channel_id: channel.get_inbound_payment_scid().unwrap(),
- fees: RoutingFees {
- base_msat: forwarding_info.fee_base_msat,
- proportional_millionths: forwarding_info.fee_proportional_millionths,
- },
- cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
- htlc_minimum_msat: channel.inbound_htlc_minimum_msat,
- htlc_maximum_msat: channel.inbound_htlc_maximum_msat,}])
- };
// If all channels are private, prefer to return route hints which have a higher capacity than
// the payment value and where we're currently connected to the channel counterparty.
// Even if we cannot satisfy both goals, always ensure we include *some* hints, preferring
} else {
b.inbound_capacity_msat.cmp(&a.inbound_capacity_msat)
}});
- eligible_channels.into_iter().take(3).map(route_hint_from_channel).collect::<Vec<RouteHint>>()
+
+ eligible_channels.into_iter().take(MAX_CHANNEL_HINTS).map(route_hint_from_channel)
}
/// prefer_current_channel chooses a channel to use for route hints between a currently selected and candidate
#[cfg(test)]
mod test {
+ use core::cell::RefCell;
use core::time::Duration;
- use crate::{Currency, Description, InvoiceDescription, SignOrCreationError, CreationError};
+ use crate::{Currency, Description, Bolt11InvoiceDescription, SignOrCreationError, CreationError};
use bitcoin_hashes::{Hash, sha256};
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::sign::PhantomKeysManager;
- use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event};
+ use lightning::events::{MessageSendEvent, MessageSendEventsProvider, Event, EventsProvider};
use lightning::ln::{PaymentPreimage, PaymentHash};
use lightning::ln::channelmanager::{PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY_DELTA, PaymentId, RecipientOnionFields, Retry};
use lightning::ln::functional_test_utils::*;
use lightning::routing::router::{PaymentParameters, RouteParameters};
use lightning::util::test_utils;
use lightning::util::config::UserConfig;
- use crate::utils::create_invoice_from_channelmanager_and_duration_since_epoch;
+ use crate::utils::{create_invoice_from_channelmanager_and_duration_since_epoch, rotate_through_iterators};
use std::collections::HashSet;
#[test]
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
// If no `min_final_cltv_expiry_delta` is specified, then it should be `MIN_FINAL_CLTV_EXPIRY_DELTA`.
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
// Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
}
#[test]
).unwrap();
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&payment_hash.0[..]).unwrap());
}
} else {
None
};
+ let genesis_timestamp = bitcoin::blockdata::constants::genesis_block(bitcoin::Network::Testnet).header.time as u64;
let non_default_invoice_expiry_secs = 4200;
let invoice =
crate::utils::create_phantom_invoice::<&test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestLogger>(
Some(payment_amt), payment_hash, "test".to_string(), non_default_invoice_expiry_secs,
route_hints, nodes[1].keys_manager, nodes[1].keys_manager, nodes[1].logger,
- Currency::BitcoinTestnet, None, Duration::from_secs(1234567)
+ Currency::BitcoinTestnet, None, Duration::from_secs(genesis_timestamp)
).unwrap();
let (payment_hash, payment_secret) = (PaymentHash(invoice.payment_hash().into_inner()), *invoice.payment_secret());
let payment_preimage = if user_generated_pmt_hash {
};
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
- assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.route_hints().len(), 2);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
assert!(!invoice.features().unwrap().supports_basic_mpp());
// Note that we have to "forward pending HTLCs" twice before we see the PaymentClaimable as
// this "emulates" the payment taking two hops, providing some privacy to make phantom node
// payments "look real" by taking more time.
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
- expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
- nodes[fwd_idx].node.process_pending_htlc_forwards();
+ let other_events = RefCell::new(Vec::new());
+ let forward_event_handler = |event: Event| {
+ if let Event::PendingHTLCsForwardable { .. } = event {
+ nodes[fwd_idx].node.process_pending_htlc_forwards();
+ } else {
+ other_events.borrow_mut().push(event);
+ }
+ };
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
+ nodes[fwd_idx].node.process_pending_events(&forward_event_handler);
let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
- expect_payment_claimable!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
+ assert_eq!(other_events.borrow().len(), 1);
+ check_payment_claimable(&other_events.borrow()[0], payment_hash, payment_secret, payment_amt, payment_preimage_opt, invoice.recover_payee_pub_key());
do_claim_payment_along_route(&nodes[0], &[&vec!(&nodes[fwd_idx])[..]], false, payment_preimage);
- let events = nodes[0].node.get_and_clear_pending_events();
- assert_eq!(events.len(), 2);
- match events[0] {
- Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
- assert_eq!(payment_preimage, *ev_preimage);
- assert_eq!(payment_hash, *ev_hash);
- assert_eq!(fee_paid_msat, &Some(0));
- },
- _ => panic!("Unexpected event")
- }
- match events[1] {
- Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
- assert_eq!(hash, Some(payment_hash));
- },
- _ => panic!("Unexpected event")
- }
+ expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
}
#[test]
assert_eq!(invoice.amount_pico_btc(), Some(200_000));
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
assert_eq!(invoice.expiry_time(), Duration::from_secs(non_default_invoice_expiry_secs.into()));
- assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
+ assert_eq!(invoice.description(), Bolt11InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Description hash phantom invoice".as_bytes()))));
}
#[test]
_ => panic!(),
}
}
+
+ #[test]
+ fn test_rotate_through_iterators() {
+ // two nested vectors
+ let a = vec![vec!["a0", "b0", "c0"].into_iter(), vec!["a1", "b1"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "a1", "b0", "b1", "c0"];
+ assert_eq!(expected, result);
+
+ // test single nested vector
+ let a = vec![vec!["a0", "b0", "c0"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "b0", "c0"];
+ assert_eq!(expected, result);
+
+ // test second vector with only one element
+ let a = vec![vec!["a0", "b0", "c0"].into_iter(), vec!["a1"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "a1", "b0", "c0"];
+ assert_eq!(expected, result);
+
+ // test three nestend vectors
+ let a = vec![vec!["a0"].into_iter(), vec!["a1", "b1", "c1"].into_iter(), vec!["a2"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "a1", "a2", "b1", "c1"];
+ assert_eq!(expected, result);
+
+ // test single nested vector with a single value
+ let a = vec![vec!["a0"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0"];
+ assert_eq!(expected, result);
+
+ // test single empty nested vector
+ let a:Vec<std::vec::IntoIter<&str>> = vec![vec![].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<&str>>();
+ let expected:Vec<&str> = vec![];
+
+ assert_eq!(expected, result);
+
+ // test first nested vector is empty
+ let a:Vec<std::vec::IntoIter<&str>>= vec![vec![].into_iter(), vec!["a1", "b1", "c1"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<&str>>();
+
+ let expected = vec!["a1", "b1", "c1"];
+ assert_eq!(expected, result);
+
+ // test two empty vectors
+ let a:Vec<std::vec::IntoIter<&str>> = vec![vec![].into_iter(), vec![].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<&str>>();
+
+ let expected:Vec<&str> = vec![];
+ assert_eq!(expected, result);
+
+ // test an empty vector amongst other filled vectors
+ let a = vec![
+ vec!["a0", "b0", "c0"].into_iter(),
+ vec![].into_iter(),
+ vec!["a1", "b1", "c1"].into_iter(),
+ vec!["a2", "b2", "c2"].into_iter(),
+ ];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "a1", "a2", "b0", "b1", "b2", "c0", "c1", "c2"];
+ assert_eq!(expected, result);
+
+ // test a filled vector between two empty vectors
+ let a = vec![vec![].into_iter(), vec!["a1", "b1", "c1"].into_iter(), vec![].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a1", "b1", "c1"];
+ assert_eq!(expected, result);
+
+ // test an empty vector at the end of the vectors
+ let a = vec![vec!["a0", "b0", "c0"].into_iter(), vec![].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "b0", "c0"];
+ assert_eq!(expected, result);
+
+ // test multiple empty vectors amongst multiple filled vectors
+ let a = vec![
+ vec![].into_iter(),
+ vec!["a1", "b1", "c1"].into_iter(),
+ vec![].into_iter(),
+ vec!["a3", "b3"].into_iter(),
+ vec![].into_iter(),
+ ];
+
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a1", "a3", "b1", "b3", "c1"];
+ assert_eq!(expected, result);
+
+ // test one element in the first nested vectore and two elements in the second nested
+ // vector
+ let a = vec![vec!["a0"].into_iter(), vec!["a1", "b1"].into_iter()];
+ let result = rotate_through_iterators(a).collect::<Vec<_>>();
+
+ let expected = vec!["a0", "a1", "b1"];
+ assert_eq!(expected, result);
+ }
}