X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-invoice%2Fsrc%2Fpayment.rs;h=e785bb39264130a4895a25fd663e1b42560fd665;hb=cd4dc39a8c4732bea1a3221617f86e34dfb7efb8;hp=1e450296b583fe5cb73eb6bf96bf4d4f36b40bec;hpb=756b3051573ed9b4e04cc2227af93c45fe0d8104;p=rust-lightning diff --git a/lightning-invoice/src/payment.rs b/lightning-invoice/src/payment.rs index 1e450296..e785bb39 100644 --- a/lightning-invoice/src/payment.rs +++ b/lightning-invoice/src/payment.rs @@ -15,6 +15,10 @@ //! and payee using information provided by the payer and from the payee's [`Invoice`], when //! applicable. //! +//! [`InvoicePayer`] is parameterized by a [`LockableScore`], which it uses for scoring failed and +//! successful payment paths upon receiving [`Event::PaymentPathFailed`] and +//! [`Event::PaymentPathSuccessful`] events, respectively. +//! //! [`InvoicePayer`] is capable of retrying failed payments. It accomplishes this by implementing //! [`EventHandler`] which decorates a user-provided handler. It will intercept any //! [`Event::PaymentPathFailed`] events and retry the failed paths for a fixed number of total @@ -31,11 +35,12 @@ //! # use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; //! # use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; //! # use lightning::ln::msgs::LightningError; -//! # use lightning::routing; +//! # use lightning::routing::scoring::Score; //! # use lightning::routing::network_graph::NodeId; //! # use lightning::routing::router::{Route, RouteHop, RouteParameters}; //! # use lightning::util::events::{Event, EventHandler, EventsProvider}; //! # use lightning::util::logger::{Logger, Record}; +//! # use lightning::util::ser::{Writeable, Writer}; //! # use lightning_invoice::Invoice; //! # use lightning_invoice::payment::{InvoicePayer, Payer, RetryAttempts, Router}; //! # use secp256k1::key::PublicKey; @@ -62,23 +67,27 @@ //! # ) -> Result<(), PaymentSendFailure> { unimplemented!() } //! # } //! # -//! # struct FakeRouter {}; -//! # impl Router for FakeRouter { +//! # struct FakeRouter {} +//! # impl Router for FakeRouter { //! # fn find_route( -//! # &self, payer: &PublicKey, params: &RouteParameters, +//! # &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash, //! # first_hops: Option<&[&ChannelDetails]>, scorer: &S //! # ) -> Result { unimplemented!() } //! # } //! # -//! # struct FakeScorer {}; -//! # impl routing::Score for FakeScorer { +//! # struct FakeScorer {} +//! # impl Writeable for FakeScorer { +//! # fn write(&self, w: &mut W) -> Result<(), std::io::Error> { unimplemented!(); } +//! # } +//! # impl Score for FakeScorer { //! # fn channel_penalty_msat( -//! # &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId +//! # &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: Option, _source: &NodeId, _target: &NodeId //! # ) -> u64 { 0 } //! # fn payment_path_failed(&mut self, _path: &[&RouteHop], _short_channel_id: u64) {} +//! # fn payment_path_successful(&mut self, _path: &[&RouteHop]) {} //! # } //! # -//! # struct FakeLogger {}; +//! # struct FakeLogger {} //! # impl Logger for FakeLogger { //! # fn log(&self, record: &Record) { unimplemented!() } //! # } @@ -98,12 +107,13 @@ //! let invoice_payer = InvoicePayer::new(&payer, router, &scorer, &logger, event_handler, RetryAttempts(2)); //! //! let invoice = "..."; -//! let invoice = invoice.parse::().unwrap(); -//! invoice_payer.pay_invoice(&invoice).unwrap(); +//! if let Ok(invoice) = invoice.parse::() { +//! invoice_payer.pay_invoice(&invoice).unwrap(); //! //! # let event_provider = FakeEventProvider {}; -//! loop { -//! event_provider.process_pending_events(&invoice_payer); +//! loop { +//! event_provider.process_pending_events(&invoice_payer); +//! } //! } //! # } //! ``` @@ -122,8 +132,7 @@ use bitcoin_hashes::sha256::Hash as Sha256; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::channelmanager::{ChannelDetails, PaymentId, PaymentSendFailure}; use lightning::ln::msgs::LightningError; -use lightning::routing; -use lightning::routing::{LockableScore, Score}; +use lightning::routing::scoring::{LockableScore, Score}; use lightning::routing::router::{Payee, Route, RouteParameters}; use lightning::util::events::{Event, EventHandler}; use lightning::util::logger::Logger; @@ -136,11 +145,15 @@ use std::sync::Mutex; use std::time::{Duration, SystemTime}; /// A utility for paying [`Invoice`]s and sending spontaneous payments. +/// +/// See [module-level documentation] for details. +/// +/// [module-level documentation]: crate::payment pub struct InvoicePayer where P::Target: Payer, - R: for <'a> Router<<::Target as routing::LockableScore<'a>>::Locked>, - S::Target: for <'a> routing::LockableScore<'a>, + R: for <'a> Router<<::Target as LockableScore<'a>>::Locked>, + S::Target: for <'a> LockableScore<'a>, L::Target: Logger, E: EventHandler, { @@ -177,11 +190,11 @@ pub trait Payer { } /// A trait defining behavior for routing an [`Invoice`] payment. -pub trait Router { +pub trait Router { /// Finds a [`Route`] between `payer` and `payee` for a payment with the given values. fn find_route( - &self, payer: &PublicKey, params: &RouteParameters, first_hops: Option<&[&ChannelDetails]>, - scorer: &S + &self, payer: &PublicKey, params: &RouteParameters, payment_hash: &PaymentHash, + first_hops: Option<&[&ChannelDetails]>, scorer: &S ) -> Result; } @@ -207,8 +220,8 @@ pub enum PaymentError { impl InvoicePayer where P::Target: Payer, - R: for <'a> Router<<::Target as routing::LockableScore<'a>>::Locked>, - S::Target: for <'a> routing::LockableScore<'a>, + R: for <'a> Router<<::Target as LockableScore<'a>>::Locked>, + S::Target: for <'a> LockableScore<'a>, L::Target: Logger, E: EventHandler, { @@ -329,10 +342,8 @@ where let payer = self.payer.node_id(); let first_hops = self.payer.first_hops(); let route = self.router.find_route( - &payer, - params, - Some(&first_hops.iter().collect::>()), - &self.scorer.lock(), + &payer, params, &payment_hash, Some(&first_hops.iter().collect::>()), + &self.scorer.lock() ).map_err(|e| PaymentError::Routing(e))?; match send_payment(&route) { @@ -392,7 +403,10 @@ where let payer = self.payer.node_id(); let first_hops = self.payer.first_hops(); - let route = self.router.find_route(&payer, ¶ms, Some(&first_hops.iter().collect::>()), &self.scorer.lock()); + let route = self.router.find_route( + &payer, ¶ms, &payment_hash, Some(&first_hops.iter().collect::>()), + &self.scorer.lock() + ); if route.is_err() { log_trace!(self.logger, "Failed to find a route for payment {}; not retrying (attempts: {})", log_bytes!(payment_hash.0), attempts); return Err(()); @@ -440,8 +454,8 @@ fn has_expired(params: &RouteParameters) -> bool { impl EventHandler for InvoicePayer where P::Target: Payer, - R: for <'a> Router<<::Target as routing::LockableScore<'a>>::Locked>, - S::Target: for <'a> routing::LockableScore<'a>, + R: for <'a> Router<<::Target as LockableScore<'a>>::Locked>, + S::Target: for <'a> LockableScore<'a>, L::Target: Logger, E: EventHandler, { @@ -469,6 +483,10 @@ where if *all_paths_failed { self.payment_cache.lock().unwrap().remove(payment_hash); } }, + Event::PaymentPathSuccessful { path, .. } => { + let path = path.iter().collect::>(); + self.scorer.lock().payment_path_successful(&path); + }, Event::PaymentSent { payment_hash, .. } => { let mut payment_cache = self.payment_cache.lock().unwrap(); let attempts = payment_cache @@ -1123,7 +1141,9 @@ mod tests { .expect_send(Amount::ForInvoice(final_value_msat)) .expect_send(Amount::OnRetry(final_value_msat / 2)); let router = TestRouter {}; - let scorer = RefCell::new(TestScorer::new().expect_channel_failure(short_channel_id.unwrap())); + let scorer = RefCell::new(TestScorer::new().expect(PaymentPath::Failure { + path: path.clone(), short_channel_id: path[0].short_channel_id, + })); let logger = TestLogger::new(); let invoice_payer = InvoicePayer::new(&payer, router, &scorer, &logger, event_handler, RetryAttempts(2)); @@ -1142,6 +1162,39 @@ mod tests { invoice_payer.handle_event(&event); } + #[test] + fn scores_successful_channels() { + let event_handled = core::cell::RefCell::new(false); + let event_handler = |_: &_| { *event_handled.borrow_mut() = true; }; + + let payment_preimage = PaymentPreimage([1; 32]); + let invoice = invoice(payment_preimage); + let payment_hash = Some(PaymentHash(invoice.payment_hash().clone().into_inner())); + let final_value_msat = invoice.amount_milli_satoshis().unwrap(); + let route = TestRouter::route_for_value(final_value_msat); + + // Expect that scorer is given short_channel_id upon handling the event. + let payer = TestPayer::new().expect_send(Amount::ForInvoice(final_value_msat)); + let router = TestRouter {}; + let scorer = RefCell::new(TestScorer::new() + .expect(PaymentPath::Success { path: route.paths[0].clone() }) + .expect(PaymentPath::Success { path: route.paths[1].clone() }) + ); + let logger = TestLogger::new(); + let invoice_payer = + InvoicePayer::new(&payer, router, &scorer, &logger, event_handler, RetryAttempts(2)); + + let payment_id = invoice_payer.pay_invoice(&invoice).unwrap(); + let event = Event::PaymentPathSuccessful { + payment_id, payment_hash, path: route.paths[0].clone() + }; + invoice_payer.handle_event(&event); + let event = Event::PaymentPathSuccessful { + payment_id, payment_hash, path: route.paths[1].clone() + }; + invoice_payer.handle_event(&event); + } + struct TestRouter; impl TestRouter { @@ -1185,13 +1238,10 @@ mod tests { } } - impl Router for TestRouter { + impl Router for TestRouter { fn find_route( - &self, - _payer: &PublicKey, - params: &RouteParameters, - _first_hops: Option<&[&ChannelDetails]>, - _scorer: &S, + &self, _payer: &PublicKey, params: &RouteParameters, _payment_hash: &PaymentHash, + _first_hops: Option<&[&ChannelDetails]>, _scorer: &S ) -> Result { Ok(Route { payee: Some(params.payee.clone()), ..Self::route_for_value(params.final_value_msat) @@ -1201,43 +1251,74 @@ mod tests { struct FailingRouter; - impl Router for FailingRouter { + impl Router for FailingRouter { fn find_route( - &self, - _payer: &PublicKey, - _params: &RouteParameters, - _first_hops: Option<&[&ChannelDetails]>, - _scorer: &S, + &self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash, + _first_hops: Option<&[&ChannelDetails]>, _scorer: &S ) -> Result { Err(LightningError { err: String::new(), action: ErrorAction::IgnoreError }) } } struct TestScorer { - expectations: VecDeque, + expectations: Option>, + } + + #[derive(Debug)] + enum PaymentPath { + Failure { path: Vec, short_channel_id: u64 }, + Success { path: Vec }, } impl TestScorer { fn new() -> Self { Self { - expectations: VecDeque::new(), + expectations: None, } } - fn expect_channel_failure(mut self, short_channel_id: u64) -> Self { - self.expectations.push_back(short_channel_id); + fn expect(mut self, expectation: PaymentPath) -> Self { + self.expectations.get_or_insert_with(|| VecDeque::new()).push_back(expectation); self } } - impl routing::Score for TestScorer { + #[cfg(c_bindings)] + impl lightning::util::ser::Writeable for TestScorer { + fn write(&self, _: &mut W) -> Result<(), std::io::Error> { unreachable!(); } + } + + impl Score for TestScorer { fn channel_penalty_msat( - &self, _short_channel_id: u64, _source: &NodeId, _target: &NodeId + &self, _short_channel_id: u64, _send_amt: u64, _chan_amt: Option, _source: &NodeId, _target: &NodeId ) -> u64 { 0 } - fn payment_path_failed(&mut self, _path: &[&RouteHop], short_channel_id: u64) { - if let Some(expected_short_channel_id) = self.expectations.pop_front() { - assert_eq!(short_channel_id, expected_short_channel_id); + fn payment_path_failed(&mut self, actual_path: &[&RouteHop], actual_short_channel_id: u64) { + if let Some(expectations) = &mut self.expectations { + match expectations.pop_front() { + Some(PaymentPath::Failure { path, short_channel_id }) => { + assert_eq!(actual_path, &path.iter().collect::>()[..]); + assert_eq!(actual_short_channel_id, short_channel_id); + }, + Some(PaymentPath::Success { path }) => { + panic!("Unexpected successful payment path: {:?}", path) + }, + None => panic!("Unexpected payment_path_failed call: {:?}", actual_path), + } + } + } + + fn payment_path_successful(&mut self, actual_path: &[&RouteHop]) { + if let Some(expectations) = &mut self.expectations { + match expectations.pop_front() { + Some(PaymentPath::Failure { path, .. }) => { + panic!("Unexpected payment path failure: {:?}", path) + }, + Some(PaymentPath::Success { path }) => { + assert_eq!(actual_path, &path.iter().collect::>()[..]); + }, + None => panic!("Unexpected payment_path_successful call: {:?}", actual_path), + } } } } @@ -1248,8 +1329,10 @@ mod tests { return; } - if !self.expectations.is_empty() { - panic!("Unsatisfied channel failure expectations: {:?}", self.expectations); + if let Some(expectations) = &self.expectations { + if !expectations.is_empty() { + panic!("Unsatisfied scorer expectations: {:?}", expectations); + } } } } @@ -1369,9 +1452,11 @@ mod tests { // *** Full Featured Functional Tests with a Real ChannelManager *** struct ManualRouter(RefCell>>); - impl Router for ManualRouter { - fn find_route(&self, _payer: &PublicKey, _params: &RouteParameters, _first_hops: Option<&[&ChannelDetails]>, _scorer: &S) - -> Result { + impl Router for ManualRouter { + fn find_route( + &self, _payer: &PublicKey, _params: &RouteParameters, _payment_hash: &PaymentHash, + _first_hops: Option<&[&ChannelDetails]>, _scorer: &S + ) -> Result { self.0.borrow_mut().pop_front().unwrap() } }