nautechsystems/nautilus_trader · error · anyhow::Error

Invalid block-scoped snapshot hash

Error message

Invalid block-scoped snapshot hash

What it means

For a block-scoped snapshot row, the stored transaction_hash must be a valid 32-byte B256 hex value; the ingestion-time block hash doubles as the snapshot's transaction hash. If the field cannot be parsed as B256, the row is malformed and processing stops. The subsequent check (not this error) also requires it to equal the expected block hash.

Source

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

        canonical_block.hash == expected_block_hash,
        "Pool state block {} changed from {} to {}; refresh the profiler before execution",
        position.number,
        expected_block_hash,
        canonical_block.hash
    );

    let snapshot_transaction = position.transaction_index == BLOCK_SCOPED_SNAPSHOT_INDEX;
    let snapshot_log = position.log_index == BLOCK_SCOPED_SNAPSHOT_INDEX;
    anyhow::ensure!(
        snapshot_transaction == snapshot_log,
        "Pool state at block {} has an invalid partial snapshot watermark",
        position.number
    );

    if snapshot_transaction {
        let snapshot_hash = B256::from_str(&position.transaction_hash)
            .with_context(|| "Invalid block-scoped snapshot hash")?;
        anyhow::ensure!(
            snapshot_hash == expected_block_hash,
            "Block-scoped snapshot hash {snapshot_hash} does not match ingestion hash {expected_block_hash}"
        );
    } else {
        validate_profiler_event_verified(
            position,
            expected_block_hash,
            plan.pool_address,
            &plan.pool,
            verification,
        )
        .await?;
    }

    let ancestry = verified_value(
        verification
            .verify_header_window(canonical_block, head.number)
            .await,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-ingest the block-scoped snapshot so transaction_hash is written as the proper block hash hex.
  2. Backfill the row with the correct block hash (0x-prefixed 64 hex chars) if re-ingestion isn't possible.
  3. Upgrade or fix the indexer component that produced the malformed hash.
  4. Validate snapshot rows at write time to reject non-B256 values early.

Example fix

// before
position.transaction_hash = "snapshot".to_string();

// after: store the block hash as the snapshot marker
position.transaction_hash = format!("{:#x}", block_hash); // valid B256 hex
Defensive patterns

Strategy: validation

Validate before calling

if B256::from_str(&position.transaction_hash).is_err() {
    anyhow::bail!("snapshot row at block {} has malformed transaction_hash", position.number);
}

Type guard

fn valid_snapshot_hash(position: &PoolStatePosition) -> bool {
    B256::from_str(&position.transaction_hash).is_ok()
}

Prevention

When it happens

Trigger: Parsing position.transaction_hash with B256::from_str fails during block-scoped-snapshot handling — e.g. the field holds a placeholder, empty string, non-hex text, or wrong-length hash.

Common situations: An indexer version writing placeholder snapshot hashes; manual data fixes with malformed hex; a schema change that repurposed transaction_hash; snapshots copied across deployments with different encodings.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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