diem/diem · error
Transaction with empty write set should be discarded.
Error message
Transaction with empty write set should be discarded.
What it means
process_vm_outputs requires every transaction the Move VM told the executor to Keep (commit) to actually mutate state; a Keep status with an empty write set is treated as an invariant violation and triggers this error. The executor deliberately refuses to commit such transactions because a committed transaction must produce a new state root and a non-empty TransactionInfo.
Source
Thrown at execution/executor/src/lib.rs:323
.collect(),
&proof_reader,
)
.expect("Failed to update state tree.");
for ((vm_output, txn), ((state_tree_hash, new_node_hashes), blobs)) in itertools::zip_eq(
itertools::zip_eq(vm_outputs.into_iter(), transactions.iter()).take(transaction_count),
itertools::zip_eq(roots_with_node_hashes, txn_blobs),
) {
let event_tree = {
let event_hashes: Vec<_> =
vm_output.events().iter().map(CryptoHash::hash).collect();
InMemoryAccumulator::<EventAccumulatorHasher>::from_leaves(&event_hashes)
};
let mut txn_info_hash = None;
match vm_output.status() {
TransactionStatus::Keep(status) => {
ensure!(
!vm_output.write_set().is_empty(),
"Transaction with empty write set should be discarded.",
);
// Compute hash for the TransactionInfo object. We need the hash of the
// transaction itself, the state root hash as well as the event root hash.
let txn_info = TransactionInfo::new(
txn.hash(),
state_tree_hash,
event_tree.root_hash(),
vm_output.gas_used(),
status.clone(),
);
let real_txn_info_hash = txn_info.hash();
txn_info_hashes.push(real_txn_info_hash);
txn_info_hash = Some(real_txn_info_hash);
}
TransactionStatus::Discard(status) => {View on GitHub (pinned to fc4714a8ea)
Solutions
- Fix the transaction itself so it performs a state write if it should be committed (non-trivial script/program), or change its semantics to be discarded.
- Check the VM adapter: if the transaction is legitimately a no-op, the adapter should map it to TransactionStatus::Discard instead of Keep before calling process_vm_outputs.
- If this comes from replaying a synced chunk, verify the upstream output data is not corrupted/truncated (write set lost during serialization).
- Ensure the executor and VM versions are consistent — rebuild/redeploy both from the same revision.
Example fix
// before: adapter marks a no-op transaction as Keep
Ok(VMStatus::Executed) // output.write_set() empty
// after: discard no-op outputs instead of committing them
if output.write_set().is_empty() {
return Ok(TransactionStatus::Discard(DiscountedVMStatus::MiscellaneousError));
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before feeding VM outputs into the commit pipeline, ensure Keep outputs write state.
for output in &vm_outputs {
if matches!(output.status(), TransactionStatus::Keep(_))
&& output.write_set().is_empty() {
anyhow::bail!("output marked Keep has empty write set; adapter bug?");
}
} Type guard
fn committable(output: &VMOutput) -> bool {
!matches!(output.status(), TransactionStatus::Keep(_))
|| !output.write_set().is_empty()
} Try / catch
match executor.execute_chunk(txns, proof, &ledger_info) {
Err(e) if e.to_string().contains("empty write set") => {
// treat as adapter/VM bug: drop the offending tx, alert, do not retry blindly
report_invariant_violation(&e);
Err(e)
}
other => other,
} Prevention
- Ensure the VM adapter maps no-op transactions to Discard, not Keep.
- Keep VM and executor on matching versions; gas/status changes can alter write sets.
- Add unit tests asserting every Keep output has a non-empty write set.
- Never hand-construct VMOutput values in tests without a write set when status is Keep.
When it happens
Trigger: The Move VM returns TransactionStatus::Keep but vm_output.write_set() is empty — typically caused by a buggy adapter/script producing a committed-but-no-op output, or a malicious/incorrect VM output being fed into process_vm_outputs during chunk execution.
Common situations: Custom or modified Move VM adapters/transaction pipelines in tests producing empty outputs; a VM version mismatch (upgraded VM no longer emits writes for a transaction that previously did); mis-wired execution path passing outputs from discarded or metadata transactions as regular Keep transactions.
Related errors
- an error occurred when executing the transaction, vm status
- Failed to verify genesis
- Unable to verify that the new tree extends the parent: {0}
- Invalid EpochChangeProof: {0}
- Invalid proposal: {0}
AI-assisted analysis of diem/diem@fc4714a8ea (2026-09-04).
Data as JSON: /api/errors/2daf8b0a0d80d4d1.
Report an issue: GitHub.