From d928cb1c450734567c774dcc09390a3807ff8c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:46:01 -0300 Subject: [PATCH 1/2] test: add snapsync smoketest --- tooling/reorgs/src/main.rs | 55 +++++++++++++++++++++ tooling/reorgs/src/simulator.rs | 87 ++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/tooling/reorgs/src/main.rs b/tooling/reorgs/src/main.rs index e17e1ac5853..d8616afd35f 100644 --- a/tooling/reorgs/src/main.rs +++ b/tooling/reorgs/src/main.rs @@ -33,6 +33,7 @@ async fn main() { info!(""); run_test(&cmd_path, no_reorgs_full_sync_smoke_test).await; + run_test(&cmd_path, snap_sync_smoke_test).await; // TODO: uncomment once #4676 is fixed // run_test(&cmd_path, test_reorg_back_to_base).await; @@ -104,6 +105,60 @@ async fn no_reorgs_full_sync_smoke_test(simulator: Arc>) { node1.update_forkchoice(&base_chain).await; } +async fn snap_sync_smoke_test(simulator: Arc>) { + let mut simulator = simulator.lock().await; + // Initcode for deploying a contract that receives two `bytes32` parameters and sets `storage[param0] = param1` + let contract_deploy_bytecode = hex::decode("656020355f35555f526006601af3").unwrap().into(); + let signer: Signer = LocalSigner::new( + "941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e" + .parse() + .unwrap(), + ) + .into(); + + let slot_key0 = U256::from(42); + let slot_value0 = U256::from(1163); + let slot_key1 = U256::from(25); + let slot_value1 = U256::from(7474); + + let node1 = simulator.start_node().await; + + // Create a chain with a few empty blocks + let mut base_chain = simulator.get_base_chain(); + + base_chain = node1.extend_chain(base_chain, 10).await; + + // Send a deploy tx for a contract which receives: `(bytes32 key, bytes32 value)` as parameters + let contract_address = node1 + .send_contract_deploy(&signer, contract_deploy_bytecode) + .await; + + // Set another storage slot in the contract in node1 + let calldata0 = [slot_key0.to_big_endian(), slot_value0.to_big_endian()] + .concat() + .into(); + node1.send_call(&signer, contract_address, calldata0).await; + + base_chain = node1.extend_chain(base_chain, 10).await; + // Set another storage slot in the contract in node1 + let calldata1 = [slot_key1.to_big_endian(), slot_value1.to_big_endian()] + .concat() + .into(); + node1.send_call(&signer, contract_address, calldata1).await; + + base_chain = node1.extend_chain(base_chain, 1000).await; + + let node0 = simulator.start_snapsync_node().await; + + node0.update_forkchoice(&base_chain).await; + + // Check the storage slots are as expected after the reorg + let value_slot0 = node0.get_storage_at(contract_address, slot_key0).await; + assert_eq!(value_slot0, slot_value0); + let value_slot1 = node0.get_storage_at(contract_address, slot_key1).await; + assert_eq!(value_slot1, slot_value1); +} + #[expect(unused)] async fn test_reorg_back_to_base(simulator: Arc>) { let mut simulator = simulator.lock().await; diff --git a/tooling/reorgs/src/simulator.rs b/tooling/reorgs/src/simulator.rs index cef1440fbfc..62ee132f479 100644 --- a/tooling/reorgs/src/simulator.rs +++ b/tooling/reorgs/src/simulator.rs @@ -164,6 +164,91 @@ impl Simulator { self.get_node(n) } + pub async fn start_snapsync_node(&mut self) -> Node { + let n = self.configs.len(); + let test_name = &self.test_name; + info!(node = n, "Starting node"); + let mut opts = self.base_opts.clone(); + opts.datadir = format!("data/{test_name}/node{n}").into(); + + opts.http_port = get_next_port().to_string(); + opts.authrpc_port = get_next_port().to_string(); + + // These are one TCP and one UDP + let p2p_port = get_next_port(); + opts.p2p_port = p2p_port.to_string(); + opts.discovery_port = p2p_port.to_string(); + + opts.syncmode = SyncMode::Snap; + + let _ = std::fs::remove_dir_all(&opts.datadir); + std::fs::create_dir_all(&opts.datadir).expect("Failed to create data directory"); + + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + let logs_file_path = format!("data/{test_name}/node{n}_{now}.log"); + let logs_file = File::create(&logs_file_path).expect("Failed to create logs file"); + + let cancel = CancellationToken::new(); + + self.configs.push(opts.clone()); + self.cancellation_tokens.push(cancel.clone()); + + let mut cmd = Command::new(&self.cmd_path); + cmd.args([ + format!("--http.addr={}", opts.http_addr), + format!("--http.port={}", opts.http_port), + format!("--authrpc.addr={}", opts.authrpc_addr), + format!("--authrpc.port={}", opts.authrpc_port), + format!("--p2p.port={}", opts.p2p_port), + format!("--discovery.port={}", opts.discovery_port), + format!("--datadir={}", opts.datadir.display()), + format!("--network={}", self.genesis_path.display()), + format!("--syncmode={:?}", opts.syncmode).to_lowercase(), + "--force".to_string(), + ]) + .stdin(Stdio::null()) + .stdout(logs_file.try_clone().unwrap()) + .stderr(logs_file); + + if !self.enodes.is_empty() { + cmd.arg(format!("--bootnodes={}", self.enodes.join(","))); + } + + let child = cmd.spawn().expect("Failed to start ethrex process"); + + let logs_file = File::open(&logs_file_path).expect("Failed to open logs file"); + let enode = + tokio::time::timeout(Duration::from_secs(5), wait_for_initialization(logs_file)) + .await + .expect("node initialization timed out"); + self.enodes.push(enode); + + tokio::spawn(async move { + let mut child = child; + tokio::select! { + _ = cancel.cancelled() => { + if let Some(pid) = child.id() { + // NOTE: we use SIGTERM instead of child.kill() so sockets are closed + signal::kill(Pid::from_raw(pid as i32), Signal::SIGTERM).unwrap(); + } + } + res = child.wait() => { + assert!(res.unwrap().success()); + } + } + }); + + info!( + "Started node {n} at http://{}:{}", + opts.http_addr, opts.http_port + ); + + self.get_node(n) + } + pub fn stop(&self) { for token in &self.cancellation_tokens { token.cancel(); @@ -378,7 +463,7 @@ impl Node { let sender_address = signer.address(); let nonce = self .rpc_client - .get_nonce(sender_address, BlockIdentifier::Tag(BlockTag::Latest)) + .get_nonce(sender_address, BlockIdentifier::Tag(BlockTag::Pending)) .await .unwrap(); let tx = EIP1559Transaction { From 5004a21fab93becf2e3764c1b8a66b3e7a5a9c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:47:20 -0300 Subject: [PATCH 2/2] chore: add makefile target for reorgs testing --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 58c5dc04d02..43a8627708f 100644 --- a/Makefile +++ b/Makefile @@ -191,3 +191,7 @@ docs: mermaid-init.js mermaid.min.js ## ๐Ÿ“š Generate the documentation docs-serve: mermaid-init.js mermaid.min.js ## ๐Ÿ“š Generate and serve the documentation mdbook serve --open + +test-reorgs: ## ๐Ÿงช Test reorg scenarios + cargo build --bin ethrex + cd tooling/reorgs && cargo run