nautechsystems/nautilus_trader · critical · anyhow::Error
Plaintext signed transaction persistence is disabled after p
Error message
Plaintext signed transaction persistence is disabled after payload activation
What it means
After execution payload protection has been activated (an execution_schema_version marker row exists for the component), the database no longer accepts plaintext (raw, unsealed) signed-transaction payloads. This guard prevents a caller from bypassing encryption and writing raw transaction bytes once protected storage is enabled.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:6647
let state = execution_payload_state_from_row(&state_row)?;
anyhow::ensure!(
state.protocol_version == EXECUTION_PAYLOAD_PROTOCOL_VERSION
&& state.operation == "ready",
"Execution payload storage is not ready for protected persistence"
);
anyhow::ensure!(
envelope_key_id(envelope)?.as_slice() == state.active_key_id.as_slice(),
"Signed transaction envelope does not use the database active key"
);
} else {
let marker = sqlx::query_scalar::<_, bool>(
"SELECT EXISTS (SELECT 1 FROM execution_schema_version WHERE component = $1)",
)
.bind(EXECUTION_PAYLOAD_COMPONENT)
.fetch_one(&mut *transaction)
.await
.context("failed to inspect execution payload marker")?;
anyhow::ensure!(
!marker,
"Plaintext signed transaction persistence is disabled after payload activation"
);
}
let current_status = sqlx::query_scalar::<_, String>(
"SELECT status FROM execution_intent WHERE id = $1 FOR UPDATE",
)
.bind(intent_id)
.fetch_optional(&mut *transaction)
.await
.map_err(|e| anyhow::anyhow!("Failed to lock execution intent {intent_id}: {e}"))?
.ok_or_else(|| anyhow::anyhow!("Execution intent {intent_id} was not found"))?;
anyhow::ensure!(
current_status == TransactionStatus::Prepared.as_str()
|| current_status == TransactionStatus::Signed.as_str(),
"Execution intent {intent_id} is {current_status}, not prepared for signing"
);
View on GitHub (pinned to 18893faf8b)
Solutions
- Upgrade/restore the sealing path so the payload is passed as sealed_transaction instead of raw_transaction.
- If plaintext mode is genuinely required, revert the payload activation (remove the execution_schema_version marker) via the supported migration procedure — only in a maintenance window.
- Ensure all writer instances run the same code version with payload protection enabled.
- Check deployment configuration so the sealing flag matches the database's activated state.
Example fix
// before add_execution_transaction_payload(intent, chain, hash, Some(raw_bytes), None).await?; // after let sealed = seal(payload, active_key)?; add_execution_transaction_payload(intent, chain, hash, None, Some(sealed)).await?;
Defensive patterns
Strategy: validation
Validate before calling
let activated: bool = sqlx::query_scalar("SELECT EXISTS (SELECT 1 FROM execution_schema_version WHERE component=$1)").bind(component).fetch_one(&pool).await?;
anyhow::ensure!(!(activated && sealed_transaction.is_none()), "payload protection active: must pass sealed_transaction"); Try / catch
match result {
Err(e) if e.to_string().contains("Plaintext signed transaction persistence is disabled") => {
error!("writer lacks payload sealing; upgrade node or enable sealing config before retrying");
}
r => r?,
} Prevention
- Keep all writer nodes on the same version as the database's payload-activation state
- Gate the raw-transaction code path behind a config check that queries the DB marker at startup
- Test rollbacks: never deploy a pre-activation writer against an activated database
- Seal by default; treat raw persistence as a legacy-only mode
When it happens
Trigger: Calling add_execution_transaction_payload with raw_transaction=Some (sealed_transaction=None) on a database where the execution_schema_version marker for EXECUTION_PAYLOAD_COMPONENT already exists — i.e. after payload-activation/encryption was enabled.
Common situations: Running an older node version that still writes raw payloads against a database migrated to protected storage; a rollback of code after activation; a config flag turning off sealing while the DB marker remains enabled.
Related errors
- Signed transaction envelope does not use the database active
- 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
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5c01e5da476f1676.
Report an issue: GitHub.