nautechsystems/nautilus_trader · error

Replacement scan verification evidence is incomplete

Error message

Replacement scan verification evidence is incomplete

What it means

record_execution_replacement_scan validates that the scan carries complete verification evidence: at least one decision, exactly 3 provider IDs, exactly 3 operator IDs, and at least 3 failure domain IDs. If any of these invariants is unmet, the scan is rejected before any database writes.

Source

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

                        .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,
            "Replacement scan verification evidence is incomplete"
        );
        let chain_id = i32::try_from(scan.chain_id)
            .context("Replacement scan chain ID exceeds PostgreSQL INTEGER")?;
        let nonce = i64::try_from(scan.nonce)
            .context("Replacement scan nonce exceeds PostgreSQL BIGINT")?;
        let mut transaction = self
            .pool
            .begin()
            .await
            .context("failed to start verified replacement scan transition")?;
        let (stored_version, stored_digest, stored_nonce, revision) =
            sqlx::query_as::<_, (String, String, i64, i64)>(
                "

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Populate provider_ids and operator_ids with the 3 configured identities and at least 3 failure domain IDs before calling
  2. Ensure at least one verification decision is collected into scan.decisions
  3. Fix the upstream discovery/aggregation step that produced incomplete identity sets
  4. Assert scan completeness at construction time to fail early

Example fix

// before
let scan = ExecutionReplacementScan { decisions: vec![], provider_ids: p, operator_ids: o, failure_domain_ids: f, .. };
// after
assert_eq!(p.len(), 3, "provider_ids");
assert_eq!(o.len(), 3, "operator_ids");
assert!(f.len() >= 3, "failure_domain_ids");
assert!(!decisions.is_empty(), "decisions");
let scan = ExecutionReplacementScan { decisions, provider_ids: p, operator_ids: o, failure_domain_ids: f, .. };
Defensive patterns

Strategy: validation

Validate before calling

fn scan_evidence_complete(scan: &ExecutionReplacementScan) -> bool {
    !scan.decisions.is_empty()
        && scan.provider_ids.len() == 3
        && scan.operator_ids.len() == 3
        && scan.failure_domain_ids.len() >= 3
}
anyhow::ensure!(scan_evidence_complete(&scan), "scan evidence incomplete");

Type guard

fn is_complete_scan(scan: &ExecutionReplacementScan) -> bool {
    scan.decisions.len() > 0
        && scan.provider_ids.len() == 3
        && scan.operator_ids.len() == 3
        && scan.failure_domain_ids.len() >= 3
}

Try / catch

if let Err(e) = db.record_execution_replacement_scan(&scan).await {
    if e.to_string().contains("evidence is incomplete") {
        // fix scan construction; this is a caller bug, not retryable
        return Err(anyhow::anyhow!("refusing to record scan: {e}"));
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Calling record_execution_replacement_scan with an ExecutionReplacementScan where decisions is empty, provider_ids.len() != 3, operator_ids.len() != 3, or failure_domain_ids.len() < 3.

Common situations: Constructing the scan struct with default/empty collections; a provider/operator discovery step returning fewer identities than configured; filtering decisions out before recording, leaving an empty batch.

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/13f24b056742a1a4. Report an issue: GitHub.