nautechsystems/nautilus_trader · error · anyhow::Error

Execution payload storage is not ready for protected persist

Error message

Execution payload storage is not ready for protected persistence

What it means

After locking the `execution_payload_state` row for 'signed_transactions', `add_execution_transaction_payload` validates via `anyhow::ensure!` that `protocol_version` matches `EXECUTION_PAYLOAD_PROTOCOL_VERSION` and `operation == "ready"`. This error fires when the protection row exists but is not in the ready state — the payload-protection lifecycle is mid-transition (activating, rotating, deactivating) or was written by an incompatible protocol version.

Source

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

        );
        let chain_id_db = i32::try_from(chain_id)
            .with_context(|| format!("Chain ID {chain_id} exceeds PostgreSQL INTEGER"))?;
        let mut transaction =
            self.pool.begin().await.map_err(|e| {
                anyhow::anyhow!("Failed to start signed transaction persistence: {e}")
            })?;

        if let Some(envelope) = sealed_transaction {
            let state_row = sqlx::query(
                "SELECT deployment_id, protocol_version, operation, active_key_id \
                 FROM execution_payload_state WHERE component = 'signed_transactions' FOR SHARE",
            )
            .fetch_optional(&mut *transaction)
            .await
            .context("failed to lock execution payload state for protected persistence")?
            .ok_or_else(|| anyhow::anyhow!("Execution payload protection is not active"))?;
            let state = execution_payload_state_from_row(&state_row)?;
            anyhow::ensure!(
                state.protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION
                    && state.operation == "ready",
                "Execution payload storage is not ready for protected persistence"
            );
            anyhow::ensure!(
                envelope_key_id(envelope)?.as_slice() == state.active_key_id.as_slice(),
                "Signed transaction envelope does not use the database active key"
            );
        } else {
            let marker = sqlx::query_scalar::<_, bool>(
                "SELECT EXISTS (SELECT 1 FROM execution_schema_version WHERE component = $1)",
            )
            .bind(EXECUTION_PAYLOAD_COMPONENT)
            .fetch_one(&mut *transaction)
            .await
            .context("failed to inspect execution payload marker")?;
            anyhow::ensure!(
                !marker,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Complete or re-run the payload protection activation/rotation procedure so `operation` returns to 'ready' and `protocol_version` matches the binary
  2. Check `SELECT protocol_version, operation FROM execution_payload_state WHERE component = 'signed_transactions'` to see the actual state and diagnose which condition failed
  3. Deploy a binary whose EXECUTION_PAYLOAD_PROTOCOL_VERSION matches the value stored in the database (or migrate the state with the deployment tooling)
  4. Wait for an in-flight key rotation to finish before broadcasting/persisting signed transactions
Defensive patterns

Strategy: validation

Validate before calling

let (protocol_version, operation): (i64, String) = sqlx::query_as(
    "SELECT protocol_version, operation FROM execution_payload_state WHERE component = 'signed_transactions'",
).fetch_one(&db.pool).await?;
anyhow::ensure!(
    protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION && operation == "ready",
    "payload state not ready (v{protocol_version}, op={operation})"
);

Try / catch

match db.add_execution_transaction_envelope(intent_id, chain_id, &hash, &sealed).await {
    Err(e) if e.to_string().contains("not ready for protected persistence") => {
        // pause broadcasting until rotation/activation completes, then re-check state
        pause_broadcasts();
        await_payload_state_ready(&db.pool).await?;
        retry(intent_id, &hash, &sealed).await
    }
    other => other?,
}

Prevention

When it happens

Trigger: Persisting a sealed transaction while `execution_payload_state.operation` is not 'ready' (e.g. 'activating', 'rotating', 'sealed' during a key-rotation procedure) or while `protocol_version` differs from the binary's expected `EXECUTION_PAYLOAD_PROTOCOL_VERSION`.

Common situations: A key rotation or protection activation was started but not completed (operation left non-ready after an interrupted procedure); an older/newer binary with a different EXECUTION_PAYLOAD_PROTOCOL_VERSION writing to the same database; manual DB edits to execution_payload_state during troubleshooting.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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