Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
1919daf
feat(mega-evme): rename --rpc.rate-limit to --rpc.cu-per-sec
RealiCZ Aug 3, 2026
c41ff35
feat(mega-evme): replace --rpc.cache-size with --rpc.cache-max-entries
RealiCZ Aug 3, 2026
65d03aa
fix(mega-evme): map cache-max-entries 0 to 1_048_576, not u32::MAX
RealiCZ Aug 3, 2026
836024e
feat(evme): add batch replay via --tx-file and --block
RealiCZ Aug 3, 2026
88ca0a6
feat(mega-evme): concurrent-safe cache persist and cache merge
RealiCZ Aug 3, 2026
903091e
feat(mega-evme): verify replays against on-chain receipts
RealiCZ Aug 3, 2026
31d0f76
feat(mega-evme): batch fixture dump via --dump-fixture-dir
RealiCZ Aug 3, 2026
169b415
feat(mega-evme): centralized exit-code taxonomy and structured failur…
RealiCZ Aug 3, 2026
038d546
fix(mega-evme): cache persist external_env conflict and provider chai…
RealiCZ Aug 3, 2026
a964c1d
fix(mega-evme): batch reporting and exit-path defects
RealiCZ Aug 3, 2026
53e8768
fix(mega-evme): optimistic external_env concurrency, swept kind, pani…
RealiCZ Aug 3, 2026
cd3326d
fix(mega-evme): pass loaded external_env baseline into capture store
RealiCZ Aug 3, 2026
d8c47fd
feat(mega-evme): add --rpc.request-timeout per-HTTP request timeout
RealiCZ Aug 4, 2026
303bb65
docs(mega-evme): fix intra-doc anchor fragments flagged by lychee
RealiCZ Aug 4, 2026
a7b3de6
fix(mega-evme): address five PR #366 review findings on batch replay
RealiCZ Aug 4, 2026
6df81ed
fix(mega-evme): close four pre-push review findings on fixture dump
RealiCZ Aug 4, 2026
00e5468
test(mega-evme): compile the draft-failure injection hook out of prod…
RealiCZ Aug 4, 2026
08ba950
chore(mega-evme): drop a stale duplicate comment block
RealiCZ Aug 4, 2026
ef026e5
Merge remote-tracking branch 'origin/cz/chore/upgrade-revm-40' into r…
RealiCZ Aug 5, 2026
ad7e0b1
fix(mega-evme): keep captured entries when a run left the external_en…
RealiCZ Aug 5, 2026
cdb61f3
refactor(mega-evme): type fixture build rejections instead of matchin…
RealiCZ Aug 5, 2026
eb3e4f7
fix(mega-evme): reject --block 0 and explain an empty block
RealiCZ Aug 5, 2026
0ee09b8
chore(mega-evme): use std file locking instead of fs2
RealiCZ Aug 5, 2026
1f6f83f
docs(mega-evme): correct the exit-site claim and record the cache module
RealiCZ Aug 5, 2026
d9eb30b
test(mega-evme): run the batch driver's multi-target tests in CI
RealiCZ Aug 5, 2026
7059a92
test(mega-evme): resolve every fixture through one helper
RealiCZ Aug 5, 2026
6f1f8b5
fix(mega-evme): keep the merged provider cache within its configured cap
RealiCZ Aug 5, 2026
003c2e2
fix(mega-evme): check tx-file targets against the block that is replayed
RealiCZ Aug 5, 2026
62e1dc0
Merge remote-tracking branch 'origin/cz/chore/upgrade-revm-40' into c…
RealiCZ Aug 11, 2026
36081e7
fix(mega-evme): read all-zero RPC accounts as nonexistent
RealiCZ Aug 11, 2026
409de9a
feat(mega-evme): make the on-disk RPC cache opt-in for batch replay
RealiCZ Aug 11, 2026
a77ed86
refactor(mega-evme): derive DecodedRawTx through FromTxWithEncoded
RealiCZ Aug 11, 2026
6a715c8
fix(mega-evme): reject mined batch targets without inclusion hash
RealiCZ Aug 11, 2026
e1b4a02
fix(mega-evme): validate parent linkage on the single-transaction rep…
RealiCZ Aug 11, 2026
80359dc
fix(mega-evme): treat block-body null tx lookup as RPC inconsistency
RealiCZ Aug 11, 2026
c3342f3
docs(mega-evme): remove stray doc-comment fragment in batch tests
RealiCZ Aug 11, 2026
10f3714
fix(mega-evme): make panic hook writes non-panicking on closed pipes
RealiCZ Aug 11, 2026
a6f8884
test(mega-evme): pin build_pre_state absent-means-nonexistent shape
RealiCZ Aug 11, 2026
fcb0bea
test(mega-evme): cover typed raw-tx envelope decoding
RealiCZ Aug 11, 2026
f5ebec6
fix(mega-evme): classify contradictory null number with hash as RPC
RealiCZ Aug 11, 2026
000911b
feat(mega-evme): make replay --override.spec a coherent what-if
RealiCZ Aug 11, 2026
7ed74d4
docs(mega-evme): scope the batch jq selectors to per-transaction lines
RealiCZ Aug 11, 2026
b3a22a5
test(mega-evme): harden lane test pins against review findings
RealiCZ Aug 11, 2026
153d448
fix(mega-evme): skip caching JSON-RPC null results in capture transport
RealiCZ Aug 11, 2026
d07f08f
fix(mega-evme): make every cache-file write take the sidecar lock
RealiCZ Aug 11, 2026
6c7348f
fix(mega-evme): print cache merge safeguard warnings on stderr
RealiCZ Aug 11, 2026
3c70398
Merge branch 'orch/evme-u4-cache-integrity' into cz/feat/evme-replay-…
RealiCZ Aug 12, 2026
45ea314
fix(mega-evme): classify single-path block-body nulls as rpc
RealiCZ Aug 12, 2026
14aa29e
fix(mega-evme): lock clear-cache and type provider persist reread
RealiCZ Aug 12, 2026
b5b6cfe
fix(mega-evme): anchor the single-path replay to its fetched block
RealiCZ Aug 12, 2026
f55d326
Merge branch 'orch/evme-u7-cache-deltas' into cz/feat/evme-replay-too…
RealiCZ Aug 12, 2026
a48ee02
fix(mega-evme): hold clear-cache lock through load
RealiCZ Aug 12, 2026
a57bbc4
fix(mega-evme): classify single-path target metadata before any fetch
RealiCZ Aug 12, 2026
d75183e
Merge branch 'orch/evme-u6-cmd-deltas' into cz/feat/evme-replay-tooling
RealiCZ Aug 12, 2026
5039318
fix(mega-evme): classify batch inclusion per target, not first-seen
RealiCZ Aug 12, 2026
10eaaae
fix(mega-evme): treat --rpc.clear-cache as a batch disk-cache opt-in
RealiCZ Aug 12, 2026
6b97fc3
fix(mega-evme): tally batch abort root cause and name failing fetches
RealiCZ Aug 12, 2026
6792c52
fix(mega-evme): classify dump-fixture receipt anomalies as RPC failures
RealiCZ Aug 12, 2026
73c5582
fix(mega-evme): batch receipt failures keep results and classify as rpc
RealiCZ Aug 12, 2026
0901653
fix(mega-evme): reject a receipt served for another transaction
RealiCZ Aug 12, 2026
1434eda
fix(mega-evme): keep batch tallies truthful and NDJSON ordered
RealiCZ Aug 12, 2026
72fc86f
Merge branch 'orch/evme-v1-cache-optin' into cz/feat/evme-replay-tooling
RealiCZ Aug 12, 2026
1c4bc11
test(mega-evme): pin the batch wrong-transaction receipt to the keep-…
RealiCZ Aug 12, 2026
e799f80
fix(mega-evme): classify stringified pre-block RPC failures as exit 3
RealiCZ Aug 12, 2026
8a8cad0
fix(mega-evme): anchor stringified RPC classification to cause boundary
RealiCZ Aug 12, 2026
b465ed5
fix(mega-evme): authenticate fetched transactions against the request…
RealiCZ Aug 12, 2026
b9f9bcf
fix(mega-evme): attribute hashless block aborts to the in-flight tran…
RealiCZ Aug 12, 2026
4310b41
fix(mega-evme): classify a null endpoint-resolved block as an RPC fai…
RealiCZ Aug 12, 2026
bd5bedc
fix(mega-evme): classify clear-cache filesystem failures as execution…
RealiCZ Aug 12, 2026
a73715f
Merge branch 'cz/chore/upgrade-revm-40-public' into cz/feat/evme-repl…
RealiCZ Aug 12, 2026
7da1ae1
test(mega-evme): pin the halt-logs receipt regression against mainnet
RealiCZ Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions bin/mega-evme/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# AGENTS.md

## OVERVIEW
CLI toolbox for direct MegaEVM execution (`run`, `tx`, `replay`) with optional forking, tracing, and state dump workflows.
CLI toolbox for direct MegaEVM execution (`run`, `tx`, `replay`, `cache`) with optional forking, tracing, and state dump workflows.

## STRUCTURE
- `src/main.rs`: CLI bootstrap and panic hook.
- `src/cmd.rs`: top-level command dispatch and error surface.
- `src/common/`: shared CLI args, state loading, tracing, tx parsing, output printers.
- `src/run/`: bytecode execution command.
- `src/tx/`: full transaction execution command with raw-tx override support.
- `src/replay/`: RPC-backed historical transaction replay through block executor.
- `src/replay/`: RPC-backed historical transaction replay through block executor, plus the batch driver.
- `src/cache/`: cache-file merge utilities (provider-cache and capture-envelope JSON shapes) backing the `cache merge` subcommand and the lock-protected merge-on-persist, plus the sidecar advisory lock every cache-file writer takes.

## KEY PATTERNS
- Shared argument groups are flattened from `run` argument structs into sibling commands.
Expand All @@ -31,3 +32,6 @@ CLI toolbox for direct MegaEVM execution (`run`, `tx`, `replay`) with optional f
- Change state-forking or prestate merge semantics: `src/common/state.rs`.
- Change replay hardfork/spec selection: `src/replay/{cmd.rs,hardforks.rs}`.
- Change receipt/summary formatting: `src/common/outcome.rs` and printer helpers.
- Change cache merge behavior (CLI or merge-on-persist): `src/cache/{mod.rs,merge.rs}`.
- Change how cache files are locked against concurrent writers: `src/cache/lock.rs` — the one place a cache-file write may acquire its lock, and every caller must fail closed when it cannot.
- Change process exit classification: `src/common/exit.rs` — the single exit site for command results.
90 changes: 90 additions & 0 deletions bin/mega-evme/src/cache/lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! Advisory locking for cache files shared by concurrent processes.
//!
//! Every writer of a cache file — clean-exit persist and the offline
//! `cache merge` subcommand alike — takes the exclusive lock on that file's
//! sidecar before it re-reads the file, merges, and renames the result into
//! place. A writer that skips the lock can only be correct by luck: two
//! read-modify-write cycles that interleave lose whichever side renamed first.
//!
//! The lock lives on a sidecar (`<target>.lock`) rather than the cache file
//! itself because the target is replaced by rename on every write, and a lock
//! held on the replaced inode protects nothing.

use std::{
fs,
fs::OpenOptions,
path::{Path, PathBuf},
};

/// Path of the advisory lock sidecar for `target` (`<target>.lock`).
pub(crate) fn lock_sidecar_path(target: &Path) -> PathBuf {
let mut os = target.as_os_str().to_owned();
os.push(".lock");
PathBuf::from(os)
}

/// RAII exclusive lock on the sidecar file for a cache target.
///
/// The lock is released when this guard is dropped (file handle closed).
/// The sidecar file itself is left on disk.
#[derive(Debug)]
pub(crate) struct ExclusiveFileLock {
_file: fs::File,
}

/// Acquire an exclusive advisory lock on `<target>.lock`, blocking until held.
///
/// The sidecar is created if missing and left in place after unlock.
///
/// Callers must fail closed on `Err`: an unlocked write is exactly the
/// lost-update race the lock exists to prevent, so a failed acquisition means
/// "do not write", never "write anyway".
pub(crate) fn acquire_exclusive_lock(target: &Path) -> std::io::Result<ExclusiveFileLock> {
let lock_path = lock_sidecar_path(target);
if let Some(parent) = lock_path.parent() {
fs::create_dir_all(parent)?;
}
// truncate(false): the sidecar is only a flock target; keep any existing bytes.
let file =
OpenOptions::new().create(true).read(true).write(true).truncate(false).open(&lock_path)?;
// Blocking exclusive advisory lock.
file.lock()?;
Ok(ExclusiveFileLock { _file: file })
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_lock_sidecar_path_suffix() {
let p = Path::new("/tmp/rpc-cache-1.json");
assert_eq!(lock_sidecar_path(p), PathBuf::from("/tmp/rpc-cache-1.json.lock"));
}

/// The sidecar is created on acquisition and left in place after unlock.
#[test]
fn test_acquire_exclusive_lock_creates_and_keeps_sidecar() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("rpc-cache-1.json");
let sidecar = lock_sidecar_path(&target);
assert!(!sidecar.exists());

let guard = acquire_exclusive_lock(&target).expect("acquire");
assert!(sidecar.exists(), "sidecar created while held");
drop(guard);
assert!(sidecar.exists(), "sidecar left in place after unlock");
}

/// An un-openable sidecar path surfaces as an error rather than a silent
/// unlocked write.
#[test]
fn test_acquire_exclusive_lock_reports_unopenable_sidecar() {
let dir = tempfile::tempdir().expect("tempdir");
let target = dir.path().join("rpc-cache-1.json");
// A directory in the sidecar's place cannot be opened as a file.
fs::create_dir(lock_sidecar_path(&target)).expect("occupy sidecar path");

acquire_exclusive_lock(&target).expect_err("un-openable sidecar must not silently succeed");
}
}
Loading
Loading