@@ -13,9 +13,10 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
1313
1414use bdk_chain:: { BlockId , ConfirmationBlockTime , TxUpdate } ;
1515use bdk_wallet:: Update ;
16- use bip157:: chain:: BlockHeaderChanges ;
16+ use bip157:: chain:: { BlockHeaderChanges , ChainState } ;
1717use 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} ;
2021use bitcoin:: constants:: SUBSIDY_HALVING_INTERVAL ;
2122use 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`.
4950const 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.
5259enum 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
0 commit comments