nautechsystems/nautilus_trader · error

cannot advance RPC profiler from block {} to earlier block {

Error message

cannot advance RPC profiler from block {} to earlier block {to_block}

What it means

advance_pool_profiler_from_rpc_snapshot moves a profiler forward from its last processed event watermark to to_block. If the requested to_block is earlier than the current watermark (from_position.number), time would go backwards, which the API forbids.

Source

Thrown at crates/adapters/blockchain/src/data/core.rs:1613

    /// The profiler must come from [`Self::bootstrap_pool_profiler_from_rpc_snapshot`] or an earlier
    /// call to this method. This keeps one command incremental without trusting an unproven stored
    /// snapshot as the topology source.
    ///
    /// # Errors
    ///
    /// Returns an error if the profiler has no RPC snapshot watermark, the target precedes that
    /// watermark, event streaming fails, or RPC hydration fails.
    pub async fn advance_pool_profiler_from_rpc_snapshot(
        &mut self,
        profiler: PoolProfiler,
        to_block: u64,
    ) -> anyhow::Result<(PoolProfiler, bool)> {
        let from_position = profiler.last_processed_event.clone().ok_or_else(|| {
            anyhow::anyhow!("cannot advance an RPC profiler without a snapshot watermark")
        })?;

        if to_block < from_position.number {
            anyhow::bail!(
                "cannot advance RPC profiler from block {} to earlier block {to_block}",
                from_position.number
            );
        }

        self.construct_pool_profiler_from_hypersync_rpc(profiler, Some(from_position), to_block)
            .await
    }

    async fn seed_pool_profiler_from_latest_snapshot(
        &self,
        pool: &SharedPool,
        to_block: u64,
    ) -> anyhow::Result<(PoolProfiler, Option<BlockPosition>)> {
        let mut profiler = PoolProfiler::new(pool.clone());

        let from_position = match self
            .cache

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Query the profiler's last_processed_event and request to_block >= that block number
  2. If you truly need history before the watermark, rebuild a fresh profiler via bootstrap_pool_profiler_from_rpc_snapshot
  3. Ensure only one caller advances a given profiler and targets are monotonic

Example fix

// before
let to_block = 19_000_000; // older than profiler watermark
client.advance_pool_profiler_from_rpc_snapshot(profiler, to_block).await?;
// after
let to_block = profiler.last_processed_event.as_ref().unwrap().number.max(19_000_000);
client.advance_pool_profiler_from_rpc_snapshot(profiler, to_block).await?;
Defensive patterns

Strategy: validation

Validate before calling

let from = profiler.last_processed_event.as_ref().map(|p| p.number).unwrap_or(0);
assert!(to_block >= from, "to_block {to_block} precedes watermark {from}");
client.advance_pool_profiler_from_rpc_snapshot(profiler, to_block).await?;

Type guard

fn can_advance(p: &PoolProfiler, to_block: u64) -> bool {
    p.last_processed_event.as_ref().is_some_and(|w| to_block >= w.number)
}

Try / catch

match client.advance_pool_profiler_from_rpc_snapshot(profiler, to_block).await {
    Err(e) if e.to_string().contains("to earlier block") => {
        log::warn!("profiler already beyond {to_block}; nothing to do");
        Ok((profiler.clone(), false))
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling advance_pool_profiler_from_rpc_snapshot with to_block lower than profiler.last_processed_event.number, e.g. reusing a stale/older target block or mixing profilers across chains.

Common situations: Caching a to_block from a previous request and replaying it against an already-advanced profiler; concurrent callers advancing the same profiler with out-of-order blocks; recovering from a backup with an older target.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/94c9b5aa463d06b7. Report an issue: GitHub.