nautechsystems/nautilus_trader · error · anyhow::Error

Finalized block timestamp overflows nanoseconds

Error message

Finalized block timestamp overflows nanoseconds

What it means

Emitted when block.timestamp.checked_mul(NANOSECONDS_IN_SECOND) overflows while converting the finalized block's timestamp to nanoseconds for the fill's ts_event. A compliant EVM node reports timestamps in seconds, which cannot overflow a u64 when scaled by 1e9; overflow therefore indicates the node reported the timestamp in non-second units (e.g., milliseconds) or returned garbage.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:2269

        "Finalized Swap input {base_amount} does not match the persisted amount {}",
        plan.amount_in
    );

    let block = executor
        .http_rpc_client
        .block_by_number(included.block_number, false)
        .await?;
    anyhow::ensure!(
        block.hash == included.receipt.block_hash,
        "Finalized block {} changed from {} to {} before fill emission",
        included.block_number,
        included.receipt.block_hash,
        block.hash
    );
    let timestamp_ns = block
        .timestamp
        .checked_mul(NANOSECONDS_IN_SECOND)
        .ok_or_else(|| anyhow::anyhow!("Finalized block timestamp overflows nanoseconds"))?;
    let mut swap = event.to_pool_swap(
        plan.pool.chain.clone(),
        plan.instrument_id,
        plan.pool.pool_identifier,
        UnixNanos::from(timestamp_ns),
    );
    swap.calculate_trade_info(&plan.pool.token0, &plan.pool.token1, None)?;
    let trade = swap
        .trade_info
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("Finalized Swap has no calculated trade information"))?;
    anyhow::ensure!(
        trade.order_side == OrderSide::Sell,
        "Finalized Swap side {} does not match Sell order",
        trade.order_side
    );
    let gas_cost = included
        .receipt

View on GitHub (pinned to 2114cf6f76)

Solutions

  1. Fetch the block with eth_getBlockByNumber and inspect the timestamp magnitude: seconds since epoch should be ~1.7e9; ~1.7e12 means milliseconds.
  2. Switch to a consensus-compliant endpoint that reports seconds.
  3. If you control the fixture/stub, generate timestamps in seconds.
  4. Report non-second timestamps to the node operator as a spec violation.
Defensive patterns

Strategy: validation

Validate before calling

const MIN_TS: u64 = 1_430_000_000;  // ~2015, post-genesis sanity floor
const MAX_TS: u64 = 4_102_444_800;  // year 2100
fn timestamp_is_seconds(ts: u64) -> bool { (MIN_TS..=MAX_TS).contains(&ts) }

Type guard

fn block_timestamp_is_plausible(block: &RpcBlock) -> bool {
    timestamp_is_seconds(block.timestamp)
}

Try / catch

Catch the overflow, log the raw block.timestamp value, and treat the endpoint as non-compliant until its timestamp units are fixed; do not divide ad hoc to 'recover'.

Prevention

When it happens

Trigger: RPC node or proxy reporting block timestamps in milliseconds/microseconds instead of seconds; a misbehaving or malicious endpoint returning near-u64::MAX timestamps; custom chains that overload the timestamp field with non-second semantics.

Common situations: L2 nodes or gateway providers with non-standard timestamp units; test stubs generating block fixtures with arbitrary large timestamp values.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@2114cf6f76 (2026-08-21). Data as JSON: /api/errors/46935283d2970b51. Report an issue: GitHub.