nautechsystems/nautilus_trader · error

Failed to persist verified action evidence: {e}

Error message

Failed to persist verified action evidence: {e}

What it means

The INSERT/UPDATE persisting a verified action-evidence batch into the database failed inside its transaction; the wrapped database error indicates the evidence record (intent, digest, heights, providers) could not be durably stored, aborting the persist step.

Source

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

                ",
            )
            .bind(batch.intent_id)
            .bind(nonce)
            .bind(batch.decision_class)
            .bind(decision.read_class)
            .bind(height_start)
            .bind(height_end)
            .bind(batch.manifest_version)
            .bind(batch.manifest_digest)
            .bind(batch.provider_ids)
            .bind(batch.operator_ids)
            .bind(batch.failure_domain_ids)
            .bind(&decision.normalized_value_digest)
            .bind(revision)
            .bind(transition_key)
            .execute(&mut *transaction)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to persist verified action evidence: {e}"))?;
        }
        transaction
            .commit()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to commit verified action evidence: {e}"))?;
        Ok(())
    }

    pub(crate) async fn load_execution_replacement_cursor(
        &self,
        intent_id: i64,
        chain_id: u32,
        wallet_address: &str,
        nonce: u64,
        manifest_digest: &str,
    ) -> anyhow::Result<Option<ExecutionVerifiedHeader>> {
        let chain_id = i32::try_from(chain_id)
            .context("Replacement scan chain ID exceeds PostgreSQL INTEGER")?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Read the inner `{e}` to identify the specific constraint or type error
  2. Ensure the same (decision_class, intent_id, attempt) evidence is not inserted twice within one batch
  3. Verify the Rust bind types match the Postgres column types (text[], i64, etc.)
  4. Re-run migrations if the table schema is older than the code expects
Defensive patterns

Strategy: validation

Validate before calling

// ensure no duplicate evidence rows for this attempt before inserting
let existing: i64 = sqlx::query_scalar(
    "SELECT COUNT(*) FROM execution_verification_decision WHERE intent_id=$1 AND decision_class=$2",
).bind(batch.intent_id).bind(batch.decision_class).fetch_one(&pool).await?;
anyhow::ensure!(existing == 0, "evidence already recorded for this attempt");

Try / catch

match result {
    Err(e) if e.to_string().contains("duplicate key") => {
        // idempotent re-delivery of the same batch; treat as success or skip
    }
    Err(e) => return Err(e.into()),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: An INSERT INTO execution_verification_decision fails for one decision: constraint violation (e.g. duplicate transition_key), value out of range for a column, type mismatch on arrays (provider_ids/operator_ids/failure_domain_ids), or connection loss.

Common situations: Duplicate transition_key collisions when the same decision_class: intent_id: read_class: attempt: index combination is inserted twice; array element type mismatch between the Rust slice and the Postgres column type; schema drift after a manifest/revision change.

Related errors


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