Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
2,200 changes: 1,136 additions & 1,064 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ reth-provider = { git = "https://github.com/scroll-tech/reth.git", default-featu
reth-rpc-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-rpc-eth-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-rpc-eth-types = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-rpc-layer = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-rpc-server-types = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-storage-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
reth-tasks = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
Expand Down
11 changes: 11 additions & 0 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,16 @@ aws-config = "1.8.0"
aws-sdk-kms = "1.76.0"

# test-utils
alloy-eips = { workspace = true, optional = true }
alloy-rpc-types-eth = { workspace = true, optional = true }
alloy-rpc-types-engine = { workspace = true, optional = true }
reth-e2e-test-utils = { workspace = true, optional = true }
reth-engine-local = { workspace = true, optional = true }
reth-provider = { workspace = true, optional = true }
reth-rpc-layer = { workspace = true, optional = true }
reth-rpc-server-types = { workspace = true, optional = true }
reth-storage-api = { workspace = true, optional = true }
reth-tokio-util = { workspace = true, optional = true }
scroll-alloy-rpc-types-engine = { workspace = true, optional = true }
scroll-alloy-rpc-types.workspace = true

Expand Down Expand Up @@ -108,6 +113,7 @@ reth-e2e-test-utils.workspace = true
reth-node-core.workspace = true
reth-provider.workspace = true
reth-primitives-traits.workspace = true
reth-rpc-layer.workspace = true
reth-rpc-server-types.workspace = true
reth-scroll-node = { workspace = true, features = ["test-utils"] }
reth-storage-api.workspace = true
Expand Down Expand Up @@ -140,10 +146,15 @@ test-utils = [
"rollup-node/test-utils",
"reth-e2e-test-utils",
"reth-rpc-server-types",
"reth-rpc-layer",
"reth-tokio-util",
"scroll-alloy-rpc-types-engine",
"alloy-rpc-types-engine",
"reth-primitives-traits/test-utils",
"reth-network-p2p/test-utils",
"rollup-node-chain-orchestrator/test-utils",
"scroll-network/test-utils",
"alloy-eips",
"reth-storage-api",
"alloy-rpc-types-eth",
]
171 changes: 171 additions & 0 deletions crates/node/src/test_utils/block_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//! Block building helpers for test fixtures.

use super::fixture::TestFixture;
use crate::test_utils::EventAssertions;

use alloy_primitives::B256;
use reth_primitives_traits::transaction::TxHashRef;
use reth_scroll_primitives::ScrollBlock;
use scroll_alloy_consensus::ScrollTransaction;

/// Builder for constructing and validating blocks in tests.
#[derive(Debug)]
pub struct BlockBuilder<'a> {
fixture: &'a mut TestFixture,
expected_tx_hashes: Vec<B256>,
expected_tx_count: Option<usize>,
expected_base_fee: Option<u64>,
expected_block_number: Option<u64>,
expect_l1_message: bool,
expected_l1_message_count: Option<usize>,
}

impl<'a> BlockBuilder<'a> {
/// Create a new block builder.
pub(crate) fn new(fixture: &'a mut TestFixture) -> Self {
Self {
fixture,
expected_tx_hashes: Vec::new(),
expected_tx_count: None,
expected_block_number: None,
expected_base_fee: None,
expect_l1_message: false,
expected_l1_message_count: None,
}
}

/// Expect a specific transaction to be included in the block.
pub fn expect_tx(mut self, tx_hash: B256) -> Self {
self.expected_tx_hashes.push(tx_hash);
self
}

/// Expect a specific number of transactions in the block.
pub const fn expect_tx_count(mut self, count: usize) -> Self {
self.expected_tx_count = Some(count);
self
}

/// Expect a specific block number.
pub const fn expect_block_number(mut self, number: u64) -> Self {
self.expected_block_number = Some(number);
self
}

/// Expect at least one L1 message in the block.
pub const fn expect_l1_message(mut self) -> Self {
self.expect_l1_message = true;
self
}

/// Expect a specific number of L1 messages in the block.
pub const fn expect_l1_message_count(mut self, count: usize) -> Self {
self.expected_l1_message_count = Some(count);
self
}

/// Build the block and validate against expectations.
pub async fn await_block(self) -> eyre::Result<ScrollBlock> {
let sequencer_node = &self.fixture.nodes[0];

// Get the sequencer from the rollup manager handle
let handle = &sequencer_node.rollup_manager_handle;

// Trigger block building
handle.build_block();

// If extract the block number.
let expect = self.fixture.expect_event();
let block =
if let Some(b) = self.expected_block_number {
expect.block_sequenced(b).await?
} else {
expect.extract(|e| {
if let rollup_node_chain_orchestrator::ChainOrchestratorEvent::BlockSequenced(
block,
) = e
{
Some(block.clone())
} else {
None
}
}).await?
};

// Finally validate the block.
self.validate_block(&block)
}

/// Validate the block against expectations.
fn validate_block(self, block: &ScrollBlock) -> eyre::Result<ScrollBlock> {
// Check transaction count
if let Some(expected_count) = self.expected_tx_count {
if block.body.transactions.len() != expected_count {
return Err(eyre::eyre!(
"Expected {} transactions, but block has {}",
expected_count,
block.body.transactions.len()
));
}
}

// Check block number
if let Some(expected_number) = self.expected_block_number {
if block.header.number != expected_number {
return Err(eyre::eyre!(
"Expected {} number, but block has {}",
expected_number,
block.header.number
));
}
}

// Check specific transaction hashes
for expected_hash in &self.expected_tx_hashes {
if !block.body.transactions.iter().any(|tx| tx.tx_hash() == expected_hash) {
return Err(eyre::eyre!(
"Expected transaction {:?} not found in block",
expected_hash
));
}
}

// Check base fee
if let Some(expected_base_fee) = self.expected_base_fee {
let actual_base_fee = block
.header
.base_fee_per_gas
.ok_or_else(|| eyre::eyre!("Block has no base fee"))?;
if actual_base_fee != expected_base_fee {
return Err(eyre::eyre!(
"Expected base fee {}, but block has {}",
expected_base_fee,
actual_base_fee
));
}
}

// Check L1 messages
if self.expect_l1_message {
let l1_message_count =
block.body.transactions.iter().filter(|tx| tx.queue_index().is_some()).count();
if l1_message_count == 0 {
return Err(eyre::eyre!("Expected at least one L1 message, but block has none"));
}
}

if let Some(expected_count) = self.expected_l1_message_count {
let l1_message_count =
block.body.transactions.iter().filter(|tx| tx.queue_index().is_some()).count();
if l1_message_count != expected_count {
return Err(eyre::eyre!(
"Expected {} L1 messages, but block has {}",
expected_count,
l1_message_count
));
}
}

Ok(block.clone())
}
}
Loading