{"record":{"id":"42c5b9f4fbbba03b","repo":"nautechsystems/nautilus_trader","slug":"execution-payload-storage-is-not-ready-for-protect","errorCode":null,"errorMessage":"Execution payload storage is not ready for protected persistence","messagePattern":"Execution payload storage is not ready for protected persistence","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":6630,"sourceCode":"        );\n        let chain_id_db = i32::try_from(chain_id)\n            .with_context(|| format!(\"Chain ID {chain_id} exceeds PostgreSQL INTEGER\"))?;\n        let mut transaction =\n            self.pool.begin().await.map_err(|e| {\n                anyhow::anyhow!(\"Failed to start signed transaction persistence: {e}\")\n            })?;\n\n        if let Some(envelope) = sealed_transaction {\n            let state_row = sqlx::query(\n                \"SELECT deployment_id, protocol_version, operation, active_key_id \\\n                 FROM execution_payload_state WHERE component = 'signed_transactions' FOR SHARE\",\n            )\n            .fetch_optional(&mut *transaction)\n            .await\n            .context(\"failed to lock execution payload state for protected persistence\")?\n            .ok_or_else(|| anyhow::anyhow!(\"Execution payload protection is not active\"))?;\n            let state = execution_payload_state_from_row(&state_row)?;\n            anyhow::ensure!(\n                state.protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION\n                    && state.operation == \"ready\",\n                \"Execution payload storage is not ready for protected persistence\"\n            );\n            anyhow::ensure!(\n                envelope_key_id(envelope)?.as_slice() == state.active_key_id.as_slice(),\n                \"Signed transaction envelope does not use the database active key\"\n            );\n        } else {\n            let marker = sqlx::query_scalar::<_, bool>(\n                \"SELECT EXISTS (SELECT 1 FROM execution_schema_version WHERE component = $1)\",\n            )\n            .bind(EXECUTION_PAYLOAD_COMPONENT)\n            .fetch_one(&mut *transaction)\n            .await\n            .context(\"failed to inspect execution payload marker\")?;\n            anyhow::ensure!(\n                !marker,","sourceCodeStart":6612,"sourceCodeEnd":6648,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L6612-L6648","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let (protocol_version, operation): (i64, String) = sqlx::query_as(\n    \"SELECT protocol_version, operation FROM execution_payload_state WHERE component = 'signed_transactions'\",\n).fetch_one(&db.pool).await?;\nanyhow::ensure!(\n    protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION && operation == \"ready\",\n    \"payload state not ready (v{protocol_version}, op={operation})\"\n);","typeGuard":null,"tryCatchPattern":"match db.add_execution_transaction_envelope(intent_id, chain_id, &hash, &sealed).await {\n    Err(e) if e.to_string().contains(\"not ready for protected persistence\") => {\n        // pause broadcasting until rotation/activation completes, then re-check state\n        pause_broadcasts();\n        await_payload_state_ready(&db.pool).await?;\n        retry(intent_id, &hash, &sealed).await\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["database","state-machine","version-mismatch","envelope-encryption"],"backgroundTag":"invalid-state-transition","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}