use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
use crate::offers::invoice::Bolt12Invoice;
-use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
+use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
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;
use core::fmt::{self, Display, Formatter};
use core::ops::Deref;
+use core::time::Duration;
use crate::prelude::*;
use crate::sync::Mutex;
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;
-/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until an invoice request without
-/// a response is timed out.
-///
-/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
-const INVOICE_REQUEST_TIMEOUT_TICKS: u8 = 3;
-
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
/// and later, also stores information for retrying the payment.
pub(crate) enum PendingOutboundPayment {
session_privs: HashSet<[u8; 32]>,
},
AwaitingInvoice {
- timer_ticks_without_response: u8,
+ expiration: StaleExpiration,
retry_strategy: Retry,
max_total_routing_fee_msat: Option<u64>,
},
params.previously_failed_channels.push(scid);
}
}
+ pub fn insert_previously_failed_blinded_path(&mut self, blinded_tail: &BlindedTail) {
+ if let PendingOutboundPayment::Retryable { payment_params: Some(params), .. } = self {
+ params.insert_previously_failed_blinded_path(blinded_tail);
+ }
+ }
fn is_awaiting_invoice(&self) -> bool {
match self {
PendingOutboundPayment::AwaitingInvoice { .. } => true,
}
fn mark_fulfilled(&mut self) {
- let mut session_privs = HashSet::new();
+ let mut session_privs = new_hash_set();
core::mem::swap(&mut session_privs, match self {
PendingOutboundPayment::Legacy { session_privs } |
PendingOutboundPayment::Retryable { session_privs, .. } |
fn mark_abandoned(&mut self, reason: PaymentFailureReason) {
if let PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } = self {
- let mut our_session_privs = HashSet::new();
+ let mut our_session_privs = new_hash_set();
core::mem::swap(&mut our_session_privs, session_privs);
*self = PendingOutboundPayment::Abandoned {
session_privs: our_session_privs,
};
} else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self {
*self = PendingOutboundPayment::Abandoned {
- session_privs: HashSet::new(),
+ session_privs: new_hash_set(),
payment_hash: *payment_hash,
reason: Some(reason)
};
if insert_res {
if let PendingOutboundPayment::Retryable {
ref mut pending_amt_msat, ref mut pending_fee_msat,
- ref mut remaining_max_total_routing_fee_msat, ..
+ ref mut remaining_max_total_routing_fee_msat, ..
} = self {
*pending_amt_msat += path.final_value_msat();
let path_fee_msat = path.fee_msat();
/// 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.
///
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),
(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),
}
/// 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,
}
}
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",
}
}
+/// How long before a [`PendingOutboundPayment::AwaitingInvoice`] should be considered stale and
+/// candidate for removal in [`OutboundPayments::remove_stale_payments`].
+#[derive(Clone, Copy)]
+pub(crate) enum StaleExpiration {
+ /// Number of times [`OutboundPayments::remove_stale_payments`] is called.
+ TimerTicks(u64),
+ /// Duration since the Unix epoch.
+ AbsoluteTimeout(core::time::Duration),
+}
+
+impl_writeable_tlv_based_enum!(StaleExpiration,
+ ;
+ (0, TimerTicks),
+ (2, AbsoluteTimeout)
+);
+
/// Indicates an immediate error on [`ChannelManager::send_payment`]. Further errors may be
/// surfaced later via [`Event::PaymentPathFailed`] and [`Event::PaymentFailed`].
///
/// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
/// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
/// This is validated when setting this field using [`Self::with_custom_tlvs`].
+ #[cfg(not(c_bindings))]
pub fn custom_tlvs(&self) -> &Vec<(u64, Vec<u8>)> {
&self.custom_tlvs
}
+ /// Gets the custom TLVs that will be sent or have been received.
+ ///
+ /// Custom TLVs allow sending extra application-specific data with a payment. They provide
+ /// additional flexibility on top of payment metadata, as while other implementations may
+ /// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs
+ /// do not have this restriction.
+ ///
+ /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each
+ /// represented by a `(u64, Vec<u8>)` for its type number and serialized value respectively.
+ /// This is validated when setting this field using [`Self::with_custom_tlvs`].
+ #[cfg(c_bindings)]
+ pub fn custom_tlvs(&self) -> Vec<(u64, Vec<u8>)> {
+ self.custom_tlvs.clone()
+ }
+
/// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we
/// have to make sure that some fields match exactly across the parts. For those that aren't
/// required to match, if they don't match we should remove them so as to not expose data
impl OutboundPayments {
pub(super) fn new() -> Self {
Self {
- pending_outbound_payments: Mutex::new(HashMap::new()),
+ pending_outbound_payments: Mutex::new(new_hash_map()),
retry_lock: Mutex::new(()),
}
}
{
let preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
- let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
+ let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array());
self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage),
retry_strategy, route_params, router, first_hops, inflight_htlcs, entropy_source,
node_signer, best_block_height, logger, pending_events, send_payment_along_path)
{
let preimage = payment_preimage
.unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes()));
- let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
+ let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array());
let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(),
payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?;
}
}
- #[allow(unused)]
pub(super) fn send_payment_for_bolt12_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
&self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R,
first_hops: Vec<ChannelDetails>, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
{
let payment_hash = invoice.payment_hash();
- let mut max_total_routing_fee_msat = None;
+ let max_total_routing_fee_msat;
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
hash_map::Entry::Occupied(entry) => match entry.get() {
PendingOutboundPayment::AwaitingInvoice { retry_strategy, max_total_routing_fee_msat: max_total_fee, .. } => {
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
};
- let route_params = RouteParameters {
- payment_params: PaymentParameters::from_bolt12_invoice(&invoice),
- final_value_msat: invoice.amount_msats(),
- max_total_routing_fee_msat,
- };
+ let pay_params = PaymentParameters::from_bolt12_invoice(&invoice);
+ let amount_msat = invoice.amount_msats();
+ let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
+ if let Some(max_fee_msat) = max_total_routing_fee_msat {
+ route_params.max_total_routing_fee_msat = Some(max_fee_msat);
+ }
self.find_route_and_send_payment(
payment_hash, payment_id, route_params, router, first_hops, &inflight_htlcs,
RetryableSendFailure::RouteNotFound
})?;
- if let Some(route_route_params) = route.route_params.as_mut() {
- if route_route_params.final_value_msat != route_params.final_value_msat {
- debug_assert!(false,
- "Routers are expected to return a route which includes the requested final_value_msat");
- route_route_params.final_value_msat = route_params.final_value_msat;
- }
+ if route.route_params.as_ref() != Some(&route_params) {
+ debug_assert!(false,
+ "Routers are expected to return a Route which includes the requested RouteParameters");
+ route.route_params = Some(route_params.clone());
}
let onion_session_privs = self.add_new_pending_payment(payment_hash,
}
};
- if let Some(route_route_params) = route.route_params.as_mut() {
- if route_route_params.final_value_msat != route_params.final_value_msat {
- debug_assert!(false,
- "Routers are expected to return a route which includes the requested final_value_msat");
- route_route_params.final_value_msat = route_params.final_value_msat;
- }
+ if route.route_params.as_ref() != Some(&route_params) {
+ debug_assert!(false,
+ "Routers are expected to return a Route which includes the requested RouteParameters");
+ route.route_params = Some(route_params.clone());
}
for path in route.paths.iter() {
retry_strategy,
attempts: PaymentAttempts::new(),
payment_params,
- session_privs: HashSet::new(),
+ session_privs: new_hash_set(),
pending_amt_msat: 0,
pending_fee_msat: Some(0),
payment_hash,
(payment, onion_session_privs)
}
- #[allow(unused)]
pub(super) fn add_new_awaiting_invoice(
- &self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
+ &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>
) -> Result<(), ()> {
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
match pending_outbounds.entry(payment_id) {
hash_map::Entry::Occupied(_) => Err(()),
hash_map::Entry::Vacant(entry) => {
entry.insert(PendingOutboundPayment::AwaitingInvoice {
- timer_ticks_without_response: 0,
+ expiration,
retry_strategy,
max_total_routing_fee_msat,
});
continue 'path_check;
}
}
+ for (i, hop) in path.hops.iter().enumerate() {
+ // Check for duplicate channel_id in the remaining hops of the path
+ if path.hops.iter().skip(i + 1).any(|other_hop| other_hop.short_channel_id == hop.short_channel_id) {
+ path_errs.push(Err(APIError::InvalidRoute{err: "Path went through the same channel twice".to_owned()}));
+ continue 'path_check;
+ }
+ }
total_value += path.final_value_msat();
path_errs.push(Ok(()));
}
let mut pending_events = pending_events.lock().unwrap();
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) {
if !payment.get().is_fulfilled() {
- let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array());
log_info!(logger, "Payment with id {} and hash {} sent!", payment_id, payment_hash);
let fee_paid_msat = payment.get().get_pending_fee_msat();
pending_events.push_back((events::Event::PaymentSent {
// TODO: We should have a second monitor event that informs us of payments
// irrevocably fulfilled.
if payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
- let payment_hash = Some(PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()));
+ let payment_hash = Some(PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()));
pending_events.push_back((events::Event::PaymentPathSuccessful {
payment_id,
payment_hash,
}
pub(super) fn remove_stale_payments(
- &self, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
+ &self, duration_since_epoch: Duration,
+ pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
{
let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
- #[cfg(not(invreqfailed))]
- let pending_events = pending_events.lock().unwrap();
- #[cfg(invreqfailed)]
let mut pending_events = pending_events.lock().unwrap();
- pending_outbound_payments.retain(|payment_id, payment| {
+ pending_outbound_payments.retain(|payment_id, payment| match payment {
// If an outbound payment was completed, and no pending HTLCs remain, we should remove it
// from the map. However, if we did that immediately when the last payment HTLC is claimed,
// this could race the user making a duplicate send_payment call and our idempotency
// removal. This should be more than sufficient to ensure the idempotency of any
// `send_payment` calls that were made at the same time the `PaymentSent` event was being
// processed.
- if let PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment {
+ PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } => {
let mut no_remaining_entries = session_privs.is_empty();
if no_remaining_entries {
for (ev, _) in pending_events.iter() {
*timer_ticks_without_htlcs = 0;
true
}
- } else if let PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response, .. } = payment {
- *timer_ticks_without_response += 1;
- if *timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS {
- true
- } else {
- #[cfg(invreqfailed)]
+ },
+ PendingOutboundPayment::AwaitingInvoice { expiration, .. } => {
+ let is_stale = match expiration {
+ StaleExpiration::AbsoluteTimeout(absolute_expiry) => {
+ *absolute_expiry <= duration_since_epoch
+ },
+ StaleExpiration::TimerTicks(timer_ticks_remaining) => {
+ if *timer_ticks_remaining > 0 {
+ *timer_ticks_remaining -= 1;
+ false
+ } else {
+ true
+ }
+ },
+ };
+ if is_stale {
pending_events.push_back(
(events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None)
);
false
+ } else {
+ true
}
- } else { true }
+ },
+ _ => true,
});
}
#[cfg(test)]
let DecodedOnionFailure {
network_update, short_channel_id, payment_failed_permanently, onion_error_code,
- onion_error_data
+ onion_error_data, failed_within_blinded_path
} = onion_error.decode_onion_failure(secp_ctx, logger, &source);
#[cfg(not(test))]
- let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } =
- onion_error.decode_onion_failure(secp_ctx, logger, &source);
+ let DecodedOnionFailure {
+ network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path
+ } = onion_error.decode_onion_failure(secp_ctx, logger, &source);
let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret);
let mut session_priv_bytes = [0; 32];
// next-hop is needlessly blaming us!
payment.get_mut().insert_previously_failed_scid(scid);
}
+ if failed_within_blinded_path {
+ debug_assert!(short_channel_id.is_none());
+ if let Some(bt) = &path.blinded_tail {
+ payment.get_mut().insert_previously_failed_blinded_path(&bt);
+ } else { debug_assert!(false); }
+ }
if payment_is_probe || !is_retryable_now || payment_failed_permanently {
let reason = if payment_failed_permanently {
payment.remove();
}
} else if let PendingOutboundPayment::AwaitingInvoice { .. } = payment.get() {
- #[cfg(invreqfailed)]
pending_events.lock().unwrap().push_back((events::Event::InvoiceRequestFailed {
payment_id,
}, None));
let mut preimage = [0u8; 64];
preimage[..32].copy_from_slice(&probing_cookie_secret);
preimage[32..].copy_from_slice(&payment_id.0);
- PaymentHash(Sha256::hash(&preimage).into_inner())
+ PaymentHash(Sha256::hash(&preimage).to_byte_array())
}
impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
(2, payment_hash, required),
},
(5, AwaitingInvoice) => {
- (0, timer_ticks_without_response, required),
+ (0, expiration, required),
(2, retry_strategy, required),
(4, max_total_routing_fee_msat, option),
},
use bitcoin::network::constants::Network;
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+ use core::time::Duration;
+
use crate::events::{Event, PathFailure, PaymentFailureReason};
use crate::ln::PaymentHash;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::ln::features::{ChannelFeatures, NodeFeatures};
use crate::ln::msgs::{ErrorAction, LightningError};
- use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure};
- #[cfg(invreqfailed)]
- use crate::ln::outbound_payment::INVOICE_REQUEST_TIMEOUT_TICKS;
+ use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration};
+ #[cfg(feature = "std")]
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
use crate::offers::offer::OfferBuilder;
use crate::offers::test_utils::*;
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);
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);
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);
router.expect_find_route(route_params.clone(), Ok(route.clone()));
let mut route_params_w_failed_scid = route_params.clone();
route_params_w_failed_scid.payment_params.previously_failed_channels.push(failed_scid);
- router.expect_find_route(route_params_w_failed_scid, Ok(route.clone()));
+ let mut route_w_failed_scid = route.clone();
+ route_w_failed_scid.route_params = Some(route_params_w_failed_scid.clone());
+ router.expect_find_route(route_params_w_failed_scid, Ok(route_w_failed_scid));
router.expect_find_route(route_params.clone(), Ok(route.clone()));
router.expect_find_route(route_params.clone(), Ok(route.clone()));
}
#[test]
- #[cfg(invreqfailed)]
- fn removes_stale_awaiting_invoice() {
+ fn removes_stale_awaiting_invoice_using_absolute_timeout() {
+ let pending_events = Mutex::new(VecDeque::new());
+ let outbound_payments = OutboundPayments::new();
+ let payment_id = PaymentId([0; 32]);
+ let absolute_expiry = 100;
+ let tick_interval = 10;
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(absolute_expiry));
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ for seconds_since_epoch in (0..absolute_expiry).step_by(tick_interval) {
+ let duration_since_epoch = Duration::from_secs(seconds_since_epoch);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
+ assert!(outbound_payments.has_pending_payments());
+ assert!(pending_events.lock().unwrap().is_empty());
+ }
+
+ let duration_since_epoch = Duration::from_secs(absolute_expiry);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
+ assert!(!outbound_payments.has_pending_payments());
+ assert!(!pending_events.lock().unwrap().is_empty());
+ assert_eq!(
+ pending_events.lock().unwrap().pop_front(),
+ Some((Event::InvoiceRequestFailed { payment_id }, None)),
+ );
+ assert!(pending_events.lock().unwrap().is_empty());
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
+ );
+ assert!(outbound_payments.has_pending_payments());
+
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_err()
+ );
+ }
+
+ #[test]
+ fn removes_stale_awaiting_invoice_using_timer_ticks() {
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let timer_ticks = 3;
+ let expiration = StaleExpiration::TimerTicks(timer_ticks);
assert!(!outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
- for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS {
- outbound_payments.remove_stale_payments(&pending_events);
+ for i in 0..timer_ticks {
+ let duration_since_epoch = Duration::from_secs(i * 60);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
assert!(outbound_payments.has_pending_payments());
assert!(pending_events.lock().unwrap().is_empty());
}
- outbound_payments.remove_stale_payments(&pending_events);
+ let duration_since_epoch = Duration::from_secs(timer_ticks * 60);
+ outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
+
assert!(!outbound_payments.has_pending_payments());
assert!(!pending_events.lock().unwrap().is_empty());
assert_eq!(
assert!(pending_events.lock().unwrap().is_empty());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None)
- .is_err()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_err()
);
}
#[test]
- #[cfg(invreqfailed)]
fn removes_abandoned_awaiting_invoice() {
let pending_events = Mutex::new(VecDeque::new());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
assert!(!outbound_payments.has_pending_payments());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
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());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), None
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
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());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
.build().unwrap()
.sign(recipient_sign).unwrap();
- assert!(outbound_payments.add_new_awaiting_invoice(
- payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
- .is_ok()
+ assert!(
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0),
+ Some(invoice.amount_msats() / 100 + 50_000)
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());
assert!(pending_events.lock().unwrap().is_empty());
}
- #[test]
- fn fails_paying_for_bolt12_invoice() {
- 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 keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet);
-
- let pending_events = Mutex::new(VecDeque::new());
- let outbound_payments = OutboundPayments::new();
- let payment_id = PaymentId([0; 32]);
-
- let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
- .amount_msats(1000)
- .build().unwrap()
- .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
- .build().unwrap()
- .sign(payer_sign).unwrap()
- .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
- .build().unwrap()
- .sign(recipient_sign).unwrap();
-
- assert!(outbound_payments.add_new_awaiting_invoice(
- payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
- .is_ok()
- );
- assert!(outbound_payments.has_pending_payments());
-
- let route_params = RouteParameters::from_payment_params_and_value(
- PaymentParameters::from_bolt12_invoice(&invoice),
- invoice.amount_msats(),
- );
- router.expect_find_route(
- route_params.clone(), Ok(Route { paths: vec![], route_params: Some(route_params) })
- );
-
- assert_eq!(
- outbound_payments.send_payment_for_bolt12_invoice(
- &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
- &&keys_manager, 0, &&logger, &pending_events, |_| panic!()
- ),
- Ok(()),
- );
- assert!(!outbound_payments.has_pending_payments());
-
- let payment_hash = invoice.payment_hash();
- let reason = Some(PaymentFailureReason::UnexpectedError);
-
- assert!(!pending_events.lock().unwrap().is_empty());
- assert_eq!(
- pending_events.lock().unwrap().pop_front(),
- Some((Event::PaymentFailed { payment_id, payment_hash, reason }, None)),
- );
- assert!(pending_events.lock().unwrap().is_empty());
- }
-
#[test]
fn sends_payment_for_bolt12_invoice() {
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());
let outbound_payments = OutboundPayments::new();
let payment_id = PaymentId([0; 32]);
+ let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
.amount_msats(1000)
assert!(pending_events.lock().unwrap().is_empty());
assert!(
- outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
+ outbound_payments.add_new_awaiting_invoice(
+ payment_id, expiration, Retry::Attempts(0), Some(1234)
+ ).is_ok()
);
assert!(outbound_payments.has_pending_payments());