nautechsystems/nautilus_trader · error · anyhow::Error
Execution payload contains plaintext during rewrap
Error message
Execution payload {} contains plaintext during rewrap What it means
During rewrap, a row must hold its payload exclusively in sealed form: `raw_transaction` (plaintext) must be NULL. This `anyhow::ensure!` fires when a row selected for rewrap still carries a plaintext `raw_transaction`, which would mean rewrapping the envelope could desynchronize the two copies or leak the plaintext the encryption scheme is meant to remove. The library refuses to rewrap such rows.
Solutions
- Find the offending row by id (from the message) and confirm both `raw_transaction` and `sealed_transaction` are populated.
- Verify the sealed envelope decrypts to the same plaintext (`raw_transaction`), then clear `raw_transaction` (set it NULL) so the rewrap can proceed.
- Identify and stop the code path writing plaintext into `raw_transaction` (old version, fallback writer, ETL) before re-running the rewrap.
- If the plaintext row cannot be trusted, re-create the payload from the transaction source and seal it under the active key, then clear the plaintext column.
Example fix
-- before: plaintext retained alongside sealed envelope SELECT id, raw_transaction, sealed_transaction FROM execution_transaction_hash WHERE id = <id>; -- after: verify envelope matches, then clear plaintext so rewrap can proceed UPDATE execution_transaction_hash SET raw_transaction = NULL, updated_at = NOW() WHERE id = <id>;
Defensive patterns
Strategy: validation
Validate before calling
let plaintext_rows: Vec<i64> = sqlx::query_scalar("SELECT id FROM execution_transaction_hash WHERE payload_expected AND raw_transaction IS NOT NULL")
.fetch_all(&pool).await?;
if !plaintext_rows.is_empty() {
// verify sealed envelopes decrypt to the plaintext, then clear raw_transaction before rewrap
} Try / catch
match rewrap_result {
Err(e) if e.to_string().contains("contains plaintext during rewrap") => {
// parse row id, verify envelope vs plaintext, clear raw_transaction, retry rewrap
},
other => other?,
} Prevention
- Deprecate/remove any code path that writes `raw_transaction` alongside sealed envelopes.
- After a failed rewrap, check whether the plaintext-clearing step ran; re-run remediation before retrying.
- Add a periodic audit query for payload_expected rows with non-NULL raw_transaction and alert immediately (plaintext retention risk).
- Validate restored/pre-encryption-era rows and seal-or-strip plaintext before admitting them into the table.
When it happens
Trigger: A row in the rewrap batch has both `payload_expected=true` and a non-NULL `raw_transaction` — e.g. a writer stored the plaintext transaction after the rewrap began (post-lock race), a prior migration step that clears `raw_transaction` after sealing was interrupted, or manual data manipulation left plaintext in place.
Common situations: An older adapter version or fallback path that wrote plaintext `raw_transaction` while `sealed_transaction` was also populated; a partially completed earlier rewrap where the plaintext-clearing UPDATE never ran; manual imports/backfills that set `raw_transaction` directly; restoring rows from a pre-encryption-era backup.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Execution payload has no envelope during rewrap
- Execution payload rewrap left
- Duplicate fill event for position
- Execution payload changed during rewrap
- Execution payload contains both representations during…
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9946be7b1d4af734.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/blockchain/src/cache/database.rs:5495
)
.execute(&mut *transaction)
.await
.context("failed to mark execution payload rewrap complete")?;
transaction
.commit()
.await
.context("failed to commit execution payload rewrap completion")?;
return Ok(true);
}
for hash in rows {
let envelope = hash.sealed_transaction.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"Execution payload {} has no envelope during rewrap",
hash.id
)
})?;
anyhow::ensure!(
hash.raw_transaction.is_none(),
"Execution payload {} contains plaintext during rewrap",
hash.id
);
let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;
let context = payload_context(&intent, &hash, keys.deployment_id())?;
let raw_transaction = keys.unseal(envelope, &context)?;
authenticate_retained_payload(&raw_transaction, &intent, &hash, keys.deployment_id())?;
reserve_execution_payload_seal(&mut transaction, keys.active_key_id()).await?;
let rewrapped = keys.seal(&raw_transaction, &context)?;
let verified = keys.unseal(&rewrapped, &context)?;
authenticate_retained_payload(&verified, &intent, &hash, keys.deployment_id())?;
anyhow::ensure!(
verified == raw_transaction,
"Execution payload {} changed during rewrap",
hash.id
);
let result = sqlx::query(View on GitHub (pinned to 18893faf8b)