Skip to content

Commit 6098d0e

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 328bdc3 commit 6098d0e

8 files changed

Lines changed: 885 additions & 16 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
};
@@ -77,9 +79,9 @@ use crate::peer_store::PeerStore;
7779
use crate::runtime::{Runtime, RuntimeSpawner};
7880
use crate::tx_broadcaster::TransactionBroadcaster;
7981
use crate::types::{
80-
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
81-
GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
82-
PeerManager, PendingPaymentStore,
82+
AsyncPersister, ChainMonitor, ChannelManager, ChannelRecordStore, DynStore, DynStoreRef,
83+
DynStoreWrapper, GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger,
84+
PaymentStore, PeerManager, PendingPaymentStore,
8385
};
8486
use crate::wallet::persist::KVStoreWalletPersister;
8587
use crate::wallet::Wallet;
@@ -1379,7 +1381,7 @@ fn build_with_store_internal(
13791381

13801382
let kv_store_ref = Arc::clone(&kv_store);
13811383
let logger_ref = Arc::clone(&logger);
1382-
let (payment_store_res, node_metris_res, pending_payment_store_res) =
1384+
let (payment_store_res, node_metris_res, pending_payment_store_res, channel_record_store_res) =
13831385
runtime.block_on(async move {
13841386
tokio::join!(
13851387
read_all_objects(
@@ -1394,6 +1396,12 @@ fn build_with_store_internal(
13941396
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
13951397
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
13961398
Arc::clone(&logger_ref),
1399+
),
1400+
read_all_objects(
1401+
&*kv_store_ref,
1402+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE,
1403+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE,
1404+
Arc::clone(&logger_ref),
13971405
)
13981406
)
13991407
});
@@ -1605,6 +1613,20 @@ fn build_with_store_internal(
16051613
},
16061614
};
16071615

1616+
let channel_record_store = match channel_record_store_res {
1617+
Ok(channel_records) => Arc::new(ChannelRecordStore::new(
1618+
channel_records,
1619+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
1620+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
1621+
Arc::clone(&kv_store),
1622+
Arc::clone(&logger),
1623+
)),
1624+
Err(e) => {
1625+
log_error!(logger, "Failed to read channel record data from store: {}", e);
1626+
return Err(BuildError::ReadFailed);
1627+
},
1628+
};
1629+
16081630
let wallet = Arc::new(Wallet::new(
16091631
bdk_wallet,
16101632
wallet_persister,
@@ -2151,6 +2173,7 @@ fn build_with_store_internal(
21512173
scorer,
21522174
peer_store,
21532175
payment_store,
2176+
channel_record_store,
21542177
lnurl_auth,
21552178
is_running,
21562179
node_metrics,

0 commit comments

Comments
 (0)