nautechsystems/nautilus_trader · error · anyhow::Error
Execution payload {} contains both representations during ro
Error message
Execution payload {} contains both representations during rollback What it means
During a rollback batch, each row selected for conversion (sealed_transaction present) must not already contain a raw_transaction — a row holding both representations simultaneously is an invariant breach of the single-representation rule enforced while protection is active. The code asserts raw_transaction IS NONE per row before unsealing, and aborts the whole transaction naming the offending row id when both representations coexist.
Source
Thrown at crates/adapters/blockchain/src/cache/database.rs:5691
for statement in [
"DROP TRIGGER IF EXISTS execution_transaction_payload_fence ON execution_transaction_hash",
"DELETE FROM execution_payload_state WHERE component = 'signed_transactions'",
"DELETE FROM execution_schema_version WHERE component = 'evm_execution_payload'",
] {
sqlx::query(statement)
.execute(&mut *transaction)
.await
.context("failed to complete execution payload rollback")?;
}
transaction
.commit()
.await
.context("failed to commit execution payload rollback completion")?;
return Ok(true);
}
for hash in rows {
anyhow::ensure!(
hash.raw_transaction.is_none(),
"Execution payload {} contains both representations during rollback",
hash.id
);
let envelope = hash
.sealed_transaction
.as_deref()
.expect("rollback query requires envelope");
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())?;
let result = sqlx::query(
"UPDATE execution_transaction_hash SET raw_transaction = $2, updated_at = NOW() \
WHERE id = $1 AND raw_transaction IS NULL AND sealed_transaction = $3",
)
.bind(hash.id)
.bind(&raw_transaction)View on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the offending row (id from the message) and decide which representation is authoritative; typically keep raw_transaction and set sealed_transaction = NULL.
- Remove the duplicate representation with a targeted UPDATE so the row holds only one payload, then rerun the rollback.
- Audit for other rows with both columns set (WHERE raw_transaction IS NOT NULL AND sealed_transaction IS NOT NULL) and fix them all before retrying.
- Ensure no script bypasses the library's two-step UPDATE with its own writes; always roll back through rollback_execution_payload.
Example fix
// before: row with both representations aborts rollback // -- execution_transaction_hash: raw_transaction='0xabc...', sealed_transaction='0xsealed...' // after: clear the duplicate representation, then retry // UPDATE execution_transaction_hash // SET sealed_transaction = NULL, updated_at = NOW() // WHERE id = $offending_id AND raw_transaction IS NOT NULL;
Defensive patterns
Strategy: validation
Validate before calling
let dupes: Vec<i64> = sqlx::query_scalar(
"SELECT id FROM execution_transaction_hash \
WHERE raw_transaction IS NOT NULL AND sealed_transaction IS NOT NULL",
).fetch_all(&mut conn).await?;
if !dupes.is_empty() {
return Err(anyhow!("rows hold both payload representations: {:?}", dupes));
} Try / catch
match db.rollback_execution_payload(&keys, batch).await {
Err(e) if e.to_string().contains("both representations") => {
// parse row id from message, clear sealed_transaction for that row, retry
},
other => other?,
} Prevention
- Keep the payload fence trigger active outside of controlled maintenance windows.
- Perform any payload repair via the library API, not ad-hoc SQL.
- Check for both-representation rows after any crash during seal/rollback operations.
- Keep backups of the table per-representation, never merged.
When it happens
Trigger: A row in execution_transaction_hash has both raw_transaction and sealed_transaction set while payload_expected=true and the rollback batch selects it. This happens if a previous partially-failed rollback committed the raw write but not the envelope clear in a non-atomic way, or if manual edits/other tooling wrote both columns.
Common situations: Crash/recovery between a manual replay of the two UPDATE steps; custom maintenance scripts writing raw_transaction without clearing sealed_transaction; restored rows from backups with both columns populated; bypassing the app's fence trigger with direct SQL updates.
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
- Execution payload rollback left {invalid} invalid row(s)
- Execution payload storage is in {operation} maintenance, not
- Unprotected execution payload storage contains {sealed_rows}
- Execution payload rewrap left {remaining} row(s)
- Execution payload {} has no envelope during rewrap
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/028016c0b11ed4ec.
Report an issue: GitHub.