nautechsystems/nautilus_trader · error · anyhow::Error

Verified nonce assignment requires exactly three provider…

Error message

Verified nonce assignment requires exactly three provider and operator IDs

What it means

The verified nonce-assignment path enforces a BFT-style quorum: it requires exactly three provider IDs and exactly three operator IDs in the ExecutionNonceAssignment. The ensure! at database.rs:5896 rejects any other cardinality, because the verification logic is designed for a 3-of-N provider/operator configuration.

Solutions

  1. Ensure exactly three provider IDs and three operator IDs are included in the assignment before calling the verified path.
  2. Fix the cluster/provider configuration so the deployment matches the required 3-provider/3-operator topology.
  3. If aggregation deduplicates or truncates, correct the aggregation logic to preserve exactly three distinct IDs.
  4. If a different quorum size is genuinely required, that is a code change to the invariant, not a caller workaround — coordinate with maintainers.

Example fix

// before: passing whatever providers responded
let assignment = ExecutionNonceAssignment { provider_ids: responded_providers, operator_ids: responded_operators, .. };
// after
anyhow::ensure!(responded_providers.len() == 3 && responded_operators.len() == 3,
    "need 3 providers and 3 operators, got {}/{}", responded_providers.len(), responded_operators.len());
let assignment = ExecutionNonceAssignment { provider_ids: responded_providers, operator_ids: responded_operators, .. };
database.assign_execution_intent_nonce_verified(&assignment).await?;
Defensive patterns

Strategy: validation

Validate before calling

// validate quorum cardinality before the verified call
if assignment.provider_ids.len() != 3 || assignment.operator_ids.len() != 3 {
    return Err(anyhow::anyhow!(
        "verified assignment needs exactly 3 providers/3 operators, got {}/{}",
        assignment.provider_ids.len(), assignment.operator_ids.len()));
}

Type guard

fn has_full_quorum(a: &ExecutionNonceAssignment<'_>) -> bool {
    a.provider_ids.len() == 3 && a.operator_ids.len() == 3
}

Try / catch

match database.assign_execution_intent_nonce_verified(&assignment).await {
    Err(e) if e.to_string().contains("exactly three provider and operator IDs") => {
        tracing::error!(providers = assignment.provider_ids.len(), operators = assignment.operator_ids.len(), "quorum cardinality mismatch");
        return Err(e);
    }
    Ok(()) => {},
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing an ExecutionNonceAssignment where provider_ids.len() != 3 or operator_ids.len() != 3 — e.g. built from a config with fewer/more providers, partially collected evidence (2 providers responded), or duplicated/missing IDs when aggregating decisions.

Common situations: Misconfigured cluster running with only 2 operators available; evidence aggregation dropping duplicates (leaving <3) or including extras; changing cluster size without updating the verified-assignment quorum expectation; environment mismatch between staging (smaller cluster) and production (3 operators).

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        );
        Ok(())
    }

    /// Assigns the canonical nonce and its authorizing verification evidence atomically.
    ///
    /// # Errors
    ///
    /// Returns an error if the durable nonce ledger, intent ownership, manifest identity, or
    /// evidence is inconsistent, or if persistence fails.
    pub(crate) async fn assign_execution_intent_nonce_verified(
        &self,
        assignment: &ExecutionNonceAssignment<'_>,
    ) -> anyhow::Result<()> {
        anyhow::ensure!(
            !assignment.decisions.is_empty(),
            "Verified nonce assignment requires decision evidence"
        );
        anyhow::ensure!(
            assignment.provider_ids.len() == 3 && assignment.operator_ids.len() == 3,
            "Verified nonce assignment requires exactly three provider and operator IDs"
        );
        anyhow::ensure!(
            assignment.failure_domain_ids.len() >= 3,
            "Verified nonce assignment requires the configured failure domains"
        );
        let chain_id = i32::try_from(assignment.chain_id)
            .context("Verification chain ID exceeds PostgreSQL INTEGER")?;
        let nonce =
            i64::try_from(assignment.nonce).context("Execution nonce exceeds PostgreSQL BIGINT")?;
        let mut transaction = self
            .pool
            .begin()
            .await
            .map_err(|e| anyhow::anyhow!("Failed to start verified nonce assignment: {e}"))?;
        let (manifest_version, manifest_digest, next_nonce, revision) =
            sqlx::query_as::<_, (String, String, i64, i64)>(

View on GitHub (pinned to 18893faf8b)