nautechsystems/nautilus_trader · error

Replacement scan cursor base fee is invalid

Error message

Replacement scan cursor base fee is invalid

What it means

Data-integrity check when loading the replacement scan cursor: the stored cursor row's base_fee column decoded to a negative value, which is not a valid EIP-1559 base fee, so the cursor is rejected as corrupt.

Source

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

        .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()
    }

    pub(crate) async fn record_execution_replacement_scan(
        &self,
        scan: &ExecutionReplacementScan<'_>,
    ) -> anyhow::Result<()> {
        anyhow::ensure!(
            !scan.decisions.is_empty()
                && scan.provider_ids.len() == 3
                && scan.operator_ids.len() == 3
                && scan.failure_domain_ids.len() >= 3,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the stored base_fee_per_gas string and correct or delete the row
  2. Fix the writer to store base fee as a canonical decimal u128 string
  3. Re-run the replacement scan to record a fresh cursor
  4. If the value can legitimately exceed u128, widen the parse/format

Example fix

// before
anyhow::anyhow!("Replacement scan cursor base fee is invalid")
// after
anyhow::anyhow!("Replacement scan cursor base fee is invalid: {value}") // log the offending value to diagnose hex vs decimal corruption
Defensive patterns

Strategy: validation

Validate before calling

fn parse_base_fee(s: &str) -> anyhow::Result<u128> {
    let v = s.parse::<u128>()
        .map_err(|e| anyhow::anyhow!("base fee {s:?} not decimal u128: {e}"))?;
    Ok(v)
}

Type guard

fn is_decimal_u128(s: &str) -> bool {
    !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) && s.parse::<u128>().is_ok()
}

Try / catch

match db.load_execution_replacement_cursor(..).await {
    Err(e) if e.to_string().contains("base fee is invalid") => {
        // row corrupt: delete and re-record scan
    }
    other => other,
}

Prevention

When it happens

Trigger: load_execution_replacement_cursor reads a non-empty base_fee_per_gas string that fails `value.parse::<u128>()` — e.g. hex-encoded, empty string, scientific notation, or truncated text.

Common situations: A writer stored the fee in hex or with 0x prefix; value exceeded u128 range; manual row edits; schema drift where the column holds a different encoding.

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/90b052ea2ee615ce. Report an issue: GitHub.