-
Notifications
You must be signed in to change notification settings - Fork 107
test(l1): add reorg testing framework #4620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
452145d
test: add reorg testing framework
MegaRedHand 547fff8
test: add additional checks
MegaRedHand 89f5548
refactor: split into two files
MegaRedHand 496bfaf
feat: connect peers via P2P
MegaRedHand e685093
test: add second test
MegaRedHand 05bf17d
fix: handle syncing status
MegaRedHand db158b4
chore: remove unused import
MegaRedHand 8ed7a6c
feat: improve error log
MegaRedHand f3fe845
docs: add readme with instructions to run it
MegaRedHand ffd27bd
fix: improve multi-test runs
MegaRedHand 0741172
ci: run reorg tests in CI
MegaRedHand df64404
Merge branch 'main' into add-reorg-framework
MegaRedHand de50cc8
feat: print ethrex version when running tests
MegaRedHand 1726494
chore: comment failing test
MegaRedHand 9c6774d
chore: fix clippy lint
MegaRedHand 032970d
Merge branch 'main' into add-reorg-framework
MegaRedHand ec108d8
Merge branch 'main' into add-reorg-framework
MegaRedHand 0ea4e9b
Merge branch 'main' into add-reorg-framework
MegaRedHand 3fec4a2
Merge branch 'main' into add-reorg-framework
MegaRedHand 9279bf0
fix: differentiate blocks according to builder
MegaRedHand e8d65ca
chore: comment failing test again
MegaRedHand File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[package] | ||
name = "reorgs" | ||
version.workspace = true | ||
edition.workspace = true | ||
|
||
[dependencies] | ||
ethrex.workspace = true | ||
ethrex-common.workspace = true | ||
ethrex-blockchain.workspace = true | ||
ethrex-rpc.workspace = true | ||
ethrex-config.workspace = true | ||
ethrex-l2-common.workspace = true | ||
ethrex-l2-rpc.workspace = true | ||
|
||
tokio.workspace = true | ||
tokio-util.workspace = true | ||
tracing.workspace = true | ||
rand.workspace = true | ||
sha2.workspace = true | ||
hex.workspace = true | ||
nix = { version = "0.30", features = ["signal"] } | ||
secp256k1.workspace = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Reorg integration tests | ||
|
||
This directory contains tests for chain reorganization. | ||
|
||
## How to run | ||
|
||
First, compile the `ethrex` binary if you haven't already: | ||
|
||
```bash | ||
cargo build --workspace --bin ethrex | ||
``` | ||
|
||
Then, run the reorg tests using: | ||
|
||
```bash | ||
cargo run | ||
``` | ||
|
||
You can run a custom binary by specifying the path: | ||
|
||
```bash | ||
cargo run -- /path/to/your/binary | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
use std::{ | ||
path::{Path, PathBuf}, | ||
process::Command, | ||
sync::Arc, | ||
}; | ||
|
||
use ethrex::{cli::Options, initializers::init_tracing}; | ||
use ethrex_l2_rpc::signer::{LocalSigner, Signer}; | ||
use tokio::sync::Mutex; | ||
use tracing::{error, info, warn}; | ||
|
||
use crate::simulator::Simulator; | ||
|
||
mod simulator; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
// Setup logging | ||
init_tracing(&Options::default_l1()); | ||
|
||
// Fetch the path to the ethrex binary from the command line arguments | ||
// If not provided, use the default path | ||
let cmd_path: PathBuf = std::env::args() | ||
.nth(1) | ||
.map(|o| o.parse().unwrap()) | ||
.unwrap_or_else(|| "../../target/debug/ethrex".parse().unwrap()); | ||
|
||
let version = get_ethrex_version(&cmd_path).await; | ||
|
||
info!(%version, binary_path = %cmd_path.display(), "Fetched ethrex binary version"); | ||
info!("Starting test run"); | ||
info!(""); | ||
|
||
run_test(&cmd_path, test_one_block_reorg_and_back).await; | ||
|
||
// TODO: this test is failing | ||
// run_test(&cmd_path, test_many_blocks_reorg).await; | ||
} | ||
|
||
async fn get_ethrex_version(cmd_path: &Path) -> String { | ||
let version_output = Command::new(cmd_path) | ||
.arg("--version") | ||
.output() | ||
.expect("failed to get ethrex version"); | ||
String::from_utf8(version_output.stdout).expect("failed to parse version output") | ||
} | ||
|
||
async fn run_test<F, Fut>(cmd_path: &Path, test_fn: F) | ||
where | ||
F: Fn(Arc<Mutex<Simulator>>) -> Fut, | ||
Fut: Future<Output = ()> + Send + 'static, | ||
MegaRedHand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
let test_name = std::any::type_name::<F>(); | ||
let start = std::time::Instant::now(); | ||
|
||
info!(test=%test_name, "Running test"); | ||
let simulator = Arc::new(Mutex::new(Simulator::new(cmd_path.to_path_buf()))); | ||
|
||
// Run in another task to clean up properly on panic | ||
let result = tokio::spawn(test_fn(simulator.clone())).await; | ||
|
||
simulator.lock_owned().await.stop(); | ||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; | ||
|
||
match result { | ||
Ok(_) => info!(test=%test_name, elapsed=?start.elapsed(), "test completed successfully"), | ||
Err(err) if err.is_panic() => { | ||
error!(test=%test_name, %err, "test panicked"); | ||
std::process::exit(1); | ||
} | ||
Err(err) => { | ||
warn!(test=%test_name, %err, "test task was cancelled"); | ||
} | ||
} | ||
// Add a blank line after each test for readability | ||
info!(""); | ||
} | ||
|
||
async fn test_one_block_reorg_and_back(simulator: Arc<Mutex<Simulator>>) { | ||
let mut simulator = simulator.lock().await; | ||
let signer: Signer = LocalSigner::new( | ||
"941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e" | ||
.parse() | ||
.unwrap(), | ||
) | ||
.into(); | ||
// Some random address | ||
let recipient = "941e103320615d394a55708be13e45994c7d93b0".parse().unwrap(); | ||
let transfer_amount = 1000000; | ||
|
||
let node0 = simulator.start_node().await; | ||
let node1 = simulator.start_node().await; | ||
|
||
// Create a chain with a few empty blocks | ||
let mut base_chain = simulator.get_base_chain(); | ||
for _ in 0..10 { | ||
let extended_base_chain = node0.build_payload(base_chain).await; | ||
node0.notify_new_payload(&extended_base_chain).await; | ||
node0.update_forkchoice(&extended_base_chain).await; | ||
|
||
node1.notify_new_payload(&extended_base_chain).await; | ||
node1.update_forkchoice(&extended_base_chain).await; | ||
base_chain = extended_base_chain; | ||
} | ||
|
||
let initial_balance = node0.get_balance(recipient).await; | ||
|
||
// Fork the chain | ||
let side_chain = base_chain.fork(); | ||
|
||
// Mine a new block in the base chain | ||
let base_chain = node0.build_payload(base_chain).await; | ||
node0.notify_new_payload(&base_chain).await; | ||
node0.update_forkchoice(&base_chain).await; | ||
|
||
// Mine a new block in the base chain (but don't announce it yet) | ||
let extended_base_chain = node0.build_payload(base_chain).await; | ||
|
||
// In parallel, mine a block in the side chain, with an ETH transfer | ||
node1 | ||
.send_eth_transfer(&signer, recipient, transfer_amount) | ||
.await; | ||
|
||
let side_chain = node1.build_payload(side_chain).await; | ||
node1.notify_new_payload(&side_chain).await; | ||
node1.update_forkchoice(&side_chain).await; | ||
|
||
// Sanity check: balance hasn't changed | ||
let same_balance = node0.get_balance(recipient).await; | ||
assert_eq!(same_balance, initial_balance); | ||
|
||
// Notify the first node of the side chain block, it should reorg | ||
node0.notify_new_payload(&side_chain).await; | ||
node0.update_forkchoice(&side_chain).await; | ||
|
||
// Check the transfer has been processed | ||
let new_balance = node0.get_balance(recipient).await; | ||
assert_eq!(new_balance, initial_balance + transfer_amount); | ||
|
||
// Finally, move to the extended base chain, it should reorg back | ||
node0.notify_new_payload(&extended_base_chain).await; | ||
node0.update_forkchoice(&extended_base_chain).await; | ||
|
||
// Check the transfer has been reverted | ||
let new_balance = node0.get_balance(recipient).await; | ||
assert_eq!(new_balance, initial_balance); | ||
} | ||
|
||
#[expect(unused)] | ||
async fn test_many_blocks_reorg(simulator: Arc<Mutex<Simulator>>) { | ||
let mut simulator = simulator.lock().await; | ||
let signer: Signer = LocalSigner::new( | ||
"941e103320615d394a55708be13e45994c7d93b932b064dbcb2b511fe3254e2e" | ||
.parse() | ||
.unwrap(), | ||
) | ||
.into(); | ||
// Some random address | ||
let recipient = "941e103320615d394a55708be13e45994c7d93b0".parse().unwrap(); | ||
let transfer_amount = 1000000; | ||
|
||
let node0 = simulator.start_node().await; | ||
let node1 = simulator.start_node().await; | ||
|
||
// Create a chain with a few empty blocks | ||
let mut base_chain = simulator.get_base_chain(); | ||
for _ in 0..10 { | ||
let extended_base_chain = node0.build_payload(base_chain).await; | ||
node0.notify_new_payload(&extended_base_chain).await; | ||
node0.update_forkchoice(&extended_base_chain).await; | ||
|
||
node1.notify_new_payload(&extended_base_chain).await; | ||
node1.update_forkchoice(&extended_base_chain).await; | ||
base_chain = extended_base_chain; | ||
} | ||
|
||
let initial_balance = node0.get_balance(recipient).await; | ||
|
||
// Fork the chain | ||
let mut side_chain = base_chain.fork(); | ||
|
||
// Create a side chain with multiple blocks only known to node0 | ||
for _ in 0..10 { | ||
side_chain = node0.build_payload(side_chain).await; | ||
node0.notify_new_payload(&side_chain).await; | ||
node0.update_forkchoice(&side_chain).await; | ||
} | ||
|
||
// Sanity check: balance hasn't changed | ||
let same_balance = node0.get_balance(recipient).await; | ||
assert_eq!(same_balance, initial_balance); | ||
|
||
// Advance the base chain with multiple blocks only known to node1 | ||
for _ in 0..10 { | ||
base_chain = node1.build_payload(base_chain).await; | ||
node1.notify_new_payload(&base_chain).await; | ||
node1.update_forkchoice(&base_chain).await; | ||
} | ||
|
||
// Sanity check: balance hasn't changed | ||
let same_balance = node0.get_balance(recipient).await; | ||
assert_eq!(same_balance, initial_balance); | ||
|
||
// Advance the side chain with one more block and an ETH transfer | ||
node1 | ||
.send_eth_transfer(&signer, recipient, transfer_amount) | ||
.await; | ||
base_chain = node1.build_payload(base_chain).await; | ||
node1.notify_new_payload(&base_chain).await; | ||
node1.update_forkchoice(&base_chain).await; | ||
|
||
// Bring node0 again to the base chain, it should reorg | ||
node0.notify_new_payload(&base_chain).await; | ||
node0.update_forkchoice(&base_chain).await; | ||
|
||
// Check the transfer has been processed | ||
let new_balance = node0.get_balance(recipient).await; | ||
assert_eq!(new_balance, initial_balance + transfer_amount); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Copilot Autofix
AI 11 days ago
To fix the problem, we should explicitly set a
permissions
block in the workflow to restrict the GITHUB_TOKEN permissions to the minimum needed. This can be set either at the workflow root or for individual jobs. As the workflow doesn't appear to require any write permissions (e.g., none of the steps push code, modify issues, or interact with pull requests), the safest minimal starting point iscontents: read
. This should be added at the top level of the workflow YAML file (immediately after thename
andon
sections, beforejobs:
), which will apply to all jobs that do not specify their ownpermissions
block.No additional imports, methods, or definitions are needed for this YAML change.