Skip to content

Commit 303deaa

Browse files
committed
fix(cbf): wait for first tip and degrade gracefully on partial fetches
Block the startup fee update on the first FiltersSynced event so the cache is populated before the background loop's first tick, and stop failing the whole update when Kyoto cannot serve historical blocks outside its known header range — use whatever recent blocks we have.
1 parent f89b46b commit 303deaa

1 file changed

Lines changed: 47 additions & 17 deletions

File tree

src/chain/cbf.rs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -877,44 +877,66 @@ impl CbfChainSource {
877877

878878
/// Derive per-target fee rates from recent blocks' coinbase outputs.
879879
///
880-
/// Returns `Ok(None)` when no chain tip is available yet (first startup before sync).
880+
/// On the very first startup call this waits for the initial `FiltersSynced` event so
881+
/// the fee cache gets populated before the background loop's first tick; subsequent
882+
/// calls find the tip already set and proceed immediately. The wait shares the
883+
/// overall timeout budget with the block fetches below.
884+
///
885+
/// Returns `Ok(None)` when no chain tip can be established within the budget, or when
886+
/// the tip block itself cannot be served by the CBF peer (initial sync / reorg). When
887+
/// subsequent historical blocks are unavailable (Kyoto only serves blocks in its
888+
/// known header chain range, so walking back far enough hits `UnknownHash`), the
889+
/// function falls back to whatever blocks it already fetched rather than failing the
890+
/// whole fee update.
881891
async fn fee_rate_cache_from_cbf(
882892
&self,
883893
) -> Result<Option<HashMap<crate::fee_estimator::ConfirmationTarget, FeeRate>>, Error> {
884894
let requester = self.requester()?;
885895

886-
let tip_hash = match *self.latest_tip.lock().unwrap() {
887-
Some(hash) => hash,
888-
None => {
889-
log_debug!(self.logger, "No tip available yet for fee rate estimation, skipping.");
896+
let now = Instant::now();
897+
let timeout = Duration::from_secs(
898+
self.sync_config.timeouts_config.fee_rate_cache_update_timeout_secs,
899+
);
900+
901+
let tip_hash = loop {
902+
if let Some(hash) = *self.latest_tip.lock().unwrap() {
903+
break hash;
904+
}
905+
if now.elapsed() >= timeout {
906+
log_debug!(
907+
self.logger,
908+
"No CBF tip available within timeout; skipping fee update.",
909+
);
890910
return Ok(None);
891-
},
911+
}
912+
tokio::time::sleep(Duration::from_millis(100)).await;
892913
};
893914

894-
let now = Instant::now();
895-
896915
// Fetch fee rates from the last N blocks for per-target estimation.
897916
// We compute fee rates ourselves rather than using Requester::average_fee_rate,
898917
// so we can sample multiple blocks and select percentiles per confirmation target.
899918
let mut block_fee_rates: Vec<u64> = Vec::with_capacity(FEE_RATE_LOOKBACK_BLOCKS);
900919
let mut current_hash = tip_hash;
901920

902-
let timeout = Duration::from_secs(
903-
self.sync_config.timeouts_config.fee_rate_cache_update_timeout_secs,
904-
);
905-
let fetch_start = Instant::now();
921+
let fetch_start = now;
906922

907923
for idx in 0..FEE_RATE_LOOKBACK_BLOCKS {
908924
// Check if we've exceeded the overall timeout for fee estimation.
909925
let remaining_timeout = timeout.saturating_sub(fetch_start.elapsed());
910926
if remaining_timeout.is_zero() {
927+
if !block_fee_rates.is_empty() {
928+
break;
929+
}
911930
log_error!(self.logger, "Updating fee rate estimates timed out.");
912931
return Err(Error::FeerateEstimationUpdateTimeout);
913932
}
914933

915934
// Fetch the block via P2P. On the first iteration, a fetch failure
916935
// likely means the cached tip is stale (initial sync or reorg), so
917936
// we clear the tip and skip gracefully instead of returning an error.
937+
// On later iterations we may walk past Kyoto's known header range and
938+
// hit `UnknownHash`; in that case we use whatever blocks we already
939+
// have rather than failing the whole fee update.
918940
let indexed_block =
919941
match tokio::time::timeout(remaining_timeout, requester.get_block(current_hash))
920942
.await
@@ -932,12 +954,14 @@ impl CbfChainSource {
932954
return Ok(None);
933955
},
934956
Ok(Err(e)) => {
935-
log_error!(
957+
log_debug!(
936958
self.logger,
937-
"Failed to fetch block for fee estimation: {:?}",
959+
"CBF could not serve block {} during fee estimation after {} successful fetches: {:?}",
960+
current_hash,
961+
block_fee_rates.len(),
938962
e
939963
);
940-
return Err(Error::FeerateEstimationUpdateFailed);
964+
break;
941965
},
942966
Err(e) if idx == 0 => {
943967
log_debug!(
@@ -951,8 +975,14 @@ impl CbfChainSource {
951975
return Ok(None);
952976
},
953977
Err(e) => {
954-
log_error!(self.logger, "Updating fee rate estimates timed out: {}", e);
955-
return Err(Error::FeerateEstimationUpdateTimeout);
978+
log_debug!(
979+
self.logger,
980+
"Timed out fetching block {} during fee estimation after {} successful fetches: {}",
981+
current_hash,
982+
block_fee_rates.len(),
983+
e
984+
);
985+
break;
956986
},
957987
};
958988

0 commit comments

Comments
 (0)