{"record":{"id":"9357bbf7e9709977","repo":"nautechsystems/nautilus_trader","slug":"rollback-query-requires-envelope","errorCode":null,"errorMessage":"rollback query requires envelope","messagePattern":"rollback query requires envelope","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":5699,"sourceCode":"                    .context(\"failed to complete execution payload rollback\")?;\n            }\n            transaction\n                .commit()\n                .await\n                .context(\"failed to commit execution payload rollback completion\")?;\n            return Ok(true);\n        }\n\n        for hash in rows {\n            anyhow::ensure!(\n                hash.raw_transaction.is_none(),\n                \"Execution payload {} contains both representations during rollback\",\n                hash.id\n            );\n            let envelope = hash\n                .sealed_transaction\n                .as_deref()\n                .expect(\"rollback query requires envelope\");\n            let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;\n            let context = payload_context(&intent, &hash, keys.deployment_id())?;\n            let raw_transaction = keys.unseal(envelope, &context)?;\n            authenticate_retained_payload(&raw_transaction, &intent, &hash, keys.deployment_id())?;\n            let result = sqlx::query(\n                \"UPDATE execution_transaction_hash SET raw_transaction = $2, updated_at = NOW() \\\n                 WHERE id = $1 AND raw_transaction IS NULL AND sealed_transaction = $3\",\n            )\n            .bind(hash.id)\n            .bind(&raw_transaction)\n            .bind(envelope)\n            .execute(&mut *transaction)\n            .await\n            .context(\"failed to recreate plaintext execution payload\")?;\n            anyhow::ensure!(\n                result.rows_affected() == 1,\n                \"Execution payload {} changed during rollback\",\n                hash.id","sourceCodeStart":5681,"sourceCodeEnd":5717,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L5681-L5717","documentation":"During the rollback query path, a transaction-hash row contains both a raw plaintext and a sealed representation, and the code dereferences `sealed_transaction` expecting the sealed envelope — panicking with \"rollback query requires envelope\" if it is `None`. The rollback path decrypts from the sealed envelope, so a missing envelope is treated as an invariant violation.","triggerScenarios":"Calling the rollback query on an `execution_transaction_hash` row whose `sealed_transaction` is NULL (the row was stored plaintext-only), after detecting both representations present in the row logic.","commonSituations":"Rolling back a database where rows were written plaintext-only by an older code path; partial migration leaving rows without sealed envelopes; schema/data written by a version that never sealed payloads.","solutions":["Seal the plaintext payload (or re-run the migration that produces envelopes) before rolling back.","Filter plaintext-only rows and handle them without unsealing in the rollback path.","Backfill `sealed_transaction` for affected rows.","Ensure the rollback code path matches the storage representation of the rows being processed."],"exampleFix":"// before\nlet envelope = hash.sealed_transaction.as_deref()\n    .expect(\"rollback query requires envelope\");\n// after\nlet envelope = match hash.sealed_transaction.as_deref() {\n    Some(env) => env,\n    None => {\n        let raw = hash.raw_transaction.as_deref()\n            .ok_or_else(|| anyhow!(\"row {} has neither payload form\", hash.id))?;\n        // proceed using the plaintext form directly\n        raw\n    }\n};","handlingStrategy":"validation","validationCode":"if hash.sealed_transaction.is_none() {\n    // handle as plaintext-only row, or seal it before rollback\n}","typeGuard":"fn has_envelope(h: &ExecutionTransactionHashRow) -> bool { h.sealed_transaction.is_some() }","tryCatchPattern":"let Some(envelope) = hash.sealed_transaction.as_deref() else {\n    return Err(anyhow!(\"row {} stored plaintext-only; seal before rollback\", hash.id));\n};","preventionTips":["Ensure the sealing migration completes before running rollback queries.","Detect and handle plaintext-only rows explicitly in rollback code.","Audit payload representation columns before schema rollbacks."],"tags":["database","rollback","panic","null-value"],"backgroundTag":"null-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}