Skip to content

Commit

Permalink
feat(derive): payload builder tests (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell authored Apr 18, 2024
1 parent a434571 commit ff42d44
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 2 deletions.
2 changes: 1 addition & 1 deletion crates/derive/src/stages/attributes_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod deposits;
pub(crate) use deposits::derive_deposits;

mod builder;
pub use builder::{AttributesBuilder, StatefulAttributesBuilder};
pub use builder::{AttributesBuilder, StatefulAttributesBuilder, SystemConfigL2Fetcher};

/// [AttributesProvider] is a trait abstraction that generalizes the [BatchQueue] stage.
///
Expand Down
207 changes: 207 additions & 0 deletions crates/derive/src/stages/attributes_queue/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,210 @@ where
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{
stages::test_utils::MockSystemConfigL2Fetcher, traits::test_utils::TestChainProvider,
types::BlockInfo,
};
use alloy_consensus::Header;
use alloy_primitives::b256;

#[tokio::test]
async fn test_prepare_payload_block_mismatch_epoch_reset() {
let cfg = Arc::new(RollupConfig::default());
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header::default();
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() },
l1_origin: BlockID { hash: l2_hash, number: 2 },
seq_num: 0,
};
// This should error because the l2 parent's l1_origin.hash should equal the epoch header
// hash. Here we use the default header whose hash will not equal the custom `l2_hash`.
let expected =
BuilderError::BlockMismatchEpochReset(epoch, l2_parent.l1_origin, B256::default());
let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err();
assert_eq!(err, expected);
}

#[tokio::test]
async fn test_prepare_payload_block_mismatch() {
let cfg = Arc::new(RollupConfig::default());
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header::default();
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() },
l1_origin: BlockID { hash: l2_hash, number: 1 },
seq_num: 0,
};
// This should error because the l2 parent's l1_origin.hash should equal the epoch hash
// Here the default header is used whose hash will not equal the custom `l2_hash` above.
let expected = BuilderError::BlockMismatch(epoch, l2_parent.l1_origin);
let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err();
assert_eq!(err, expected);
}

#[tokio::test]
async fn test_prepare_payload_broken_time_invariant() {
let block_time = 10;
let timestamp = 100;
let cfg = Arc::new(RollupConfig { block_time, ..Default::default() });
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header { timestamp, ..Default::default() };
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() },
l1_origin: BlockID { hash, number: 1 },
seq_num: 0,
};
let next_l2_time = l2_parent.block_info.timestamp + block_time;
let block_id = BlockID { hash, number: 0 };
let expected = BuilderError::BrokenTimeInvariant(
l2_parent.l1_origin,
next_l2_time,
block_id,
timestamp,
);
let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err();
assert_eq!(err, expected);
}

#[tokio::test]
async fn test_prepare_payload_without_forks() {
let block_time = 10;
let timestamp = 100;
let cfg = Arc::new(RollupConfig { block_time, ..Default::default() });
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header { timestamp, ..Default::default() };
let prev_randao = header.mix_hash;
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash },
l1_origin: BlockID { hash, number: 1 },
seq_num: 0,
};
let next_l2_time = l2_parent.block_info.timestamp + block_time;
let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap();
let expected = L2PayloadAttributes {
timestamp: next_l2_time,
prev_randao,
fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS,
transactions: payload.transactions.clone(),
no_tx_pool: true,
gas_limit: Some(u64::from_be_bytes(
alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(),
)),
withdrawals: None,
parent_beacon_block_root: None,
};
assert_eq!(payload, expected);
assert_eq!(payload.transactions.len(), 1);
}

#[tokio::test]
async fn test_prepare_payload_with_canyon() {
let block_time = 10;
let timestamp = 100;
let cfg = Arc::new(RollupConfig { block_time, canyon_time: Some(0), ..Default::default() });
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header { timestamp, ..Default::default() };
let prev_randao = header.mix_hash;
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash },
l1_origin: BlockID { hash, number: 1 },
seq_num: 0,
};
let next_l2_time = l2_parent.block_info.timestamp + block_time;
let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap();
let expected = L2PayloadAttributes {
timestamp: next_l2_time,
prev_randao,
fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS,
transactions: payload.transactions.clone(),
no_tx_pool: true,
gas_limit: Some(u64::from_be_bytes(
alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(),
)),
withdrawals: Some(Vec::default()),
parent_beacon_block_root: None,
};
assert_eq!(payload, expected);
assert_eq!(payload.transactions.len(), 1);
}

#[tokio::test]
async fn test_prepare_payload_with_ecotone() {
let block_time = 10;
let timestamp = 100;
let cfg =
Arc::new(RollupConfig { block_time, ecotone_time: Some(0), ..Default::default() });
let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002");
let mut fetcher = MockSystemConfigL2Fetcher::default();
fetcher.insert(l2_hash, SystemConfig::default());
let mut provider = TestChainProvider::default();
let header = Header { timestamp, ..Default::default() };
let parent_beacon_block_root = Some(header.parent_beacon_block_root.unwrap_or_default());
let prev_randao = header.mix_hash;
let hash = header.hash_slow();
provider.insert_header(hash, header);
let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider);
let epoch = BlockID { hash, number: 1 };
let l2_parent = L2BlockInfo {
block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash },
l1_origin: BlockID { hash, number: 1 },
seq_num: 0,
};
let next_l2_time = l2_parent.block_info.timestamp + block_time;
let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap();
let expected = L2PayloadAttributes {
timestamp: next_l2_time,
prev_randao,
fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS,
transactions: payload.transactions.clone(),
no_tx_pool: true,
gas_limit: Some(u64::from_be_bytes(
alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(),
)),
withdrawals: None,
parent_beacon_block_root,
};
assert_eq!(payload, expected);
assert_eq!(payload.transactions.len(), 7);
}
}
1 change: 1 addition & 0 deletions crates/derive/src/stages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub use batch_queue::{BatchQueue, BatchQueueProvider};
mod attributes_queue;
pub use attributes_queue::{
AttributesBuilder, AttributesProvider, AttributesQueue, StatefulAttributesBuilder,
SystemConfigL2Fetcher,
};

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions crates/derive/src/stages/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ pub use channel_reader::MockChannelReaderProvider;

mod tracing;
pub use tracing::{CollectingLayer, TraceStorage};

mod sys_config_fetcher;
pub use sys_config_fetcher::MockSystemConfigL2Fetcher;
33 changes: 33 additions & 0 deletions crates/derive/src/stages/test_utils/sys_config_fetcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Implements a mock [L2SystemConfigFetcher] for testing.

use crate::{stages::attributes_queue::SystemConfigL2Fetcher, types::SystemConfig};
use alloy_primitives::B256;
use hashbrown::HashMap;

/// A mock implementation of the [`SystemConfigL2Fetcher`] for testing.
#[derive(Debug, Default)]
pub struct MockSystemConfigL2Fetcher {
/// A map from [B256] block hash to a [SystemConfig].
pub system_configs: HashMap<B256, SystemConfig>,
}

impl MockSystemConfigL2Fetcher {
/// Inserts a new system config into the mock fetcher with the given hash.
pub fn insert(&mut self, hash: B256, config: SystemConfig) {
self.system_configs.insert(hash, config);
}

/// Clears all system configs from the mock fetcher.
pub fn clear(&mut self) {
self.system_configs.clear();
}
}

impl SystemConfigL2Fetcher for MockSystemConfigL2Fetcher {
fn system_config_by_l2_hash(&self, hash: B256) -> anyhow::Result<SystemConfig> {
self.system_configs
.get(&hash)
.cloned()
.ok_or_else(|| anyhow::anyhow!("system config not found"))
}
}
13 changes: 12 additions & 1 deletion crates/derive/src/traits/test_utils/data_sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ impl TestChainProvider {
self.receipts.push((hash, receipts));
}

/// Insert a header into the mock chain provider.
pub fn insert_header(&mut self, hash: B256, header: Header) {
self.headers.push((hash, header));
}

/// Clears headers from the mock chain provider.
pub fn clear_headers(&mut self) {
self.headers.clear();
}

/// Clears blocks from the mock chain provider.
pub fn clear_blocks(&mut self) {
self.blocks.clear();
Expand All @@ -81,6 +91,7 @@ impl TestChainProvider {
pub fn clear(&mut self) {
self.clear_blocks();
self.clear_receipts();
self.clear_headers();
}
}

Expand All @@ -90,7 +101,7 @@ impl ChainProvider for TestChainProvider {
if let Some((_, header)) = self.headers.iter().find(|(_, b)| b.hash_slow() == hash) {
Ok(header.clone())
} else {
Err(anyhow::anyhow!("Block not found"))
Err(anyhow::anyhow!("Header not found"))
}
}

Expand Down

0 comments on commit ff42d44

Please sign in to comment.