For the complete documentation index, see llms.txt. This page is also available as Markdown.

replay

Fetch and re-execute one or many on-chain transactions with optional overrides, tracing, and on-chain receipt verification.

Re-execute historical transactions locally using an RPC endpoint or a previously captured fixture file. In online mode, mega-evme fetches the transaction, block environment, and pre-state from the RPC and re-executes locally. In offline mode (--rpc.replay-file), all data is served from a local fixture captured by an earlier run — no network access is required.

replay has two modes. The single-transaction mode replays the transaction named by the positional TX_HASH and supports the full option set (overrides, tracing, state dumps, fixture dumps). Batch mode (--tx-file / --block) replays many transactions in one process and reports one summary per transaction.

Usage

mega-evme replay [OPTIONS] <TX_HASH|--tx-file <PATH>|--block <N>>

Exactly one replay target is required: the positional TX_HASH, --tx-file, or --block. The three are mutually exclusive.

Arguments

TX_HASH

The transaction hash to replay (32-byte hex).

mega-evme re-executes the transaction locally using state and block context sourced from either an RPC endpoint or a local fixture file. This gives you a fully reproducible execution without needing a local archive node.

The transaction lookup's own block number and inclusion hash are classified first, before any block is fetched. Both present is a mined target; neither present is a pending one. The two mixed shapes cannot be replayed at all and are reported as RPC failures (exit 3) from the metadata alone, so no fetch precedes the verdict and no later failure can mask it: a block number without an inclusion hash is an unanchored view, since the number alone cannot anchor the replay to a block body, and an inclusion hash without a block number is contradictory metadata, since the hash proves inclusion while the missing number denies it.

Resolving a mined transaction then takes two more calls — the block the lookup reports and that block's parent — which a reorg in progress or a load-balanced endpoint can answer from different views of the chain. Replaying a mixed view yields a plausible but wrong result, so the answers are checked against each other and a disagreement is reported as an RPC failure (exit 3) instead of being replayed: the parent block must be the replayed block's parent, the fetched block must be the one the transaction was resolved as included in, and that block's body must list the transaction — its position there is what defines the preceding transactions replayed ahead of it.

A pending transaction has no such pair, since its state base is the latest block, which is also the block it is replayed in. That block is fetched once and fills both roles, so the two cannot disagree.

--rpc <URL>

Aliases: --rpc-url

The RPC endpoint to fetch the transaction and state from. Any standard Ethereum JSON-RPC provider works here. Required for online replay and capture mode; omit when using --rpc.replay-file.

Batch Replay

Replaying a corpus of transactions one process at a time pays for provider construction, chain-id resolution, and RPC cache parsing once per transaction — work that dominates the actual EVM execution. Batch mode does all of it once.

A batch run builds a single provider and a single RPC cache, groups the requested transactions by their containing block, and processes the blocks in ascending order. Each block is executed exactly once: state is forked at the parent block, pre-execution changes are applied, and every transaction of the block runs in order, with each requested transaction's result recorded before it is committed. A capture file (--rpc.capture-file) is persisted once, on exit, even if some transactions failed — the captured responses are the artifact you need to debug the failure offline. The per-chain on-disk RPC cache is opt-in for batch runs: it is loaded and persisted only when --rpc.cache-dir names a directory explicitly, or when --rpc.clear-cache asks for the cache file to be deleted. A batch scan walks linear history whose request keys essentially never repeat across runs, so a shared cache file buys almost no hits, while its clean-exit re-read-merge-rewrite grows with the file and serializes concurrent processes on the persist lock. The in-memory cache still serves every repeated request within the run.

--rpc.clear-cache counts as an explicit opt-in because deleting the cache file only means something while the disk cache is engaged: a batch run that forced the cache off would parse the flag, do nothing, and leave the polluted file in place for the next run. With it, the cache file (at the default path, or under --rpc.cache-dir) is deleted under the sidecar lock, the run starts from an empty cache, and the cache is persisted on exit. An explicit --rpc.no-cache-file still wins over both flags and keeps the disk cache off, exactly as in single-transaction mode.

A plain batch replay issues the same RPC calls as single-transaction replay, so an offline envelope captured by single-transaction runs serves a batch run without a cache miss. --verify-receipt and --dump-fixture-dir are the exception: both fetch the receipt of every target in the block, including transactions a single-transaction capture never asked about, so an older envelope will miss them and the run exits 3.

--tx-file <PATH>

Replay every transaction hash listed in <PATH>, one per line.

Blank lines and lines whose first non-whitespace character is # are ignored. A hash listed more than once is replayed once. A line that is not a valid 32-byte hex hash aborts the run before any network access, naming the offending line number.

--block <N>

Replay every transaction of block N, given in decimal or 0x-prefixed hex.

Restrictions

Batch mode reports one summary per transaction and has no meaningful semantics for single-file fixture dumps, tracing, state dumps, or what-if knobs, so the following are rejected up front with an explanatory error rather than silently ignored:

  • --dump-fixture — use --dump-fixture-dir for batch sedimentation

  • Transaction overrides (--override.gas-limit, --override.value, --override.input, --override.input-file)

  • --override.spec — each block's spec is auto-detected from its timestamp

  • All trace options (--trace, --trace.output, --tracer, --trace.*)

  • All state dump options (--dump, --dump.output)

Single-transaction replay keeps accepting all of them. Batch mode additionally accepts --dump-fixture-dir for per-target fixture sedimentation.

Output

With --json, batch mode writes NDJSON: exactly one compact, single-line JSON object per requested transaction, in processing order (ascending block, then transaction index).

A transaction that executed is reported as its tx_hash, block_number, and tx_index, followed by the same fields the single-transaction JSON output carries (success, gas_used, logs_count, and the optional output / contract_address / revert_reason / halt_reason) and its receipt. In every replay mode a creation's receipt.contractAddress is the address it targeted whether or not it deployed, as the on-chain receipt reports it, while contract_address appears only for a creation that actually deployed. Both shapes below are expanded for readability; on the wire each object occupies exactly one line.

A transaction that could not be executed is reported as an error entry instead:

kind is one of not_found (unknown hash), pending (mined into no block yet), rpc (an RPC call failed), or execution (block setup or the block executor rejected the transaction). Execution outcomes are not errors: a reverted or halted transaction is a normal result line with success: false.

A failure while running the block aborts it, because the executor state no longer matches the chain. The transaction the failure is about — the hash the endpoint denied, or the one the executor rejected — is reported with that failure's own kind. Every target behind it is reported as rpc with a message naming the aborting cause: nothing was established about those transactions, so they went unanswered rather than being unknown. Targets that never ran are still emitted in the block's transaction-index order, keeping the whole stream in ascending (block, tx_index) order; a hash the endpoint claimed for this block but that the body does not list is reported last within its block, in input order, as rpc (an unanswered, divergent view — not a definitive unknown hash). Hashes that could not be resolved to a block at all (unknown as not_found, pending, or an endpoint failure during resolution) are emitted before every block result, since the run cannot place them in the stream's order.

Without --json, each transaction is printed with a header naming its hash, block, and index, followed by the same summary and receipt the single-transaction mode prints. A final one-line summary (transactions replayed, transactions failed, elapsed time) is logged at INFO level, so pass -vvv to see it.

With --verify-receipt, each result line additionally carries a verification object. With --dump-fixture-dir, each result line additionally carries a fixture object (path, skipped, or error). With --verify-block, the block's lines are followed by one block line carrying the block's verdict.

Exit Status

A batch run exits 0 when every requested transaction produced an execution result and nothing the run was asked to do failed, and non-zero otherwise — see Exit codes for how the failure classes are ranked. Fixture skips (fidelity mismatch, BLOCKHASH readers, unsupported shapes) are not failures and do not fail the run; a fixture construction or write failure is an execution-class failure of its target; an unanswered on-chain receipt for the fidelity gate is an rpc-class failure of its target. When a mid-block abort discards a drafted fixture, that fixture error inherits the abort's class (so a transport abort still exits 3). The NDJSON stream is written to stdout in both cases; diagnostics go to stderr.

Swept targets behind an abort always report as rpc ("unanswered"). When the aborting transaction is not itself a target, the run still tallies the abort's own class so the process exit reflects the root cause — a non-target executor abort exits 1, a transport abort exits 3. --block 0 is rejected as invalid input (exit 1): the user asked for a genesis block that cannot be replayed. An endpoint that resolves a transaction hash into block 0 is contradictory endpoint data instead — each such target is reported as rpc and the run exits 3, in the same family as unanchored views and contradictory metadata. A block that genuinely holds no transactions produces no stdout lines, exits 0, and says so on stderr.

Examples

Replay a whole block offline and stream the results as NDJSON:

Replay a corpus of transactions against a live RPC, one process for the lot:

Where corpus.txt looks like:

Count the transactions that did not succeed:

A failed run ends its stdout with a run-level {"error": …} object (see Exit codes), which carries no tx_hash, so selecting on .tx_hash keeps the count to per-transaction lines.

Receipt Verification

Replaying a transaction only proves that the local EVM produced some result; equivalence verification needs that result checked against what the chain recorded. --verify-receipt builds that check into the tool: it fetches the on-chain receipt of every replayed transaction and compares it against the receipt the replay produced, so verifying an upgrade is one command over one transaction list instead of a replay run plus a separate receipt-diffing pipeline.

--verify-receipt

Verify every replayed transaction against its on-chain receipt. Supported in both single-transaction and batch mode.

Every field of the consensus receipt encoding is compared, plus the transaction's own gas:

  • Status — the success flag.

  • Gas used — the transaction's own gas.

  • Cumulative gas used — the block's gas up to and including this transaction, which a preceding transaction that used different gas shifts even when this one's own gas agrees.

  • Receipt type — the EIP-2718 type of the receipt envelope.

  • Deposit nonce and deposit receipt version — the two fields a deposit receipt adds; absent (null) on any other type.

  • Logs — the number of logs, and each log's address, topics, and data.

Logs are compared explicitly rather than inferred from gas: LOG gas depends on topic count and data length, never on content, so two executions can burn identical gas yet emit different log payloads. The logs bloom is not compared as a dimension of its own: it is a function of the logs, an on-chain receipt is only admitted when its bloom is the bloom of its own logs (see below), and the replay's bloom is built from its logs, so equal logs mean equal blooms. The fields an RPC receipt adds beside the consensus encoding — contractAddress, effectiveGasPrice, and the L1 fee fields — are not compared.

The receipt is fetched with the same call the fixture dump uses, so a run with --rpc.capture-file records it and a later --rpc.replay-file run verifies the same transaction offline. An envelope captured without --verify-receipt (or by any earlier run that never needed a receipt) holds no receipts, so verifying against it fails the receipt fetch — capture once online with the flag, then re-verify offline as often as you like.

Verified, Unverified, and Mismatched

A transaction is only reported as mismatched when both receipts were compared and disagreed. In every mode, a fetched receipt is admitted only after it is checked against the request and against itself — it describes the requested transaction, its inclusion is the replayed block, and its bloom is the bloom of its logs; the remaining fields are then compared as served. Anything that prevents the comparison from running is an infrastructure failure — the transaction is unverified, which is a different finding from a divergence:

  • The endpoint fails the receipt call, or has pruned the receipt below its retention height (common on non-archive endpoints): reported as an rpc failure.

  • The receipt contradicts itself: its logsBloom is not the bloom of the logs it carries (a corrupted backend, or a tampered capture): reported as an rpc failure, because no execution produced that receipt, and its logs could otherwise match while its bloom is forged.

  • The receipt describes a different inclusion than the replayed block (its blockHash differs from the replayed block, or is null — a reorg in progress, or a load-balanced endpoint serving divergent views): reported as an rpc failure, because comparing against it would compare the replay to the wrong on-chain execution, and a receipt with no inclusion hash cannot be anchored at all.

  • The receipt describes a different transaction than the one requested (its transactionHash is not the hash the receipt was asked for — an inconsistent endpoint, or a tampered capture): reported as an rpc failure, because the verdict would describe the wrong transaction, and two transactions sharing their consensus facts would even yield a spurious match.

  • The target is a pending transaction, which has no receipt yet: rejected up front in single-transaction mode, and reported as a pending error entry in batch mode.

Each of those checks answers one receipt's own question, so a receipt rewritten under its own transaction hash and inclusion would pass them all. When a batch run has fetched and admitted the receipt of every transaction of a block (a --block run asks for all of them), the receipts are therefore also authenticated as a set: their consensus encodings, in body order, must rebuild the receipts root the block's authenticated header commits to. That root covers each receipt's cumulative gas but not the gasUsed an RPC receipt reports beside it, so each gasUsed must also equal the rise in cumulative gas over the receipt before it (over zero for the first). A set that fails either check is endpoint data the block does not commit to, so every receipt of that block is reported as unverified (rpc, exit 3) rather than compared, and no verdict rests on it.

What a verdict rests on therefore depends on the mode:

  • A --block run, or a --tx-file run that lists every transaction of a block, compares receipts the header commits to: their consensus fields through the receipts root, and their gasUsed through the cumulative gas.

  • A single-transaction run, or a --tx-file run that covers only part of a block, cannot rebuild the root: each receipt passes the per-receipt admission checks above, and its consensus fields and its gasUsed are then compared as served.

In batch mode, when a target already produced an execution result and only the receipt fetch failed, the target keeps its full result line (execution summary, local receipt, timing) and reports the failure on that line as "verification": {"error": "…"}. The target still counts as replayed; the unanswered receipt is tallied as rpc and the run exits 3. A target that never reached execution (pending, not-found, block setup failure) remains a bare error entry, exactly like any other infrastructure failure before replay.

Transaction overrides and --override.spec are still accepted with --verify-receipt, but they make the replay a what-if that the chain never executed, so the comparison will normally report a mismatch.

Output

With --json, the verdict is a verification object — added to the single-transaction summary, and to each batch result line. The field is absent entirely without the flag.

A match carries nothing else:

An unanswered receipt (fetch failed, pruned, reorg / divergent inclusion) carries only the error — no match field, so it is never confused with a divergence:

A mismatch carries a diff holding only the dimensions that disagreed, each as {"onchain": …, "replay": …}. The keys are status, gas_used, cumulative_gas_used, tx_type, deposit_nonce, deposit_receipt_version, and logs; a deposit field is null on a side whose receipt is not a deposit.

Under logs, count is present when the two sides emitted a different number of logs, and first_mismatch names the first log both sides emitted whose contents differ — its field is address, topics, or data, and the two values are that field's contents on each side. Both can appear at once, which distinguishes truncated logs from rewritten ones.

Without --json, each transaction gets one verdict line after its usual output:

The mismatch line names every dimension that disagreed, comma-separated, under the same keys as the JSON diff (logs_count and logs[i].<field> for the two log findings), and prints an absent deposit field as none. The failed line is used when the comparison never ran.

Exit Status

A run in which every target replayed and every verification matched exits 0. A verification mismatch exits 2 through a dedicated error (Receipt verification mismatch: N of M verified transaction(s) did not reproduce the on-chain receipt), reported after every result line has been written. Infrastructure failures keep their own exit code and take precedence in a batch run: a target that never replayed was also never verified, so reporting it as a mismatch would overstate what the run found. An execution or input failure exits 1, an RPC failure (including a receipt the endpoint cannot serve) exits 3. See Exit codes for the full taxonomy and the batch precedence rule.

Examples

Verify one transaction against a live RPC:

Verify a whole corpus in one process and collect the divergences:

Receipt-fetch failures on a target that still replayed live under .verification.error on the result line (the line keeps receipt / success); infrastructure failures that prevented execution live under .error. Both selectors require .tx_hash so that the run-level {"error": …} object a failed run appends to stdout is not counted as an unverified transaction.

Capture once online, then re-verify the same corpus offline:

Block Verification

Receipt verification checks each transaction against the receipt an endpoint serves for it. Block verification checks the replayed block as a whole against the commitments its header carries, so a whole-block replay can be judged by the header alone, without trusting the served receipts.

--verify-block

Verify the replayed block against its header. Only valid with --block <N>: a single-transaction replay stops at its target and a --tx-file replay stops at each block's last listed target, so neither executes the block its header describes. Both are rejected before anything is fetched, with exit 1. The flag is independent of --verify-receipt: either can be given alone, or both together.

The served body is checked first, before the block executes. Every listed transaction is fetched and authenticated against its body-listed hash, and the ordered trie over their EIP-2718 encodings, in listing order, must rebuild the header's transactions root. That root depends only on what the endpoint served, never on execution, so a body that does not rebuild it is not a divergence of the replay: the endpoint served a body the header does not commit to (a listing with a transaction left out, added, or reordered under an authentic header, whose hash does not cover the listing). The verdict is then the error shape, the block counts as an RPC failure, and nothing executes: every transaction of the block is reported as an RPC failure with the block. Checking the body first keeps a forgery that would stop execution — a transaction left out, so that the next one from its sender is a nonce ahead — from being reported as an execution failure. A listed transaction that cannot be fetched or authenticated leaves the body unchecked; the block then executes as it would without the flag, and that transaction's failure is reported as usual.

Only for a body the header commits to are the execution commitments the replayed block produces compared against the header:

  • Receipts root — the ordered trie over the EIP-2718 encodings of the receipts the replay produced.

  • Logs bloom — the union of those receipts' blooms.

  • Gas used and blob gas used — as the block executor accounted them.

  • Requests hash — the EIP-7685 commitment: a header that carries a requestsHash must carry the hash of the requests the replay produced, and a header without one must come from a block that produced none.

The header compared against is the one the replay already authenticated: its hash is recomputed from its own fields before anything reads it, and it links to the parent block the state was forked from. A match therefore shows that the replay reproduces the execution outputs committed to by a header that is self-consistent and linked to its parent. Whether that header is the canonical one is not something the replay can establish on its own: the caller establishes it by pinning the block hash the run reports against a source it trusts.

The state root and the withdrawals root are not compared. MegaETH commits to its state in a SALT trie rather than a Merkle-Patricia trie, and a replay over forked RPC state — online or from a capture — holds no proofs to rebuild either root from.

Block verification issues no RPC call of its own: it fetches the body's transactions before the block runs rather than while it runs, each once, so a capture recorded by a plain --block run verifies offline.

An empty listing is still a claim about the block, so --verify-block does not let it pass silently: where a plain --block run reports that the block holds no transactions and exits 0, a verifying run emits a block line for it. Nothing executes; the header must link to its parent and commit to the empty body, and the execution commitments are those of an empty body — the empty receipts root, an empty bloom, zero gas, zero blob gas, and no requests, since the block executor produces receipts, gas and requests only for transactions. On MegaETH every block starts with its L1 attributes deposit, so an empty listing under an authentic header is an RPC failure.

Output

With --json, the block's target lines are followed by one block line (expanded here for readability; on the wire it occupies one line). It carries no tx_hash, which is how a consumer tells it from a target line, and its verdict sits under block_verification rather than verification, so a filter over the target lines' verdicts never matches it:

The verdict has the same three shapes as a receipt verdict: {"match": true}, {"match": false, "diff": {…}}, or {"error": "…"} when the comparison could not run. A mismatch's diff holds only the commitments that disagreed, each as {"onchain": …, "replay": …}, where onchain is the header's value. The keys are receipts_root, logs_bloom, gas_used, blob_gas_used, and requests_hash; the last two are null on a side that lacks the field.

A replay that burns different gas than the chain moves both the block's gas used and its receipts root. A body the header does not commit to is never a mismatch; it carries the error shape, for example:

Without --json, the verdict is printed under a block heading after the block's transactions:

The mismatch line names every commitment that disagreed, comma-separated (a bloom only as logs_bloom differs), and the failed line says why the comparison never ran.

Exit Status

A block that does not reproduce its header is a verification mismatch, like a receipt mismatch: a run whose only findings are mismatches exits 2, and an execution or RPC failure still takes precedence. The final message names every dimension that diverged, for example Block verification mismatch: 1 of 1 verified block(s) did not reproduce the block header, or both the receipts and the block when both were verified and both diverged.

A block whose served body the header does not commit to — or, for an empty listing, whose parent the endpoint does not serve or link — is an RPC failure of the block itself, since no transaction line carries it: the run exits 3 (unless an execution failure outranks it), and its message names the block, for example 1 block(s) could not be verified against the block header.

When the block did not execute in full — a transaction aborted the walk, a setup step failed, or the block could not be finished — there is nothing to compare. The block line then carries the error shape and is not counted; the transactions' own lines carry the failure class that decides the exit.

Examples

Verify a block against its header and its receipts, and keep only the block verdict:

Capture once online, then re-verify offline:

RPC Cache File

mega-evme replay supports a transport-level JSON-RPC fixture mechanism that records every request/response pair to a single file and serves them back on later runs without touching the network. It is useful for pinning a reproducible replay (e.g. for regression tests, debugging sessions, or offline review) and for running replay in environments that cannot reach the RPC endpoint.

The generic RPC Cache records the same request/response pairs in the same envelope format, and the capture file differs from it in three ways: it lives at a path you choose rather than per chain under the platform cache directory, it holds the whole recorded conversation rather than a bounded number of entries, and it keeps the answers that are only true for the moment they were taken — the chain tip, block-tag reads, still-pending transaction metadata — which the per-chain cache drops so a later run cannot inherit that moment. Keeping them is what lets an offline rerun reproduce a pending transaction. Neither cache records batched JSON-RPC requests; they are forwarded as-is.

The mechanism has two modes, selected by two mutually exclusive flags.

--rpc.capture-file <PATH>

Capture mode. Requires --rpc.

On the first run, every JSON-RPC request and response issued while serving the replay is captured in memory and the complete envelope is written to <PATH> atomically on clean exit. On subsequent runs the existing file is loaded, its entries are merged into the in-memory transport cache, and only missing requests are fetched from the RPC endpoint. The updated set of entries is persisted back to the same file on clean exit.

The file also embeds an external-environment snapshot — currently the set of --bucket-capacity values in effect — so the captured fixture is self-contained. If --bucket-capacity is not passed on a subsequent run, the previous envelope's values are reused; passing --bucket-capacity overrides them (an intentional A→B refresh of an existing capture is accepted at persist when no concurrent writer changed the on-disk snapshot, and a run that reused the previous values yields to a concurrent refresh rather than conflicting with it; only two writers changing the same snapshot differently hard-errors, naming the load-time, caller, and on-disk values — see state management).

The capture is written even when the replay itself failed — an execution or verification failure is exactly the case you want to debug offline. If the write fails, it is reported on stderr like any other failure, next to the run's own error; the run error keeps the exit code, since it is the root cause.

--rpc.capture-file is mutually exclusive with --rpc.replay-file, --rpc.cache-dir, --rpc.clear-cache, --rpc.no-cache-file, and --rpc.cache-max-entries.

--rpc.replay-file <PATH>

Replay mode. Requires neither --rpc nor network access.

The envelope at <PATH> is loaded and serves as the only source of JSON-RPC responses. Any request that is not present in the fixture aborts the run with a hard error — there is no fall-through to a live RPC endpoint.

Bucket-capacity data is read from the fixture envelope, so --bucket-capacity is neither required nor accepted with --rpc.replay-file. Passing --bucket-capacity together with --rpc.replay-file is rejected; to regenerate a fixture with new capacities, re-run in capture mode.

--rpc.replay-file is mutually exclusive with --rpc, --rpc.capture-file, --rpc.cache-dir, --rpc.clear-cache, --rpc.no-cache-file, and --rpc.cache-max-entries.

Examples

Capture a transaction's RPC traffic to a fixture file (first run hits the endpoint, later runs with the same flag reuse and top up the file):

Replay the captured transaction fully offline:

Self-Validating Fixture Dump

--dump-fixture <FILE> turns a single replay into a self-validating regression fixture. The fixture is written in the same EEST state-test schema that the state-test runner consumes (TestUnit { env, pre, transaction, post, out }), so re-running it through state-test re-executes the transaction and checks the result — no RPC access required.

--dump-fixture <FILE>

Dump a self-validating fixture for the replayed transaction to <FILE>.

The fixture captures everything needed to deterministically re-execute the target transaction in isolation:

  • pre — the pre-state read closure (every account and storage slot the transaction touched), with their pre-transaction values.

  • env — the block environment (number, timestamp, coinbase, base fee, gas limit, prevrandao, excess blob gas).

  • transaction — the target transaction.

  • megaEnv — a MegaETH-specific extension carrying the SALT bucket capacities and oracle storage in effect, so dynamic storage-gas pricing reproduces exactly. Pure-Ethereum fixtures omit this field.

  • post — the expected result for the executed spec: state-root and logs-root, plus explicit megaGasUsed and megaStatus expectations that produce readable diffs on mismatch.

The post expectation is computed by the state-test runner itself — the exact code path that later validates the fixture — so a dumped fixture is self-consistent by construction.

Fidelity gate. Before building the fixture, the dump fetches the transaction's on-chain receipt and requires the local replay to reproduce the receipt's gasUsed, success status, and logs root exactly. Logs are compared explicitly rather than inferred from gas: LOG gas depends on topic count and data length, never content, so two executions can burn identical gas yet emit different log payloads. A mismatch aborts the dump (no file is written) with a clear error — this catches a wrong spec or hardfork config, which self-validation alone cannot, because the fixture is validated under the same spec it was dumped with. A refused dump still prints the replay result first. With --verify-receipt as well, a mismatch is reported as the verification diff and exits 2, and the refused fixture is reported on stderr beside it. It then additionally cross-checks the isolated execution against the full replay (gas, status — including the exact halt reason for halted transactions — output, and logs root), so any gas-, output-, or log-visible divergence — including across the L1 data fee, which the isolated run zeroes but the full replay charges — aborts the dump. One channel stays open by construction: the isolated run's sender balance is shifted by the zeroed L1 fee, so a contract that stores a balance-derived value bakes that shifted value into post (and the sender's final balance in post likewise differs from the chain). The fixture still self-validates and reproduces gas exactly; only such balance-derived state values differ.

--dump-fixture cannot be combined with transaction overrides or --override.spec (a forced spec would record a what-if, not the on-chain transaction), and deposit transactions are not supported. A target transaction that reads a block hash via BLOCKHASH is also rejected: fixtures carry no historical block hashes, so the isolated re-execution could not reproduce the values the replay observed. Block hash reads by preceding transactions in the same block do not matter — only the target transaction's reads are checked. Because the fidelity gate reads the receipt, an offline dump (--rpc.replay-file) requires the receipt to be present in the capture — so capture and dump together in the online run, then re-dump offline reproducibly. A receipt the endpoint does not serve — no receipt at all, one describing a different inclusion than the replayed block, or one describing a different transaction than the one requested — is classified exactly as under --verify-receipt: an RPC failure (exit 3), because the question went unanswered rather than answered no. When combined with --rpc.capture-file, the capture file is written even if execution or the fidelity gate fails, so the captured RPC responses remain available for debugging the failure offline.

--dump-fixture-dir <DIR>

Batch-only. Dump a self-validating fixture for every successfully replayed target into <DIR>/<tx_hash>.json. The fixture content and format match the single-transaction --dump-fixture path (same EEST schema, same sorted megaEnv, same self-validation via state-test). The directory is created if it does not exist. Existing files are refused unless --overwrite is also set — a refused overwrite is a failed dump for that target, not a skip.

Per-target gating mirrors the single-transaction rules, but records a skip instead of failing the run. The fixture draft is built against the pre-commit state (same moment as the single-transaction dump) and only written after the block finishes successfully — a commit-time rejection or finish failure never creates or replaces a fixture file.

Gate
Outcome

On-chain receipt unavailable (not in capture, pruned, reorg/divergent inclusion, receipt for another transaction)

fixture.error with the reason; rpc-class failure

Fidelity mismatch (gas / status / logs root)

fixture.skipped with fidelity gate failed: …

Target reads BLOCKHASH

fixture.skipped (fixtures carry no historical block hashes)

Unsupported shape (deposit, EIP-7702, unknown spec mapping)

fixture.skipped

Fixture construction failure (database / pre-state reads)

fixture.error with the reason; execution-class failure

Finalize / write / self-validation failure, refused overwrite

fixture.error with the reason; execution-class failure

Pending / unresolvable target

already an error entry; no fixture report

BLOCKHASH access is isolated per transaction: the access record is cleared before each transaction of the block, so preceding readers do not poison a later target's dump.

NDJSON result lines gain "fixture": {"path": "…"}, "fixture": {"skipped": "<reason>"}, or "fixture": {"error": "<reason>"}. Human mode prints one fixture line per target. An end-of-run INFO summary reports written / skipped / failed counts.

A failed dump is reported on the target's own result line rather than replacing it: the transaction did replay, so its receipt — and, with --verify-receipt, its verdict — is still what the run was asked for, and a divergence found on such a target is still counted as a mismatch. Fixture skips do not fail the run; a failed dump does — as an execution-class failure for construction/write failures, or as an rpc-class failure when the on-chain receipt question went unanswered.

Registration into bench/replay/manifest.json is not performed — corpus curation stays manual. --dump-fixture-dir cannot be combined with --dump-fixture, and is rejected in single-transaction mode.

Throughput Benchmark

To benchmark a replayed transaction, dump it to a fixture and time the fixture with the state-test runner — there is no replay-side benchmark flag:

state-test --bench reports min / median / mean time and throughput (Mgas/s), timing only the EVM transact call. A committed corpus of characteristic transactions and a base-vs-PR comparison driver build on this to track real-transaction throughput across changes; see bench/replay/ and the replay-bench CI workflow.

Spec Auto-Detection

The EVM spec controls which opcodes, gas rules, and MegaETH-specific behaviors are active during execution. replay auto-detects the spec from the chain ID and the block timestamp of the replayed transaction. Hardcoded hardfork configs exist for:

  • Chain 6343 — MegaETH testnet v2

  • Chain 4326 — MegaETH mainnet

For any other chain, replay enables every hardfork up to a pinned spec at genesis — currently Rex7. That pin does not follow the newest spec automatically: introducing a spec leaves unrecognized chains where they are, so a replay of history they already produced keeps its meaning. Use --override.spec below to replay such a chain under a different spec.

--override.spec <SPEC>

Override the auto-detected spec. Useful when you want to test how the transaction would behave under a different spec, or when replaying against a chain that isn't recognized.

The override replaces the entire execution world, not just the EVM semantics. The block is executed as if it had run on a chain whose schedule activates the forced spec at genesis: the pre-block system contract deploys, the EIP-2935 and EIP-4788 pre-block calls, the block-level resource limits, and the EVM semantics all come from the forced spec. This keeps a forced replay coherent — mixing the historical setup with forced semantics would execute a world that never existed on any chain.

A consequence worth stating explicitly: replaying an old block under a newer spec installs predeploys that did not exist at that block (for example, forcing Rex5 on a pre-Rex5 block deploys the SequencerRegistry), and forcing an older spec withholds predeploys the block did have, or installs an earlier version of them. That is intentional — it is what "how would this transaction behave under spec X" means. The replayed state therefore diverges from the chain's historical state by construction, so --verify-receipt will normally report a mismatch and --dump-fixture is rejected outright.

The forced spec does not synthesize chain configuration. Per-fork parameters (currently the SequencerRegistry seeds a chain publishes for Rex5 and Rex6) are taken from the chain's own configuration, so a fork the chain has not configured cannot be forced: the run fails before executing, naming the missing parameters, rather than proceeding with an invented value.

Transaction Overrides

Override flags let you modify the transaction before re-executing it. The original on-chain transaction is unchanged; only the local replay is affected. This is useful for "what-if" testing: what happens with less gas, different calldata, or a different ETH value?

Flag
Aliases
Description

--override.gas-limit <GAS>

--override.gaslimit

Replace the transaction's gas limit

--override.value <VALUE>

Replace the ETH value sent (supports 1ether, 100gwei suffixes)

--override.input <HEX>

--override.data

Replace the calldata with a hex-encoded byte string

--override.input-file <FILE>

--override.data-file

Replace the calldata with the contents of a file

Note that replay does not accept --chain, block environment flags, or pre-state flags. All of that context comes from the RPC.

Options

replay supports the following shared option groups. See the linked pages for full details. Options marked (single transaction only) are rejected in batch mode.

  • Batch replay — Replay many transactions in one process via --tx-file / --block. See Batch Replay above.

  • Receipt verification — Check every replayed transaction against its on-chain receipt via --verify-receipt. See Receipt Verification above.

  • Block verification (--block only) — Check the replayed block against its header via --verify-block. See Block Verification above.

  • SALT buckets — Configure SALT bucket capacity for dynamic storage gas pricing. See SALT Buckets.

  • State dump (single transaction only) — Dump or load pre/post-state snapshots. See State Management.

  • RPC cache file — Single-file JSON-RPC capture and offline replay via --rpc.capture-file / --rpc.replay-file. See RPC Cache File above.

  • RPC cache / retry — Per-chain response cache, retry, and rate-limit settings. See RPC Cache and Retry.

  • Tracing (single transaction only) — Emit execution traces (call traces, opcode traces, gas profiles, etc.). See Tracing Overview.

  • Fixture dump — Write a self-validating EEST state-test fixture via --dump-fixture (single transaction) or --dump-fixture-dir (batch). See Self-Validating Fixture Dump above.

  • Throughput benchmark — Dump a fixture (--dump-fixture / --dump-fixture-dir) and time it with state-test --bench. See Throughput Benchmark above.

Examples

In all examples below, replace 0xabc123... with a real transaction hash from MegaETH mainnet. Every replay invocation requires one of --rpc <URL>, --rpc.capture-file <PATH>, or --rpc.replay-file <PATH>.

Replay against a live RPC

Replay with call tracing

Replay with a tighter gas limit

Replay with different calldata

Replay under a specific spec

Replay a whole block as NDJSON

Verify a whole block against its on-chain receipts

See Also

  • run — Execute raw EVM bytecode locally without fetching from RPC

  • tx — Send a new transaction against a local state

  • Tracing Overview

  • SALT Buckets

  • State Management

Last updated