nautechsystems/nautilus_trader · error
migration query requires plaintext
Error message
migration query requires plaintext
What it means
During the migration query path, a transaction-hash row contains both a plaintext `raw_transaction` and a sealed payload, and the code dereferences the plaintext field expecting it to be present — panicking with "migration query requires plaintext" if it is `None`. The migration path only supports rows stored as plaintext, so a missing raw transaction is treated as a programmer/data invariant violation.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:4958
self.complete_execution_payload_migration(&mut transaction, keys)
.await?;
transaction
.commit()
.await
.context("failed to commit execution payload migration completion")?;
return Ok(true);
}
for hash in rows {
anyhow::ensure!(
hash.sealed_transaction.is_none(),
"Execution transaction {} contains both plaintext and sealed payloads",
hash.id
);
let raw_transaction = hash
.raw_transaction
.as_deref()
.expect("migration query requires plaintext");
let intent = load_execution_intent(&mut transaction, hash.intent_id).await?;
let context = authenticate_retained_payload(
raw_transaction,
&intent,
&hash,
keys.deployment_id(),
)
.with_context(|| format!("failed to authenticate execution payload {}", hash.id))?;
reserve_execution_payload_seal(&mut transaction, keys.active_key_id()).await?;
let envelope = keys.seal(raw_transaction, &context)?;
let unsealed = keys.unseal(&envelope, &context)?;
authenticate_retained_payload(&unsealed, &intent, &hash, keys.deployment_id())?;
anyhow::ensure!(
unsealed == raw_transaction,
"Execution payload {} changed during seal round trip",
hash.id
);
let result = sqlx::query(View on GitHub (pinned to 18893faf8b)
Solutions
- Unseal the sealed payload into plaintext (with `keys.unseal`) before running the migration path.
- Re-run the migration only on rows stored in plaintext; filter sealed-only rows out beforehand.
- Restore or backfill `raw_transaction` for the affected rows.
- Check that the migration code path matches the storage representation of your rows (plaintext vs sealed).
Example fix
// before
let raw_transaction = hash.raw_transaction.as_deref()
.expect("migration query requires plaintext");
// after
let raw_transaction = match hash.raw_transaction.as_deref() {
Some(raw) => raw,
None => {
let envelope = hash.sealed_transaction.as_deref()
.ok_or_else(|| anyhow!("row {} has neither payload form", hash.id))?;
&keys.unseal(envelope, &payload_context(&intent, &hash, keys.deployment_id())?)?
}
}; Defensive patterns
Strategy: validation
Validate before calling
if hash.raw_transaction.is_none() {
// unseal from sealed_transaction first, or skip this row in the migration
} Type guard
fn has_plaintext(h: &ExecutionTransactionHashRow) -> bool { h.raw_transaction.is_some() } Try / catch
let Some(raw) = hash.raw_transaction.as_deref() else {
return Err(anyhow!("row {} stored sealed-only; unseal before migration", hash.id));
}; Prevention
- Run migrations only on rows matching the expected storage representation.
- Backfill/unseal sealed rows into plaintext before plaintext-requiring migrations.
- Audit schema state (raw vs sealed columns) before running migration queries.
When it happens
Trigger: Calling the migration query on an `execution_transaction_hash` row whose `raw_transaction` is NULL (the row was stored sealed-only), after the code has already flagged the both-representations conflict.
Common situations: Migrating a database where rows were written by a newer sealed-payload code path; mixed old/new schema rows; running the migration against data written by a version that sealed payloads without keeping plaintext.
Related errors
- rollback query requires envelope
- Implement FromRow for FuturesSpread
- Implement FromRow for OptionSpread
- Failed to add pool event block hash storage: {e}
- Missing block timestamp for pool snapshot {} at block {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ad6546b3d4fa3626.
Report an issue: GitHub.