nautechsystems/nautilus_trader · error · anyhow::Error

Verified nonce assignment requires exactly three provider an

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.

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)

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.