X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=a1c6596784e27c6d70d5811589ee440d5fb39704;hb=2f6b5d157a00181e8554383653aac48552dec524;hp=83f83eb5d9679bdb50429561696362c395323aea;hpb=4e3c031d5647bac1bdd5cbd9a2b20f1f9dec981c;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 83f83eb5..a1c65967 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -263,7 +263,7 @@ pub enum Retry { /// Each attempt may be multiple HTLCs along multiple paths if the router decides to split up a /// 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(usize), + Attempts(u32), #[cfg(not(feature = "no-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. @@ -305,7 +305,7 @@ pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime; pub(crate) struct PaymentAttemptsUsingTime { /// This count will be incremented only after the result of the attempt is known. When it's 0, /// it means the result of the first attempt is not known yet. - pub(crate) count: usize, + 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"))] first_attempted_at: T, @@ -442,6 +442,7 @@ pub enum PaymentSendFailure { } /// An error when attempting to pay a BOLT 12 invoice. +#[derive(Clone, Debug, PartialEq, Eq)] pub(super) enum Bolt12PaymentError { /// The invoice was not requested. UnexpectedInvoice, @@ -1682,7 +1683,10 @@ mod tests { use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError}; - use crate::ln::outbound_payment::{INVOICE_REQUEST_TIMEOUT_TICKS, OutboundPayments, Retry, RetryableSendFailure}; + use crate::ln::outbound_payment::{Bolt12PaymentError, INVOICE_REQUEST_TIMEOUT_TICKS, OutboundPayments, Retry, RetryableSendFailure}; + use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY; + use crate::offers::offer::OfferBuilder; + use crate::offers::test_utils::*; use crate::routing::gossip::NetworkGraph; use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters}; use crate::sync::{Arc, Mutex, RwLock}; @@ -1932,4 +1936,240 @@ mod tests { ); assert!(pending_events.lock().unwrap().is_empty()); } + + #[cfg(feature = "std")] + #[test] + fn fails_sending_payment_for_expired_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]); + + assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.has_pending_payments()); + + let created_at = now() - DEFAULT_RELATIVE_EXPIRY; + 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(), created_at).unwrap() + .build().unwrap() + .sign(recipient_sign).unwrap(); + + 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::PaymentExpired); + + 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 fails_finding_route_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]); + + assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.has_pending_payments()); + + 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(); + + router.expect_find_route( + RouteParameters { + payment_params: PaymentParameters::from_bolt12_invoice(&invoice), + final_value_msat: invoice.amount_msats(), + }, + Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }), + ); + + 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::RouteNotFound); + + 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 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]); + + assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.has_pending_payments()); + + 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(); + + let route_params = RouteParameters { + payment_params: PaymentParameters::from_bolt12_invoice(&invoice), + final_value_msat: 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 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(); + + let route_params = RouteParameters { + payment_params: PaymentParameters::from_bolt12_invoice(&invoice), + final_value_msat: invoice.amount_msats(), + }; + router.expect_find_route( + route_params.clone(), + Ok(Route { + paths: vec![ + Path { + hops: vec![ + RouteHop { + pubkey: recipient_pubkey(), + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: invoice.amount_msats(), + cltv_expiry_delta: 0, + } + ], + blinded_tail: None, + } + ], + route_params: Some(route_params), + }) + ); + + assert!(!outbound_payments.has_pending_payments()); + 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!() + ), + Err(Bolt12PaymentError::UnexpectedInvoice), + ); + assert!(!outbound_payments.has_pending_payments()); + assert!(pending_events.lock().unwrap().is_empty()); + + assert!(outbound_payments.add_new_awaiting_invoice(payment_id).is_ok()); + assert!(outbound_payments.has_pending_payments()); + + assert_eq!( + outbound_payments.send_payment_for_bolt12_invoice( + &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, + &&keys_manager, 0, &&logger, &pending_events, |_| Ok(()) + ), + Ok(()), + ); + assert!(outbound_payments.has_pending_payments()); + assert!(pending_events.lock().unwrap().is_empty()); + + 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!() + ), + Err(Bolt12PaymentError::DuplicateInvoice), + ); + assert!(outbound_payments.has_pending_payments()); + assert!(pending_events.lock().unwrap().is_empty()); + } }