Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions crates/pipe-exec-layer-ext-v2/execute/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,29 @@ alloy-genesis.workspace = true
alloy-rpc-types-trace.workspace = true
reth-trie = { workspace = true, features = ["test-utils"] }
ureq = { version = "2", features = ["json"] }
# difftx_fuzz:交易序列 property/fuzz 生成器(仅测试期)。
proptest = "1"

[features]
pipe_test = ["reth-provider/pipe_test"]
config-from-env = ["gravity-primitives/config-from-env"]
# 差分预言机(tx_filter ⊇ revm):编译 difftx 模块与 difftx 工具二进制。
difftx = []
# serial ⟷ grevm 执行差分:编译 difftx_exec 模块与 difftx_exec 工具二进制。
difftx_exec = ["difftx"]

[[bench]]
name = "bls_pop_verify_bench"
harness = false

# 差分预言机命令行工具:跑 revm 变体覆盖矩阵 + 7702 执行诱导 TOCTOU 自检。
[[bin]]
name = "difftx"
path = "src/bin/difftx.rs"
required-features = ["difftx"]

# serial(revm) ⟷ grevm 执行差分预言机命令行工具:跑 baseline+adversarial 块,对比双后端。
[[bin]]
name = "difftx_exec"
path = "src/bin/difftx_exec.rs"
required-features = ["difftx_exec"]
108 changes: 108 additions & 0 deletions crates/pipe-exec-layer-ext-v2/execute/src/bin/difftx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! 差分测试预言机命令行入口。
//!
//! 运行整个 revm 变体矩阵,打印人读表格 + 完整 JSON;对每个停链缺口(gap)在
//! `difftx-repro/` 目录写一个 gnode `send` 兼容的复现文件。有 gap 退出码 3,否则 0。
//!
//! 必须带 `--features difftx` 构建/运行:
//! ```text
//! cargo run -p reth-pipe-exec-layer-ext-v2 --features difftx --bin difftx
//! ```

use reth_pipe_exec_layer_ext_v2::difftx::{self, DiffOutcome};
use std::{fs, path::Path, process::ExitCode};

fn main() -> ExitCode {
let matrix: Vec<DiffOutcome> = difftx::variant_matrix();

// —— 人读表格 —— //
println!("差分预言机矩阵:tx_filter ⊇ revm 交易级校验");
println!("{:<34} | {:^12} | {:^11} | verdict", "variant", "filter_admit", "revm_reject");
println!("{}", "-".repeat(90));
let mut gap_count = 0usize;
for o in &matrix {
let verdict = if o.is_gap {
"CHAIN-HALT GAP"
} else if o.revm_reject {
"OK (both reject)"
} else if o.revm_variant.as_deref().map(|s| s.contains(':')).unwrap_or(false) {
// 说明行:revm_variant 里带分类前缀(xxx: 原因)。
"note"
} else {
"OK (both admit)"
};
if o.is_gap {
gap_count += 1;
}
println!("{:<34} | {:^12} | {:^11} | {}", o.name, o.filter_admit, o.revm_reject, verdict);
}
println!("{}", "-".repeat(90));
println!("总计 {} 行,gap {} 个", matrix.len(), gap_count);

// —— 完整 JSON —— //
let json_matrix: Vec<serde_json::Value> = matrix
.iter()
.map(|o| {
serde_json::json!({
"name": o.name,
"filter_admit": o.filter_admit,
"revm_reject": o.revm_reject,
"revm_variant": o.revm_variant,
"is_gap": o.is_gap,
})
})
.collect();
println!("\n完整 JSON:\n{}", serde_json::to_string_pretty(&json_matrix).unwrap());

// ==================== Tier-2:多笔交易序列执行差分 ==================== //
println!("\n============ Tier-2:多笔交易序列执行差分(执行诱导 TOCTOU)============");
let mut seq_gap_count = 0usize;
let repro_dir = Path::new("difftx-repro");
for case in difftx::seq_cases() {
let out = difftx::run_seq_case(&case);
println!("\n用例 {}: filter_admitted={:?}", out.name, out.filter_admitted);
if out.exec_gaps.is_empty() {
println!(" 执行缺口: 无(该序列被 filter 拦截或执行存活)");
} else {
for g in &out.exec_gaps {
println!(
" 执行缺口: tx#{} sender=0x{} -> {}",
g.tx_index,
hex::encode(g.sender),
g.revm_variant
);
seq_gap_count += 1;
}
// 写 gnode 可复现工件。
fs::create_dir_all(repro_dir).expect("创建 difftx-repro 目录");
let repro = difftx::emit_seq_repro(&case, &out);
let path = repro_dir.join(format!("seq_{}.json", out.name));
fs::write(&path, serde_json::to_string_pretty(&repro).unwrap())
.expect("写序列复现文件");
eprintln!("写出序列复现文件: {}", path.display());
}
}

// —— 对每个 Tier-1 gap 写复现文件 —— //
if gap_count > 0 {
fs::create_dir_all(repro_dir).expect("创建 difftx-repro 目录");
// 重新按可造用例遍历,找出 gap 并导出(run_case + emit_repro)。
for case in difftx::craftable_cases_pub() {
let out = difftx::run_case(&case);
if out.is_gap {
let repro = difftx::emit_repro(&case, &out);
let path = repro_dir.join(format!("{}.json", out.name));
fs::write(&path, serde_json::to_string_pretty(&repro).unwrap())
.expect("写复现文件");
eprintln!("写出复现文件: {}", path.display());
}
}
}

println!("\n总判定:Tier-1 gap={} 个,Tier-2 exec_gap={} 个", gap_count, seq_gap_count);
// 任一 tier 出现 gap 即退出码 3(Tier-2 的 exec_gap 就是已知真链停机的复现)。
if gap_count > 0 || seq_gap_count > 0 {
ExitCode::from(3)
} else {
ExitCode::from(0)
}
}
145 changes: 145 additions & 0 deletions crates/pipe-exec-layer-ext-v2/execute/src/bin/difftx_exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! serial(revm) ⟷ grevm 执行差分预言机 —— 命令行工具。
//!
//! 跑所有 baseline + adversarial 用例,逐块过**两个后端**(串行 `WrapExecutor` /
//! 并行 `GrevmExecutor`),打印一张对比表;对**任何背离**写一份可复现 artifact 到
//! `difftx-repro/exec_<name>.json`(含区块交易 + 如何在 live 集群复现的指引),
//! 并以退出码 3 收场;无背离则退出 0。
//!
//! ```text
//! cargo run -p reth-pipe-exec-layer-ext-v2 --features difftx_exec --bin difftx_exec
//! ```

use reth_pipe_exec_layer_ext_v2::difftx_exec::{
exec_adversarial_cases, exec_baseline_cases, run_exec_diff, ExecCase, ExecDiff,
};
use std::io::Write;

fn main() {
let mut all: Vec<(String, ExecCase, ExecDiff)> = Vec::new();

for case in exec_baseline_cases() {
let d = run_exec_diff(&case);
all.push(("baseline".to_string(), case, d));
}
for case in exec_adversarial_cases() {
let d = run_exec_diff(&case);
all.push(("adversarial".to_string(), case, d));
}

// —— 对比表 —— //
println!("\n serial(revm) ⟷ grevm 执行差分预言机\n");
println!(
"{:<12} {:<40} {:>9} {:>8} {:>9} divergence",
"kind", "case", "serial_ok", "grevm_ok", "diverged"
);
println!("{}", "-".repeat(110));

let mut diverged_count = 0usize;
for (kind, _case, d) in &all {
println!(
"{:<12} {:<40} {:>9} {:>8} {:>9} {}",
kind,
d.name,
d.serial_ok,
d.grevm_ok,
d.diverged,
d.divergence.as_deref().unwrap_or("-")
);
if d.diverged {
diverged_count += 1;
}
}

// —— 对背离写可复现 artifact —— //
if diverged_count > 0 {
let dir = std::path::Path::new("difftx-repro");
if let Err(e) = std::fs::create_dir_all(dir) {
eprintln!("无法创建 difftx-repro 目录:{e}");
} else {
for (kind, case, d) in &all {
if !d.diverged {
continue;
}
let path = dir.join(format!("exec_{}.json", d.name));
match write_repro(&path, kind, case, d) {
Ok(()) => println!(" ↳ 已写复现 artifact:{}", path.display()),
Err(e) => eprintln!(" ↳ 写 {} 失败:{e}", path.display()),
}
}
}
}

println!("\n汇总:{} 个用例,{} 个背离。", all.len(), diverged_count);
if diverged_count > 0 {
println!("判决:发现 serial⟷grevm 背离 —— 疑似共识 state-fork。退出码 3。");
std::process::exit(3);
} else {
println!("判决:所有用例 serial==grevm,未发现背离。退出码 0。");
}
}

/// 写一份 JSON 复现 artifact:用例元信息 + 逐笔交易(RLP hex)+ live 集群复现指引。
fn write_repro(
path: &std::path::Path,
kind: &str,
case: &ExecCase,
d: &ExecDiff,
) -> std::io::Result<()> {
use alloy_eips::eip2718::Encodable2718;

let txs: Vec<serde_json::Value> = case
.txs
.iter()
.zip(case.senders.iter())
.map(|(tx, sender)| {
let mut raw = Vec::new();
tx.encode_2718(&mut raw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the fixture sender in repro txs

When a difftx_exec case diverges, this artifact tells operators to submit raw_2718_hex, but those bytes still contain the dummy Signature::test_signature() used by the fixtures while the oracle itself bypasses recovery via RecoveredBlock::new_unhashed(..., case.senders.clone()). On a live node the sender is recovered from the raw signature instead of the JSON sender, so the transaction will run from a different account than the seeded fixture and the repro will either fail nonce/balance checks or exercise a different block; please either sign with the fixture sender keys or emit a replay format that preserves explicit senders.

Useful? React with 👍 / 👎.

serde_json::json!({
"sender": format!("{sender}"),
"type": tx.tx_type() as u8,
"raw_2718_hex": format!("0x{}", hex::encode(&raw)),
})
})
.collect();

let seed: Vec<serde_json::Value> = case
.seed
.iter()
.map(|s| {
serde_json::json!({
"addr": format!("{}", s.addr),
"balance": format!("{}", s.info.balance),
"nonce": s.info.nonce,
"has_code": s.info.code.is_some(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include seeded bytecode in repro artifacts

For any divergence in the code-dependent cases (for example the delegated 7702 baseline or the RAW/WAW storage adversarial case), the artifact only records has_code and drops the actual bytecode and storage from SeedAccount. The reproduction steps ask operators to preseed the accounts, but this JSON no longer contains enough state to reconstruct the block that the oracle executed, so those artifacts cannot reproduce the reported fork; include the code bytes/code hash and storage values instead of only a boolean.

Useful? React with 👍 / 👎.

})
})
.collect();

let doc = serde_json::json!({
"name": d.name,
"kind": kind,
"serial_ok": d.serial_ok,
"grevm_ok": d.grevm_ok,
"diverged": d.diverged,
"divergence": d.divergence,
"base_fee": case.base_fee,
"block_gas_limit": case.block_gas_limit,
"seed": seed,
"txs": txs,
"reproduce_on_live_cluster": {
"note": "两侧后端从同一份 seed 状态 + 同一区块(Prague@ts=0, number=1)执行;\
背离即共识 state-fork。用 gnode 把这批交易灌进一个块复现:",
"steps": [
"1) 用 seed 预置账户余额/nonce/code(genesis alloc 或直接状态注入)",
"2) 按 txs[].raw_2718_hex 顺序提交:gnode send --no-wait <rawtx> (逐笔,保持块内顺序)",
"3) gnode difftx-exec 对该块跑 serial vs grevm 并比对 state root / BundleState / receipts",
"4) 若 state root 背离 → 确认为链停/分叉级 finding"
]
}
});

let mut f = std::fs::File::create(path)?;
f.write_all(serde_json::to_string_pretty(&doc)?.as_bytes())?;
f.write_all(b"\n")?;
Ok(())
}
Loading
Loading