Skip to content

Commit 0151c4a

Browse files
committed
refactor(cbf): extract build_cbf_node helper and add wallet reference
Preparation for auto-restart: extract bip157 node build logic into a reusable helper method, add chain_state() from wallet checkpoint to avoid genesis re-sync, and thread Arc<Wallet> through start(). AI: claude
1 parent 7b878ef commit 0151c4a

File tree

3 files changed

+70
-17
lines changed

3 files changed

+70
-17
lines changed

src/chain/cbf.rs

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
1313

1414
use bdk_chain::{BlockId, ConfirmationBlockTime, TxUpdate};
1515
use bdk_wallet::Update;
16-
use bip157::chain::BlockHeaderChanges;
16+
use bip157::chain::{BlockHeaderChanges, ChainState};
1717
use bip157::{
18-
BlockHash, Builder, Client, Event, Info, Requester, SyncUpdate, TrustedPeer, Warning,
18+
BlockHash, Builder, Client, Event, HeaderCheckpoint, Info, Node as CbfNode, Requester,
19+
SyncUpdate, TrustedPeer, Warning,
1920
};
2021
use bitcoin::constants::SUBSIDY_HALVING_INTERVAL;
2122
use bitcoin::{Amount, FeeRate, Network, Script, ScriptBuf, Transaction, Txid};
@@ -48,6 +49,12 @@ const FEE_RATE_LOOKBACK_BLOCKS: usize = 6;
4849
/// Matches bdk-kyoto's `IMPOSSIBLE_REORG_DEPTH`.
4950
const REORG_SAFETY_BLOCKS: u32 = 7;
5051

52+
/// Maximum consecutive restart attempts before giving up.
53+
const MAX_RESTART_RETRIES: u32 = 5;
54+
55+
/// Initial backoff delay for restart retries (doubles each attempt).
56+
const INITIAL_BACKOFF_MS: u64 = 500;
57+
5158
/// The fee estimation back-end used by the CBF chain source.
5259
enum FeeSource {
5360
/// Derive fee rates from the coinbase reward of recent blocks.
@@ -97,6 +104,8 @@ pub(super) struct CbfChainSource {
97104
kv_store: Arc<DynStore>,
98105
/// Node configuration (network, storage path, etc.).
99106
config: Arc<Config>,
107+
/// On-chain wallet reference for deriving chain_state checkpoints on restart.
108+
onchain_wallet: Mutex<Option<Arc<Wallet>>>,
100109
/// Logger instance.
101110
logger: Arc<Logger>,
102111
/// Shared node metrics (sync timestamps, etc.).
@@ -148,6 +157,7 @@ impl CbfChainSource {
148157
let scan_lock = tokio::sync::Mutex::new(());
149158
let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
150159
let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed);
160+
let onchain_wallet = Mutex::new(None);
151161
Ok(Self {
152162
peers,
153163
sync_config,
@@ -163,21 +173,21 @@ impl CbfChainSource {
163173
onchain_wallet_sync_status,
164174
lightning_wallet_sync_status,
165175
fee_estimator,
176+
onchain_wallet,
166177
kv_store,
167178
config,
168179
logger,
169180
node_metrics,
170181
})
171182
}
172183

173-
/// Start the bip157 node and spawn background tasks for event processing.
174-
pub(crate) fn start(&self, runtime: Arc<Runtime>) {
175-
let mut status = self.cbf_runtime_status.lock().unwrap();
176-
if matches!(*status, CbfRuntimeStatus::Started { .. }) {
177-
debug_assert!(false, "We shouldn't call start if we're already started");
178-
return;
179-
}
180-
184+
/// Build a new bip157 node and client from the current configuration.
185+
///
186+
/// If an on-chain wallet reference is available, a `ChainState::Checkpoint`
187+
/// is derived from the wallet's persisted checkpoint (walked back by
188+
/// `REORG_SAFETY_BLOCKS`) so the node resumes near its last known height
189+
/// instead of re-syncing from genesis.
190+
fn build_cbf_node(&self) -> (CbfNode, Client) {
181191
let network = self.config.network;
182192

183193
let mut builder = Builder::new(network);
@@ -210,7 +220,46 @@ impl CbfChainSource {
210220
builder =
211221
builder.response_timeout(Duration::from_secs(self.sync_config.response_timeout_secs));
212222

213-
let (node, client) = builder.build();
223+
// If we have a wallet reference, derive a chain_state checkpoint so the
224+
// bip157 node can skip already-synced headers on restart.
225+
if let Some(wallet) = self.onchain_wallet.lock().unwrap().as_ref() {
226+
let cp = wallet.latest_checkpoint();
227+
let target_height = cp.height().saturating_sub(REORG_SAFETY_BLOCKS);
228+
// Walk the checkpoint chain back to the target height.
229+
let mut cursor = cp;
230+
while cursor.height() > target_height {
231+
match cursor.prev() {
232+
Some(prev) => cursor = prev,
233+
None => break,
234+
}
235+
}
236+
if cursor.height() > 0 {
237+
let header_cp = HeaderCheckpoint::new(cursor.height(), cursor.hash());
238+
builder = builder.chain_state(ChainState::Checkpoint(header_cp));
239+
log_debug!(
240+
self.logger,
241+
"CBF builder: resuming from checkpoint height={}, hash={}",
242+
cursor.height(),
243+
cursor.hash(),
244+
);
245+
}
246+
}
247+
248+
builder.build()
249+
}
250+
251+
/// Start the bip157 node and spawn background tasks for event processing.
252+
pub(crate) fn start(&self, runtime: Arc<Runtime>, onchain_wallet: Arc<Wallet>) {
253+
let mut status = self.cbf_runtime_status.lock().unwrap();
254+
if matches!(*status, CbfRuntimeStatus::Started { .. }) {
255+
debug_assert!(false, "We shouldn't call start if we're already started");
256+
return;
257+
}
258+
259+
// Store the wallet reference for future restarts.
260+
*self.onchain_wallet.lock().unwrap() = Some(Arc::clone(&onchain_wallet));
261+
262+
let (node, client) = self.build_cbf_node();
214263

215264
let Client { requester, info_rx, warn_rx, event_rx } = client;
216265

src/chain/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,15 @@ impl ChainSource {
222222
Ok((Self { kind, registered_txids, tx_broadcaster, logger }, None))
223223
}
224224

225-
pub(crate) fn start(&self, runtime: Arc<Runtime>) -> Result<(), Error> {
225+
pub(crate) fn start(
226+
&self, runtime: Arc<Runtime>, onchain_wallet: Arc<Wallet>,
227+
) -> Result<(), Error> {
226228
match &self.kind {
227229
ChainSourceKind::Electrum(electrum_chain_source) => {
228230
electrum_chain_source.start(runtime)?
229231
},
230232
ChainSourceKind::Cbf(cbf_chain_source) => {
231-
cbf_chain_source.start(runtime);
233+
cbf_chain_source.start(runtime, onchain_wallet);
232234
},
233235
_ => {
234236
// Nothing to do for other chain sources.

src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,12 @@ impl Node {
268268
);
269269

270270
// Start up any runtime-dependant chain sources (e.g. Electrum)
271-
self.chain_source.start(Arc::clone(&self.runtime)).map_err(|e| {
272-
log_error!(self.logger, "Failed to start chain syncing: {}", e);
273-
e
274-
})?;
271+
self.chain_source.start(Arc::clone(&self.runtime), Arc::clone(&self.wallet)).map_err(
272+
|e| {
273+
log_error!(self.logger, "Failed to start chain syncing: {}", e);
274+
e
275+
},
276+
)?;
275277

276278
// Block to ensure we update our fee rate cache once on startup
277279
let chain_source = Arc::clone(&self.chain_source);

0 commit comments

Comments
 (0)