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
- Complete or re-run the payload protection activation/rotation procedure so `operation` returns to 'ready' and `protocol_version` matches the binary
- Check `SELECT protocol_version, operation FROM execution_payload_state WHERE component = 'signed_transactions'` to see the actual state and diagnose which condition failed
- Deploy a binary whose EXECUTION_PAYLOAD_PROTOCOL_VERSION matches the value stored in the database (or migrate the state with the deployment tooling)
- 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
- Ensure activation/rotation procedures always finalize operation back to 'ready', including on crash recovery
- Deploy binaries whose EXECUTION_PAYLOAD_PROTOCOL_VERSION matches the database state; gate startup on a version check
- Never hand-edit execution_payload_state; use the deployment tooling for all state changes
- Serialize key rotation with broadcast workers so no sealed persistence happens mid-rotation
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
- Execution payload storage is not rolling back
- Execution intent {intent_id} is not prepared for nonce {nonc
- Replacement scan conflicts with the canonical nonce or manif
- Active replacement scan intent was not found
- Replacement scan cursor regressed or changed
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/42c5b9f4fbbba03b.
Report an issue: GitHub.