Skip to content

Commit c9d3974

Browse files
committed
fix(payment): skip store insertion on routing failures to allow retry
Adjust the error handling for outbound payments so that when a payment fails at the pathfinding stage (e.g., `RetryableSendFailure::RouteNotFound`), we bypass inserting a `PaymentStatus::Failed` entry into the database. Fix #903
1 parent 109978d commit c9d3974

3 files changed

Lines changed: 32 additions & 0 deletions

File tree

src/payment/bolt11.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ impl Bolt11Payment {
307307
log_error!(self.logger, "Failed to send payment: {:?}", e);
308308
match e {
309309
RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment),
310+
RetryableSendFailure::RouteNotFound => Err(Error::PaymentSendingFailed),
310311
_ => {
311312
let kind = PaymentKind::Bolt11 {
312313
hash: payment_hash,
@@ -422,6 +423,7 @@ impl Bolt11Payment {
422423
log_error!(self.logger, "Failed to send payment: {:?}", e);
423424
match e {
424425
RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment),
426+
RetryableSendFailure::RouteNotFound => Err(Error::PaymentSendingFailed),
425427
_ => {
426428
let kind = PaymentKind::Bolt11 {
427429
hash: payment_hash,

src/payment/spontaneous.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ impl SpontaneousPayment {
139139

140140
match e {
141141
RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment),
142+
RetryableSendFailure::RouteNotFound => Err(Error::PaymentSendingFailed),
142143
_ => {
143144
let kind = PaymentKind::Spontaneous {
144145
hash: payment_hash,

tests/integration_tests_rust.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,3 +2957,32 @@ async fn splice_in_with_all_balance() {
29572957
node_a.stop().unwrap();
29582958
node_b.stop().unwrap();
29592959
}
2960+
2961+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2962+
async fn test_retry_on_routing_failure() {
2963+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
2964+
let chain_source = random_chain_source(&bitcoind, &electrsd);
2965+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, true);
2966+
2967+
let invoice_description =
2968+
Bolt11InvoiceDescription::Direct(Description::new(String::from("test-retry")).unwrap());
2969+
let invoice = node_b
2970+
.bolt11_payment()
2971+
.receive(100_000, &invoice_description.clone().into(), 3600)
2972+
.unwrap();
2973+
2974+
let first_attempt = node_a.bolt11_payment().send(&invoice, None);
2975+
assert!(first_attempt.is_err(), "expecting error at first attempt");
2976+
assert_eq!(first_attempt.unwrap_err(), NodeError::PaymentSendingFailed);
2977+
2978+
let second_attempt = node_a.bolt11_payment().send(&invoice, None);
2979+
2980+
assert_ne!(
2981+
second_attempt.unwrap_err(),
2982+
NodeError::DuplicatePayment,
2983+
"The invoice hash was incorrectly locked in the payment store after a routing failure!"
2984+
);
2985+
2986+
node_a.stop().unwrap();
2987+
node_b.stop().unwrap();
2988+
}

0 commit comments

Comments
 (0)