Skip to content

Commit 09c0a1f

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 09c0a1f

3 files changed

Lines changed: 34 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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,3 +2957,34 @@ 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 = node_b
2968+
.bolt11_payment()
2969+
.receive(
2970+
100_000,
2971+
&Bolt11InvoiceDescription::Direct(Description::new("test-retry".to_string()).unwrap()),
2972+
3600,
2973+
)
2974+
.unwrap();
2975+
2976+
let first_attempt = node_a.bolt11_payment().send(&invoice, None);
2977+
assert!(first_attempt.is_err(), "expecting error at first attempt");
2978+
assert_eq!(first_attempt.unwrap_err(), NodeError::PaymentSendingFailed);
2979+
2980+
let second_attempt = node_a.bolt11_payment().send(&invoice, None);
2981+
2982+
assert_ne!(
2983+
second_attempt.unwrap_err(),
2984+
NodeError::DuplicatePayment,
2985+
"The invoice hash was incorrectly locked in the payment store after a routing failure!"
2986+
);
2987+
2988+
node_a.stop().unwrap();
2989+
node_b.stop().unwrap();
2990+
}

0 commit comments

Comments
 (0)