nautechsystems/nautilus_trader · error

Replacement scan cursor number is negative

Error message

Replacement scan cursor number is negative

What it means

The replacement scan cursor stores the block number as a signed i64 BIGINT. When converting it back to the u64 ExecutionVerifiedHeader.number, a negative value cannot be represented and this error is raised.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:6215

              AND s.wallet_address = $3
              AND s.nonce = $4
            ",
        )
        .bind(intent_id)
        .bind(chain_id)
        .bind(wallet_address)
        .bind(nonce)
        .fetch_optional(&self.pool)
        .await
        .context("failed to load replacement scan cursor")?;
        row.map(
            |(number, hash, parent_hash, timestamp, base_fee, stored_digest)| {
                anyhow::ensure!(
                    stored_digest == manifest_digest,
                    "Replacement scan manifest identity changed"
                );
                Ok(ExecutionVerifiedHeader {
                    number: u64::try_from(number)
                        .context("Replacement scan cursor number is negative")?,
                    hash,
                    parent_hash,
                    timestamp: u64::try_from(timestamp)
                        .context("Replacement scan cursor timestamp is negative")?,
                    base_fee_per_gas: base_fee
                        .map(|value| {
                            value.parse::<u128>().map_err(|_| {
                                anyhow::anyhow!("Replacement scan cursor base fee is invalid")
                            })
                        })
                        .transpose()?,
                })
            },
        )
        .transpose()
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the offending row's number value and correct or delete the corrupted row
  2. Audit the writer path that inserted the negative block number
  3. Re-run the replacement scan from a known-good finalized header
Defensive patterns

Strategy: validation

Validate before calling

let number: i64 = /* loaded cursor number */;
if number < 0 {
    // treat row as corrupt: delete and rescan
}

Type guard

fn valid_block_number(n: i64) -> bool { n >= 0 }

Try / catch

match db.load_execution_replacement_cursor(..).await {
    Err(e) if e.to_string().contains("cursor number is negative") => {
        // purge corrupt row and re-record the scan
    }
    other => other,
}

Prevention

When it happens

Trigger: The execution_verified_finalized_header.number column (or the joined scan row) contains a negative i64, and load_execution_replacement_cursor attempts u64::try_from(number).

Common situations: Manual database edits or corrupted rows; a buggy writer inserting sign-flipped values; type confusion when another tool wrote the table.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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