@@ -80,8 +80,6 @@ pub(super) struct CbfChainSource {
8080 fee_source : FeeSource ,
8181 /// Tracks whether the bip157 node is running and holds the command handle.
8282 cbf_runtime_status : Arc < Mutex < CbfRuntimeStatus > > ,
83- /// Latest chain tip hash, updated by the background event processing task.
84- latest_tip : Arc < Mutex < Option < BlockHash > > > ,
8583 /// Scripts to match against compact block filters during a scan.
8684 watched_scripts : Arc < RwLock < Vec < ScriptBuf > > > ,
8785 /// Block (height, hash) pairs where filters matched watched scripts.
@@ -119,7 +117,6 @@ enum CbfRuntimeStatus {
119117
120118/// Shared state passed to the background event processing task.
121119struct CbfEventState {
122- latest_tip : Arc < Mutex < Option < BlockHash > > > ,
123120 watched_scripts : Arc < RwLock < Vec < ScriptBuf > > > ,
124121 matched_block_hashes : Arc < Mutex < Vec < ( u32 , BlockHash ) > > > ,
125122 sync_completion_tx : Arc < Mutex < Option < oneshot:: Sender < SyncUpdate > > > > ,
@@ -148,7 +145,6 @@ impl CbfChainSource {
148145 } ;
149146
150147 let cbf_runtime_status = Arc :: new ( Mutex :: new ( CbfRuntimeStatus :: Stopped ) ) ;
151- let latest_tip = Arc :: new ( Mutex :: new ( None ) ) ;
152148 let watched_scripts = Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) ;
153149 let matched_block_hashes = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
154150 let sync_completion_tx = Arc :: new ( Mutex :: new ( None ) ) ;
@@ -163,7 +159,6 @@ impl CbfChainSource {
163159 sync_config,
164160 fee_source,
165161 cbf_runtime_status,
166- latest_tip,
167162 watched_scripts,
168163 matched_block_hashes,
169164 sync_completion_tx,
@@ -288,7 +283,6 @@ impl CbfChainSource {
288283 // block is 'static (no borrows of `self`).
289284 let restart_status = Arc :: clone ( & self . cbf_runtime_status ) ;
290285 let restart_logger = Arc :: clone ( & self . logger ) ;
291- let restart_latest_tip = Arc :: clone ( & self . latest_tip ) ;
292286 let restart_watched_scripts = Arc :: clone ( & self . watched_scripts ) ;
293287 let restart_matched_block_hashes = Arc :: clone ( & self . matched_block_hashes ) ;
294288 let restart_sync_completion_tx = Arc :: clone ( & self . sync_completion_tx ) ;
@@ -317,7 +311,6 @@ impl CbfChainSource {
317311 Arc :: clone ( & restart_logger) ,
318312 ) ) ;
319313 let event_state = CbfEventState {
320- latest_tip : Arc :: clone ( & restart_latest_tip) ,
321314 watched_scripts : Arc :: clone ( & restart_watched_scripts) ,
322315 matched_block_hashes : Arc :: clone ( & restart_matched_block_hashes) ,
323316 sync_completion_tx : Arc :: clone ( & restart_sync_completion_tx) ,
@@ -428,7 +421,6 @@ impl CbfChainSource {
428421 match event {
429422 Event :: FiltersSynced ( sync_update) => {
430423 let tip = sync_update. tip ( ) ;
431- * state. latest_tip . lock ( ) . unwrap ( ) = Some ( tip. hash ) ;
432424 log_info ! (
433425 logger,
434426 "CBF filters synced to tip: height={}, hash={}" ,
@@ -448,7 +440,6 @@ impl CbfChainSource {
448440 reorganized. len( ) ,
449441 accepted. len( ) ,
450442 ) ;
451- * state. latest_tip . lock ( ) . unwrap ( ) = None ;
452443
453444 // No height reset needed: skip heights are derived from
454445 // BDK's checkpoint (on-chain) and LDK's best block
@@ -861,60 +852,69 @@ impl CbfChainSource {
861852
862853 /// Derive per-target fee rates from recent blocks' coinbase outputs.
863854 ///
864- /// Returns `Ok(None)` when no chain tip is available yet (first startup before sync).
855+ /// Returns `Ok(None)` when the chain is too short to sample `FEE_RATE_LOOKBACK_BLOCKS`
856+ /// blocks (e.g. kyoto has not yet synced past the genesis region).
865857 async fn fee_rate_cache_from_cbf (
866858 & self ,
867859 ) -> Result < Option < HashMap < crate :: fee_estimator:: ConfirmationTarget , FeeRate > > , Error > {
868860 let requester = self . requester ( ) ?;
869861
870- let tip_hash = match * self . latest_tip . lock ( ) . unwrap ( ) {
871- Some ( hash) => hash,
872- None => {
873- log_debug ! ( self . logger, "No tip available yet for fee rate estimation, skipping." ) ;
862+ let timeout = Duration :: from_secs (
863+ self . sync_config . timeouts_config . fee_rate_cache_update_timeout_secs ,
864+ ) ;
865+ let fetch_start = Instant :: now ( ) ;
866+
867+ // Ask kyoto for its current chain tip rather than maintaining a mirrored
868+ // cache: the returned hash is always fresh (post-reorg, post-restart),
869+ // so no defensive invalidation is needed below.
870+ let tip = match tokio:: time:: timeout ( timeout, requester. chain_tip ( ) ) . await {
871+ Ok ( Ok ( tip) ) => tip,
872+ Ok ( Err ( e) ) => {
873+ log_debug ! (
874+ self . logger,
875+ "Failed to fetch CBF chain tip for fee estimation: {:?}" ,
876+ e,
877+ ) ;
874878 return Ok ( None ) ;
875879 } ,
880+ Err ( e) => {
881+ log_error ! ( self . logger, "Timed out fetching CBF chain tip: {}" , e) ;
882+ return Err ( Error :: FeerateEstimationUpdateTimeout ) ;
883+ } ,
876884 } ;
877885
886+ if ( tip. height as usize ) < FEE_RATE_LOOKBACK_BLOCKS {
887+ log_debug ! (
888+ self . logger,
889+ "CBF chain tip at height {} is below the {}-block lookback window, \
890+ skipping fee estimation.",
891+ tip. height,
892+ FEE_RATE_LOOKBACK_BLOCKS ,
893+ ) ;
894+ return Ok ( None ) ;
895+ }
896+
878897 let now = Instant :: now ( ) ;
879898
880899 // Fetch fee rates from the last N blocks for per-target estimation.
881900 // We compute fee rates ourselves rather than using Requester::average_fee_rate,
882901 // so we can sample multiple blocks and select percentiles per confirmation target.
883902 let mut block_fee_rates: Vec < u64 > = Vec :: with_capacity ( FEE_RATE_LOOKBACK_BLOCKS ) ;
884- let mut current_hash = tip_hash ;
903+ let mut current_hash = tip . hash ;
885904
886- let timeout = Duration :: from_secs (
887- self . sync_config . timeouts_config . fee_rate_cache_update_timeout_secs ,
888- ) ;
889- let fetch_start = Instant :: now ( ) ;
890-
891- for idx in 0 ..FEE_RATE_LOOKBACK_BLOCKS {
905+ for _ in 0 ..FEE_RATE_LOOKBACK_BLOCKS {
892906 // Check if we've exceeded the overall timeout for fee estimation.
893907 let remaining_timeout = timeout. saturating_sub ( fetch_start. elapsed ( ) ) ;
894908 if remaining_timeout. is_zero ( ) {
895909 log_error ! ( self . logger, "Updating fee rate estimates timed out." ) ;
896910 return Err ( Error :: FeerateEstimationUpdateTimeout ) ;
897911 }
898912
899- // Fetch the block via P2P. On the first iteration, a fetch failure
900- // likely means the cached tip is stale (initial sync or reorg), so
901- // we clear the tip and skip gracefully instead of returning an error.
902913 let indexed_block =
903914 match tokio:: time:: timeout ( remaining_timeout, requester. get_block ( current_hash) )
904915 . await
905916 {
906917 Ok ( Ok ( indexed_block) ) => indexed_block,
907- Ok ( Err ( e) ) if idx == 0 => {
908- log_debug ! (
909- self . logger,
910- "Cached CBF tip {} was unavailable during fee estimation, \
911- likely due to initial sync or a reorg: {:?}",
912- current_hash,
913- e
914- ) ;
915- * self . latest_tip . lock ( ) . unwrap ( ) = None ;
916- return Ok ( None ) ;
917- } ,
918918 Ok ( Err ( e) ) => {
919919 log_error ! (
920920 self . logger,
@@ -923,17 +923,6 @@ impl CbfChainSource {
923923 ) ;
924924 return Err ( Error :: FeerateEstimationUpdateFailed ) ;
925925 } ,
926- Err ( e) if idx == 0 => {
927- log_debug ! (
928- self . logger,
929- "Timed out fetching cached CBF tip {} during fee estimation, \
930- likely due to initial sync or a reorg: {}",
931- current_hash,
932- e
933- ) ;
934- * self . latest_tip . lock ( ) . unwrap ( ) = None ;
935- return Ok ( None ) ;
936- } ,
937926 Err ( e) => {
938927 log_error ! ( self . logger, "Updating fee rate estimates timed out: {}" , e) ;
939928 return Err ( Error :: FeerateEstimationUpdateTimeout ) ;
0 commit comments