nautechsystems/nautilus_trader · error · anyhow::Error

Pool state at block {} has an invalid partial snapshot water

Error message

Pool state at block {} has an invalid partial snapshot watermark

What it means

Block-scoped snapshots are recorded as a single watermark where transaction_index and log_index must both equal the sentinel BLOCK_SCOPED_SNAPSHOT_INDEX. If exactly one of them matches, the snapshot row is only partially written or corrupted, and the library cannot tell whether the event is a snapshot marker or a normal event. It rejects the row as invalid.

Source

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

        verification.verify_decision_header(now_unix_secs).await,
        "swap decision header",
    )?;
    validate_quote_age(position.number, head.number, max_age_blocks)?;
    let canonical_block = verified_value(
        verification.verify_block(position.number).await,
        "profiler watermark header",
    )?;
    anyhow::ensure!(
        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,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Re-ingest the snapshot for that block so both transaction_index and log_index are set (or unset) consistently.
  2. Fix the indexer/writer to set both sentinel fields atomically.
  3. Check for in-progress writes or migrations touching that row and re-run them.
  4. If the row should be a normal event, clear both sentinel fields.

Example fix

// before: partially set sentinel
position.transaction_index = BLOCK_SCOPED_SNAPSHOT_INDEX;

// after: set both atomically
position.transaction_index = BLOCK_SCOPED_SNAPSHOT_INDEX;
position.log_index = BLOCK_SCOPED_SNAPSHOT_INDEX;
Defensive patterns

Strategy: validation

Validate before calling

let snap_tx = position.transaction_index == BLOCK_SCOPED_SNAPSHOT_INDEX;
let snap_log = position.log_index == BLOCK_SCOPED_SNAPSHOT_INDEX;
if snap_tx != snap_log {
    anyhow::bail!("partial snapshot watermark at block {}", position.number);
}

Type guard

fn is_consistent_snapshot_marker(position: &PoolStatePosition) -> bool {
    (position.transaction_index == BLOCK_SCOPED_SNAPSHOT_INDEX)
        == (position.log_index == BLOCK_SCOPED_SNAPSHOT_INDEX)
}

Prevention

When it happens

Trigger: During quote validation, position.transaction_index == BLOCK_SCOPED_SNAPSHOT_INDEX differs in truth from position.log_index == BLOCK_SCOPED_SNAPSHOT_INDEX for the pool-state row.

Common situations: A partially applied update that set one sentinel field but not the other; a buggy indexer writing snapshot markers; manual DB edits; schema migration that changed the sentinel encoding for one column only.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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