Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.

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", tag = "scroll
reth-rpc-api = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-rpc-eth-api = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-rpc-eth-types = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-rpc-layer = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-rpc-server-types = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-storage-api = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", default-features = false }
reth-tasks = { git = "https://github.com/scroll-tech/reth.git", tag = "scroll-v91", 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",
]
183 changes: 183 additions & 0 deletions crates/node/src/test_utils/block_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//! 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>,
expected_l1_message: Option<L1MessagesAssertion>,
}

/// The assertion on the L1 messages.
#[derive(Debug)]
pub enum L1MessagesAssertion {
/// Expect at least a single L1 message.
ExpectL1Message,
/// Expect an exact number of L1 messages.
ExpectL1MessageCount(usize),
}

impl L1MessagesAssertion {
/// Assert the L1 messages count is correct.
pub fn assert(&self, got: usize) -> eyre::Result<()> {
match self {
Self::ExpectL1Message => {
if got == 0 {
return Err(eyre::eyre!("Expected at least one L1 message, but block has none"));
}
}
Self::ExpectL1MessageCount(count) => {
if got != *count {
return Err(eyre::eyre!("Expected at {count} L1 messages, but block has {got}"));
}
}
}
Ok(())
}
}

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,
expected_l1_message: 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.expected_l1_message = Some(L1MessagesAssertion::ExpectL1Message);
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 = Some(L1MessagesAssertion::ExpectL1MessageCount(count));
self
}

/// Build the block and validate against expectations.
pub async fn build_and_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?.first().expect("should have block sequenced").clone()
};

// 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 let Some(assertion) = self.expected_l1_message {
let l1_message_count =
block.body.transactions.iter().filter(|tx| tx.queue_index().is_some()).count();
assertion.assert(l1_message_count)?;
}

Ok(block.clone())
}
}
Loading
Loading