Skip to content

Commit 492bd32

Browse files
jkczyzclaude
andcommitted
Retry user-initiated splices across restarts and disconnects
LDK does not durably record a splice until its negotiation reaches the signature exchange, and it abandons an in-progress negotiation whenever the peer disconnects -- which includes stopping the node. A restart or an ill-timed disconnect after splice_in, splice_out, or bump_channel_funding_fee returned Ok would therefore silently drop the splice. Persist the splice intent in a new UserChannelId-keyed channel record store before handing the contribution to LDK, and resubmit it until the splice locks. A startup reconciler probes LDK's live splice state to detect dropped intents -- including those lost to a crash before LDK persisted anything -- and the SpliceNegotiationFailed handler retries recoverable failures, rebuilding the contribution with fresh parameters when the stored one has gone stale. Resubmission does not require the peer to be connected, as LDK holds the contribution and initiates quiescence once the peer reconnects. Event::SpliceNegotiationFailed is now emitted only once a splice is given up on (a non-transient failure or retries exhausted) rather than for every failed negotiation round. Generated with assistance from Claude Code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent bec7723 commit 492bd32

8 files changed

Lines changed: 884 additions & 15 deletions

File tree

src/builder.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ use crate::io::utils::{
6464
};
6565
use crate::io::vss_store::VssStoreBuilder;
6666
use crate::io::{
67-
self, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
67+
self, CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE,
68+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
69+
PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6870
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
6971
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
7072
};
@@ -79,9 +81,9 @@ use crate::peer_store::PeerStore;
7981
use crate::runtime::{Runtime, RuntimeSpawner};
8082
use crate::tx_broadcaster::TransactionBroadcaster;
8183
use crate::types::{
82-
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
83-
GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
84-
PeerManager, PendingPaymentStore, SyncAndAsyncKVStore,
84+
AsyncPersister, ChainMonitor, ChannelManager, ChannelRecordStore, DynStore, DynStoreRef,
85+
DynStoreWrapper, GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger,
86+
PaymentStore, PeerManager, PendingPaymentStore, SyncAndAsyncKVStore,
8587
};
8688
use crate::wallet::persist::KVStoreWalletPersister;
8789
use crate::wallet::Wallet;
@@ -1394,7 +1396,7 @@ fn build_with_store_internal(
13941396

13951397
let kv_store_ref = Arc::clone(&kv_store);
13961398
let logger_ref = Arc::clone(&logger);
1397-
let (payment_store_res, node_metris_res, pending_payment_store_res) =
1399+
let (payment_store_res, node_metris_res, pending_payment_store_res, channel_record_store_res) =
13981400
runtime.block_on(async move {
13991401
tokio::join!(
14001402
read_all_objects(
@@ -1409,6 +1411,12 @@ fn build_with_store_internal(
14091411
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
14101412
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
14111413
Arc::clone(&logger_ref),
1414+
),
1415+
read_all_objects(
1416+
&*kv_store_ref,
1417+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE,
1418+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE,
1419+
Arc::clone(&logger_ref),
14121420
)
14131421
)
14141422
});
@@ -1612,6 +1620,20 @@ fn build_with_store_internal(
16121620
},
16131621
};
16141622

1623+
let channel_record_store = match channel_record_store_res {
1624+
Ok(channel_records) => Arc::new(ChannelRecordStore::new(
1625+
channel_records,
1626+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
1627+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
1628+
Arc::clone(&kv_store),
1629+
Arc::clone(&logger),
1630+
)),
1631+
Err(e) => {
1632+
log_error!(logger, "Failed to read channel record data from store: {}", e);
1633+
return Err(BuildError::ReadFailed);
1634+
},
1635+
};
1636+
16151637
let wallet = Arc::new(Wallet::new(
16161638
bdk_wallet,
16171639
wallet_persister,
@@ -2171,6 +2193,7 @@ fn build_with_store_internal(
21712193
scorer,
21722194
peer_store,
21732195
payment_store,
2196+
channel_record_store,
21742197
lnurl_auth,
21752198
is_running,
21762199
node_metrics,

0 commit comments

Comments
 (0)