nautechsystems/nautilus_trader · error
Execution payload storage is in
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.
Solutions
- Finish the current operation first: drain 'migrate' batches to ready or complete the 'rewrap' rotation, then start the rollback
- If the goal is to abort a rotation, use the appropriate rotation tooling to finish/cancel it so state returns to 'ready' before rollback
- Check SELECT operation FROM execution_payload_state to confirm the phase and coordinate with whoever/whatever started it
- 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
- Wait for migrations/rotations to reach 'ready' before initiating rollback
- Use an advisory lock so rollback cannot interleave with rotation tooling
- Monitor execution_payload_state for non-ready operations older than expected
- Plan protection removal as the final step of a serialized maintenance window
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
- Execution payload storage is in
- Execution payload storage is in
- Execution payload contains both representations during…
- Execution payload contains plaintext during rewrap
- Execution payload has no envelope during rewrap
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/3c0f5ecb5559d664.
Report an issue: GitHub.
Appendix: 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)