nautechsystems/nautilus_trader · error · anyhow::Error

Protected execution transaction {} contains plaintext

Error message

Protected execution transaction {} contains plaintext

What it means

When payload-protection keys and a deployment id are available, the loader treats execution transactions as protected: rows must store only sealed envelopes, never plaintext signed bytes. This ensure! fires when a row's raw_transaction column is non-NULL even though protection is active, aborting the load to avoid exposing plaintext signing material.

Source

Thrown at crates/adapters/blockchain/src/cache/database.rs:5225

            for hash in &rows {
                cursor = hash.id;
                plaintext_rows += u64::from(hash.raw_transaction.is_some());
                if !hash.payload_expected {
                    replacement_rows += 1;
                    anyhow::ensure!(
                        hash.raw_transaction.is_none() && hash.sealed_transaction.is_none(),
                        "Replacement execution transaction {} retains signed bytes",
                        hash.id
                    );
                    continue;
                }
                original_rows += 1;
                let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;
                let raw_transaction = if let (Some(keys), Some(deployment_id)) =
                    (keys, deployment_id.as_deref())
                {
                    anyhow::ensure!(
                        hash.raw_transaction.is_none(),
                        "Protected execution transaction {} contains plaintext",
                        hash.id
                    );
                    let envelope = hash.sealed_transaction.as_deref().ok_or_else(|| {
                        anyhow::anyhow!(
                            "Protected execution transaction {} has no envelope",
                            hash.id
                        )
                    })?;
                    let key_id = envelope_key_id(envelope)?;
                    anyhow::ensure!(
                        keys.contains_key(&key_id),
                        "Execution transaction {} requires an unavailable payload key",
                        hash.id
                    );
                    key_ids.insert(alloy::hex::encode(key_id));
                    let context = payload_context(&intent, hash, deployment_id)?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Run rewrap_execution_payload_storage with the correct PayloadKeySet to seal the remaining plaintext rows into envelopes.
  2. Verify the deployment_id supplied to the load matches the deployment_id recorded in execution_payload_state.
  3. For rows that should remain legacy (no protection), ensure keys/deployment_id are not passed so the legacy branch is taken instead.
  4. Check that payload protection setup (begin_execution_payload_rewrap) completed fully before loading.

Example fix

// Load before rewrap (protection active, row still plaintext) -> error
load_execution_transactions(&pool, Some(&keys), Some(deployment_id)).await?;
// after: complete rewrap first
rewrap.rewrap_execution_payload_storage(&keys, 500).await?;
load_execution_transactions(&pool, Some(&keys), Some(deployment_id)).await?;
Defensive patterns

Strategy: validation

Validate before calling

-- Rows still plaintext while protection is active
SELECT id FROM execution_transactions
WHERE raw_transaction IS NOT NULL
  AND (SELECT deployment_id FROM execution_payload_state WHERE component='signed_transactions') IS NOT NULL;

Try / catch

match load_result {
    Err(e) if e.to_string().contains("contains plaintext") => {
        run_payload_rewrap(&keys).await?;
        retry_load().await
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling the execution-transaction load path with keys and deployment_id supplied while the scanned row still has a non-NULL raw_transaction (e.g. a row written before protection was enabled and never rewrapped).

Common situations: Protection was recently enabled but the rewrap of legacy plaintext rows did not complete; rows inserted by an older node version; keys configured for the wrong deployment so rewrap skipped them.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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