+ #[test]
+ fn pays_zero_value_invoice_using_amount() {
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let invoice = zero_value_invoice(payment_preimage);
+ let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner());
+ let final_value_msat = 100;
+
+ let payer = TestPayer::new().expect_send(Amount::ForInvoice(final_value_msat));
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ let payment_id =
+ Some(invoice_payer.pay_zero_value_invoice(&invoice, final_value_msat).unwrap());
+ assert_eq!(*payer.attempts.borrow(), 1);
+
+ invoice_payer.handle_event(&Event::PaymentSent {
+ payment_id, payment_preimage, payment_hash, fee_paid_msat: None
+ });
+ assert_eq!(*event_handled.borrow(), true);
+ assert_eq!(*payer.attempts.borrow(), 1);
+ }
+
+ #[test]
+ fn fails_paying_zero_value_invoice_with_amount() {
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payer = TestPayer::new();
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let invoice = invoice(payment_preimage);
+
+ // Cannot repay an invoice pending payment.
+ match invoice_payer.pay_zero_value_invoice(&invoice, 100) {
+ Err(PaymentError::Invoice("amount unexpected")) => {},
+ Err(_) => panic!("unexpected error"),
+ Ok(_) => panic!("expected invoice error"),
+ }
+ }
+
+ #[test]
+ fn pays_pubkey_with_amount() {
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let pubkey = pubkey();
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ let final_value_msat = 100;
+ let final_cltv_expiry_delta = 42;
+
+ let payer = TestPayer::new()
+ .expect_send(Amount::Spontaneous(final_value_msat))
+ .expect_send(Amount::OnRetry(final_value_msat));
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+
+ let payment_id = Some(invoice_payer.pay_pubkey(
+ pubkey, payment_preimage, final_value_msat, final_cltv_expiry_delta
+ ).unwrap());
+ assert_eq!(*payer.attempts.borrow(), 1);
+
+ let retry = RouteParameters {
+ payment_params: PaymentParameters::for_keysend(pubkey),
+ final_value_msat,
+ final_cltv_expiry_delta,
+ };
+ let event = Event::PaymentPathFailed {
+ payment_id,
+ payment_hash,
+ network_update: None,
+ payment_failed_permanently: false,
+ all_paths_failed: false,
+ path: vec![],
+ short_channel_id: None,
+ retry: Some(retry),
+ };
+ invoice_payer.handle_event(&event);
+ assert_eq!(*event_handled.borrow(), false);
+ assert_eq!(*payer.attempts.borrow(), 2);
+
+ invoice_payer.handle_event(&Event::PaymentSent {
+ payment_id, payment_preimage, payment_hash, fee_paid_msat: None
+ });
+ assert_eq!(*event_handled.borrow(), true);
+ assert_eq!(*payer.attempts.borrow(), 2);
+ }
+
+ #[test]
+ fn scores_failed_channel() {
+ 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 = PaymentHash(invoice.payment_hash().clone().into_inner());
+ let final_value_msat = invoice.amount_milli_satoshis().unwrap();
+ let path = TestRouter::path_for_value(final_value_msat);
+ let short_channel_id = Some(path[0].short_channel_id);
+
+ // Expect that scorer is given short_channel_id upon handling the event.
+ let payer = TestPayer::new()
+ .expect_send(Amount::ForInvoice(final_value_msat))
+ .expect_send(Amount::OnRetry(final_value_msat / 2));
+ let scorer = TestScorer::new().expect(TestResult::PaymentFailure {
+ path: path.clone(), short_channel_id: path[0].short_channel_id,
+ });
+ let router = TestRouter::new(scorer);
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+
+ let payment_id = Some(invoice_payer.pay_invoice(&invoice).unwrap());
+ let event = Event::PaymentPathFailed {
+ payment_id,
+ payment_hash,
+ network_update: None,
+ payment_failed_permanently: false,
+ all_paths_failed: false,
+ path,
+ short_channel_id,
+ retry: Some(TestRouter::retry_for_invoice(&invoice)),
+ };
+ 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 scorer = TestScorer::new()
+ .expect(TestResult::PaymentSuccess { path: route.paths[0].clone() })
+ .expect(TestResult::PaymentSuccess { path: route.paths[1].clone() });
+ let router = TestRouter::new(scorer);
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(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);
+ }
+
+ #[test]
+ fn generates_correct_inflight_map_data() {
+ 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 payer = TestPayer::new().expect_send(Amount::ForInvoice(final_value_msat));
+ let final_value_msat = invoice.amount_milli_satoshis().unwrap();
+ let route = TestRouter::route_for_value(final_value_msat);
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ let payment_id = invoice_payer.pay_invoice(&invoice).unwrap();
+
+ let inflight_map = invoice_payer.create_inflight_map();
+ // First path check
+ assert_eq!(inflight_map.0.get(&(0, false)).unwrap().clone(), 94);
+ assert_eq!(inflight_map.0.get(&(1, true)).unwrap().clone(), 84);
+ assert_eq!(inflight_map.0.get(&(2, false)).unwrap().clone(), 64);
+
+ // Second path check
+ assert_eq!(inflight_map.0.get(&(3, false)).unwrap().clone(), 74);
+ assert_eq!(inflight_map.0.get(&(4, false)).unwrap().clone(), 64);
+
+ invoice_payer.handle_event(&Event::PaymentPathSuccessful {
+ payment_id, payment_hash, path: route.paths[0].clone()
+ });
+
+ let inflight_map = invoice_payer.create_inflight_map();
+
+ assert_eq!(inflight_map.0.get(&(0, false)), None);
+ assert_eq!(inflight_map.0.get(&(1, true)), None);
+ assert_eq!(inflight_map.0.get(&(2, false)), None);
+
+ // Second path should still be inflight
+ assert_eq!(inflight_map.0.get(&(3, false)).unwrap().clone(), 74);
+ assert_eq!(inflight_map.0.get(&(4, false)).unwrap().clone(), 64)
+ }
+
+ #[test]
+ fn considers_inflight_htlcs_between_invoice_payments_when_path_succeeds() {
+ // First, let's just send a payment through, but only make sure one of the path completes
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let payment_invoice = invoice(payment_preimage);
+ let payment_hash = Some(PaymentHash(payment_invoice.payment_hash().clone().into_inner()));
+ let final_value_msat = payment_invoice.amount_milli_satoshis().unwrap();
+
+ let payer = TestPayer::new()
+ .expect_send(Amount::ForInvoice(final_value_msat))
+ .expect_send(Amount::ForInvoice(final_value_msat));
+ let final_value_msat = payment_invoice.amount_milli_satoshis().unwrap();
+ let route = TestRouter::route_for_value(final_value_msat);
+ let scorer = TestScorer::new()
+ // 1st invoice, 1st path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 84, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 94, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // 1st invoice, 2nd path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 74, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // 2nd invoice, 1st path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 84, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 94, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // 2nd invoice, 2nd path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 64, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 74, inflight_htlc_msat: 74, effective_capacity: EffectiveCapacity::Unknown } );
+ let router = TestRouter::new(scorer);
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ // Succeed 1st path, leave 2nd path inflight
+ let payment_id = invoice_payer.pay_invoice(&payment_invoice).unwrap();
+ invoice_payer.handle_event(&Event::PaymentPathSuccessful {
+ payment_id, payment_hash, path: route.paths[0].clone()
+ });
+
+ // Let's pay a second invoice that will be using the same path. This should trigger the
+ // assertions that expect the last 4 ChannelUsage values above where TestScorer is initialized.
+ // Particularly, the 2nd path of the 1st payment, since it is not yet complete, should still
+ // have 64 msats inflight for paths considering the channel with scid of 1.
+ let payment_preimage_2 = PaymentPreimage([2; 32]);
+ let payment_invoice_2 = invoice(payment_preimage_2);
+ invoice_payer.pay_invoice(&payment_invoice_2).unwrap();
+ }
+
+ #[test]
+ fn considers_inflight_htlcs_between_retries() {
+ // First, let's just send a payment through, but only make sure one of the path completes
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let payment_invoice = invoice(payment_preimage);
+ let payment_hash = PaymentHash(payment_invoice.payment_hash().clone().into_inner());
+ let final_value_msat = payment_invoice.amount_milli_satoshis().unwrap();
+
+ let payer = TestPayer::new()
+ .expect_send(Amount::ForInvoice(final_value_msat))
+ .expect_send(Amount::OnRetry(final_value_msat / 2))
+ .expect_send(Amount::OnRetry(final_value_msat / 4));
+ let final_value_msat = payment_invoice.amount_milli_satoshis().unwrap();
+ let scorer = TestScorer::new()
+ // 1st invoice, 1st path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 84, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 94, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // 1st invoice, 2nd path
+ .expect_usage(ChannelUsage { amount_msat: 64, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 74, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // Retry 1, 1st path
+ .expect_usage(ChannelUsage { amount_msat: 32, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 52, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 62, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // Retry 1, 2nd path
+ .expect_usage(ChannelUsage { amount_msat: 32, inflight_htlc_msat: 64, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 42, inflight_htlc_msat: 64 + 10, effective_capacity: EffectiveCapacity::Unknown } )
+ // Retry 2, 1st path
+ .expect_usage(ChannelUsage { amount_msat: 16, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 36, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 46, inflight_htlc_msat: 0, effective_capacity: EffectiveCapacity::Unknown } )
+ // Retry 2, 2nd path
+ .expect_usage(ChannelUsage { amount_msat: 16, inflight_htlc_msat: 64 + 32, effective_capacity: EffectiveCapacity::Unknown } )
+ .expect_usage(ChannelUsage { amount_msat: 26, inflight_htlc_msat: 74 + 32 + 10, effective_capacity: EffectiveCapacity::Unknown } );
+ let router = TestRouter::new(scorer);
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(2));
+
+ // Fail 1st path, leave 2nd path inflight
+ let payment_id = Some(invoice_payer.pay_invoice(&payment_invoice).unwrap());
+ invoice_payer.handle_event(&Event::PaymentPathFailed {
+ payment_id,
+ payment_hash,
+ network_update: None,
+ payment_failed_permanently: false,
+ all_paths_failed: false,
+ path: TestRouter::path_for_value(final_value_msat),
+ short_channel_id: None,
+ retry: Some(TestRouter::retry_for_invoice(&payment_invoice)),
+ });
+
+ // Fails again the 1st path of our retry
+ invoice_payer.handle_event(&Event::PaymentPathFailed {
+ payment_id,
+ payment_hash,
+ network_update: None,
+ payment_failed_permanently: false,
+ all_paths_failed: false,
+ path: TestRouter::path_for_value(final_value_msat / 2),
+ short_channel_id: None,
+ retry: Some(RouteParameters {
+ final_value_msat: final_value_msat / 4,
+ ..TestRouter::retry_for_invoice(&payment_invoice)
+ }),
+ });
+ }
+
+ #[test]
+ fn accounts_for_some_inflight_htlcs_sent_during_partial_failure() {
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let invoice_to_pay = invoice(payment_preimage);
+ let final_value_msat = invoice_to_pay.amount_milli_satoshis().unwrap();
+
+ let retry = TestRouter::retry_for_invoice(&invoice_to_pay);
+ let payer = TestPayer::new()
+ .fails_with_partial_failure(
+ retry.clone(), OnAttempt(1),
+ Some(vec![
+ Err(ChannelUnavailable { err: "abc".to_string() }), Err(MonitorUpdateInProgress)
+ ]))
+ .expect_send(Amount::ForInvoice(final_value_msat));
+
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ invoice_payer.pay_invoice(&invoice_to_pay).unwrap();
+ let inflight_map = invoice_payer.create_inflight_map();
+
+ // Only the second path, which failed with `MonitorUpdateInProgress` should be added to our
+ // inflight map because retries are disabled.
+ assert_eq!(inflight_map.0.len(), 2);
+ }
+
+ #[test]
+ fn accounts_for_all_inflight_htlcs_sent_during_partial_failure() {
+ let event_handled = core::cell::RefCell::new(false);
+ let event_handler = |_: &_| { *event_handled.borrow_mut() = true; };
+
+ let payment_preimage = PaymentPreimage([1; 32]);
+ let invoice_to_pay = invoice(payment_preimage);
+ let final_value_msat = invoice_to_pay.amount_milli_satoshis().unwrap();
+
+ let retry = TestRouter::retry_for_invoice(&invoice_to_pay);
+ let payer = TestPayer::new()
+ .fails_with_partial_failure(
+ retry.clone(), OnAttempt(1),
+ Some(vec![
+ Ok(()), Err(MonitorUpdateInProgress)
+ ]))
+ .expect_send(Amount::ForInvoice(final_value_msat));
+
+ let router = TestRouter::new(TestScorer::new());
+ let logger = TestLogger::new();
+ let invoice_payer =
+ InvoicePayer::new(&payer, router, &logger, event_handler, Retry::Attempts(0));
+
+ invoice_payer.pay_invoice(&invoice_to_pay).unwrap();
+ let inflight_map = invoice_payer.create_inflight_map();
+
+ // All paths successful, hence we check of the existence of all 5 hops.
+ assert_eq!(inflight_map.0.len(), 5);
+ }
+
+ struct TestRouter {
+ scorer: RefCell<TestScorer>,
+ }