nautechsystems/nautilus_trader · error

Execution payload storage is in {operation} maintenance, not

Error message

Execution payload storage is in {operation} maintenance, not ready for rollback

What it means

begin_execution_payload_rollback converts protected payload storage back to its unprotected representation. It accepts state.operation of 'ready' (start a new rollback) or 'rollback' (resume an interrupted one); any other maintenance value (e.g. 'migrate' or 'rewrap') makes rollback unsafe because payloads are in another transitional representation. The library bails so operators cannot interleave conflicting maintenance modes.

Source

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

            "ready" => {
                sqlx::query(
                    "ALTER TABLE execution_transaction_hash \
                     DROP CONSTRAINT IF EXISTS execution_transaction_payload_protected_check",
                )
                .execute(&mut *transaction)
                .await
                .context("failed to open execution payload rollback representation")?;
                sqlx::query(
                    "UPDATE execution_payload_state \
                     SET operation = 'rollback', progress_id = 0, updated_at = NOW() \
                     WHERE component = 'signed_transactions'",
                )
                .execute(&mut *transaction)
                .await
                .context("failed to record execution payload rollback state")?;
            }
            "rollback" => {}
            operation => anyhow::bail!(
                "Execution payload storage is in {operation} maintenance, not ready for rollback"
            ),
        }
        transaction
            .commit()
            .await
            .context("failed to commit execution payload rollback state")?;
        Ok(())
    }

    async fn rollback_execution_payload_batch(
        &self,
        keys: &PayloadKeySet,
        batch_size: i64,
    ) -> anyhow::Result<bool> {
        let mut transaction = self
            .pool
            .begin()

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Finish the current operation first: drain 'migrate' batches to ready or complete the 'rewrap' rotation, then start the rollback
  2. If the goal is to abort a rotation, use the appropriate rotation tooling to finish/cancel it so state returns to 'ready' before rollback
  3. Check SELECT operation FROM execution_payload_state to confirm the phase and coordinate with whoever/whatever started it
  4. Schedule protection changes in a single serialized maintenance window

Example fix

// before: rollback during active rewrap
begin_execution_payload_rollback(&keys).await?;
// after: let rotation finish, then roll back
finish_rewrap_to_ready(&keys).await?; // operation == 'ready'
begin_execution_payload_rollback(&keys).await?;
Defensive patterns

Strategy: validation

Validate before calling

let op: String = sqlx::query_scalar(
    "SELECT operation FROM execution_payload_state WHERE component = 'signed_transactions'")
    .fetch_one(&pool).await?;
anyhow::ensure!(matches!(op.as_str(), "ready" | "rollback"),
    "cannot roll back while storage operation is '{op}'");

Type guard

fn rollback_allowed(operation: &str) -> bool {
    matches!(operation, "ready" | "rollback")
}

Try / catch

match db.begin_execution_payload_rollback(&keys).await {
    Err(e) if e.to_string().contains("not ready for rollback") => {
        // let migrate/rewrap finish to 'ready', then retry rollback
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling the rollback command (begin_execution_payload_rollback) while execution_payload_state.operation is 'migrate' or 'rewrap' — for example trying to disable protection while the initial encryption migration is still running, or while an active key rotation is in progress.

Common situations: Operator attempted to roll back protection before the initial migration finished; a key rotation was started by another process and rollback was issued concurrently; resume scripts invoked rollback while rotation tooling still held the state; misread state table led to ordering mistakes.

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/3c0f5ecb5559d664. Report an issue: GitHub.