Skip to content

Commit d0f7f4e

Browse files
jkczyzclaude
andcommitted
f - Preserve funding details when a splice candidate is replaced
The TxReplaced wallet event rebuilt the payment record from scratch, dropping its funding details. When a wallet sync fell between a splice broadcast and its RBF, the replacement of the original candidate cleared those details, so the payment no longer graduated to Succeeded on ChannelReady. Funding records are managed by the classify path and the Lightning lifecycle handlers, so leave them untouched on replacement. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cec878e commit d0f7f4e

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

src/wallet/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,18 @@ impl Wallet {
410410
continue;
411411
};
412412

413+
// Funding records (channel opens and splices) track their active candidate and
414+
// status through `classify_*` and the Lightning lifecycle handlers. A replaced
415+
// candidate is expected during splice RBF and must not reset the record or drop
416+
// its funding details, so leave such records untouched here.
417+
if self
418+
.pending_payment_store
419+
.get(&payment_id)
420+
.map_or(false, |p| p.funding_details.is_some())
421+
{
422+
continue;
423+
}
424+
413425
// Collect all conflict txids
414426
let mut conflict_txids: Vec<Txid> =
415427
conflicts.iter().map(|(_, conflict_txid)| *conflict_txid).collect();

tests/integration_tests_rust.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,24 @@ async fn rbf_splice_channel() {
12181218
let original_txo = expect_splice_negotiated_event!(node_a, node_b.node_id());
12191219
expect_splice_negotiated_event!(node_b, node_a.node_id());
12201220

1221+
// Sync so the original splice candidate is recorded as a canonical wallet transaction before
1222+
// the RBF below replaces it. This makes the post-RBF sync observe the original candidate being
1223+
// replaced (a `WalletEvent::TxReplaced`), which must not drop the payment's funding details.
1224+
//
1225+
// This is a best-effort regression guard rather than a deterministic one: with the
1226+
// funding-details-preservation fix in place the splice still graduates correctly, but without
1227+
// it the resulting inconsistency only surfaces intermittently (via a timing-dependent
1228+
// `debug_assert` in the chain-tip handler), so a reverted fix is caught probabilistically.
1229+
//
1230+
// TODO: Make this deterministic. If funding payments carried a durable classification in the
1231+
// main payment store (e.g. a `tx_type` on `PaymentKind::Onchain`, as in
1232+
// lightningdevkit/ldk-node#791), a dropped funding-details record would be a detectable
1233+
// contradiction on `ChannelReady` rather than a timing-dependent assert, letting this test
1234+
// fail reliably whenever the fix is reverted.
1235+
wait_for_tx(&electrsd.client, original_txo.txid).await;
1236+
node_a.sync_wallets().unwrap();
1237+
node_b.sync_wallets().unwrap();
1238+
12211239
// splice_in should fail when there's a pending splice (RBF guard)
12221240
assert_eq!(
12231241
node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000),

0 commit comments

Comments
 (0)