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.

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)

Solutions

  1. Find the offending row by id (from the message) and confirm both `raw_transaction` and `sealed_transaction` are populated.
  2. Verify the sealed envelope decrypts to the same plaintext (`raw_transaction`), then clear `raw_transaction` (set it NULL) so the rewrap can proceed.
  3. Identify and stop the code path writing plaintext into `raw_transaction` (old version, fallback writer, ETL) before re-running the rewrap.
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/9946be7b1d4af734. Report an issue: GitHub.