nautechsystems/nautilus_trader · error · anyhow::Error
Legacy execution transaction {} contains an envelope
Error message
Legacy execution transaction {} contains an envelope What it means
In the legacy load path (no payload-protection keys or no deployment id), rows must contain plaintext raw transactions only. This ensure! fires when a row holds a sealed envelope, which the legacy path cannot decrypt — indicating the row was sealed while the caller is loading without protection keys.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:5246
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)?;
keys.unseal(envelope, &context)?
} else {
anyhow::ensure!(
hash.sealed_transaction.is_none(),
"Legacy execution transaction {} contains an envelope",
hash.id
);
hash.raw_transaction.clone().ok_or_else(|| {
anyhow::anyhow!("Legacy execution transaction {} has no plaintext", hash.id)
})?
};
authenticate_retained_payload(
&raw_transaction,
&intent,
hash,
deployment_id.as_deref().unwrap_or(""),
)?;
if let Some(policy) = policy
&& retained_payload_requires_policy(&intent, hash, policy)?
{View on GitHub (pinned to 18893faf8b)
Solutions
- Configure and pass the payload keys plus deployment_id so the protected branch runs and can unseal the envelope.
- Upgrade the loading component to the version that supports envelope payload protection.
- Check that the deployment-id environment/config value is set and matches execution_payload_state.deployment_id.
- If legacy mode is intentional, this database is incompatible — migrate or rewrap, or point the loader at the correct database.
Example fix
// before: legacy load against a protected database
load_execution_transactions(&pool, None, None).await?;
// after: supply keys + deployment id
let deployment_id = std::env::var("EXECUTION_PAYLOAD_DEPLOYMENT_ID")?;
load_execution_transactions(&pool, Some(&keys), Some(&deployment_id)).await?; Defensive patterns
Strategy: validation
Validate before calling
// Detect protected rows before choosing legacy load
let has_state = sqlx::query_scalar::<_, bool>(
"SELECT EXISTS(SELECT 1 FROM execution_payload_state WHERE component='signed_transactions')"
).fetch_one(&pool).await?;
assert!(!has_state, "database is protected; supply keys + deployment_id"); Prevention
- Propagate payload key config (keys + deployment id) to every component that reads this database.
- Fail fast at startup if execution_payload_state exists but keys are unconfigured.
- Keep loader and writer versions in lockstep regarding payload protection.
When it happens
Trigger: Calling the execution-transaction load with keys=None or deployment_id=None while the scanned row has a non-NULL sealed_transaction column.
Common situations: Deployment misconfiguration: payload protection is enabled in the database but the loader's keys/deployment_id were not passed (env var or config not set on this node); loading a protected database with an unprotected legacy code path or old software version.
Understand the failure class
Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.
Related errors
- Execution payload storage is in {operation} maintenance; com
- Execution payload protection is active, but no payload key i
- Execution payload storage is in {operation} maintenance, not
- Execution payload storage is in {operation} maintenance, not
- Protected execution transaction {} contains plaintext
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/b7e609ba6a8717c8.
Report an issue: GitHub.