{"record":{"id":"56ea68165ae43483","repo":"nautechsystems/nautilus_trader","slug":"execution-transaction-contains-both-plaintext-a","errorCode":null,"errorMessage":"Execution transaction {} contains both plaintext and sealed payloads","messagePattern":"Execution transaction (.+?) contains both plaintext and sealed payloads","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/cache/database.rs","lineNumber":4950,"sourceCode":"            \",\n        )\n        .bind(batch_size)\n        .fetch_all(&mut *transaction)\n        .await\n        .context(\"failed to load legacy signed transaction batch\")?;\n\n        if rows.is_empty() {\n            self.complete_execution_payload_migration(&mut transaction, keys)\n                .await?;\n            transaction\n                .commit()\n                .await\n                .context(\"failed to commit execution payload migration completion\")?;\n            return Ok(true);\n        }\n\n        for hash in rows {\n            anyhow::ensure!(\n                hash.sealed_transaction.is_none(),\n                \"Execution transaction {} contains both plaintext and sealed payloads\",\n                hash.id\n            );\n            let raw_transaction = hash\n                .raw_transaction\n                .as_deref()\n                .expect(\"migration query requires plaintext\");\n            let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;\n            let context = authenticate_retained_payload(\n                raw_transaction,\n                &intent,\n                &hash,\n                keys.deployment_id(),\n            )\n            .with_context(|| format!(\"failed to authenticate execution payload {}\", hash.id))?;\n            reserve_execution_payload_seal(&mut transaction, keys.active_key_id()).await?;\n            let envelope = keys.seal(raw_transaction, &context)?;","sourceCodeStart":4932,"sourceCodeEnd":4968,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/cache/database.rs#L4932-L4968","documentation":"While migrating plaintext rows to sealed envelopes, each fetched row must be plaintext-only. A row carrying both raw_transaction and sealed_transaction is ambiguous (it would be double-sealed or hand-modified), so migration aborts citing the offending transaction hash id to protect data integrity.","triggerScenarios":"During migrate_execution_payload_batch, an execution_transaction_hash row is returned with sealed_transaction set even though the query selected pending (plaintext) rows — the row changed concurrently or was written inconsistently.","commonSituations":"Another process sealed the row between selection and processing without proper fencing; manual UPDATEs on execution_transaction_hash; a previous failed migration wrote sealed values without clearing plaintext.","solutions":["Re-run the batch — the fenced transaction should re-select only consistent rows","If the row genuinely holds both payloads, repair it via the library (re-seal from plaintext, clearing the stale sealed value) or restore from backup","Ensure only one migration/sealing process runs at a time against the table","Audit for manual writes to execution_transaction_hash and restrict direct table access"],"exampleFix":"-- find inconsistent rows\nSELECT id FROM execution_transaction_hash\nWHERE raw_transaction IS NOT NULL AND sealed_transaction IS NOT NULL;\n-- repair: clear stale sealed value (only if plaintext is authoritative) then retry migration\nUPDATE execution_transaction_hash SET sealed_transaction = NULL\nWHERE id = <offending_id>;","handlingStrategy":"retry","validationCode":"let bad: i64 = sqlx::query_scalar(\n    \"SELECT COUNT(*) FROM execution_transaction_hash\n     WHERE raw_transaction IS NOT NULL AND sealed_transaction IS NOT NULL\",\n).fetch_one(&pool).await?;\nif bad > 0 { anyhow::bail!(\"{bad} rows hold both plaintext and sealed payloads; repair before migrating\"); }","typeGuard":null,"tryCatchPattern":"match db.migrate_execution_payload_batch(&keys, 500).await {\n    Ok(done) => { /* continue batches */ }\n    Err(e) if e.to_string().contains(\"both plaintext and sealed\") => {\n        // extract hash id from message, repair the row, resume migration\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Seal rows exclusively through the library's fenced migration/seal path","Disable direct writes to execution_transaction_hash in production","Run one migration worker at a time","After any failed migration, audit for rows with both payloads populated"],"tags":["data-integrity","migration","encryption"],"backgroundTag":"internal-invariant-violation","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"}