-
Notifications
You must be signed in to change notification settings - Fork 2
tests: adds builder simulation test harness #219
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
base: main
Are you sure you want to change the base?
Changes from all commits
06a2547
519f3c8
040c822
c16e14a
e1e7db5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| //! Test utilities for block building and simulation. | ||
| //! This module provides builders for creating `BlockBuild` instances | ||
| //! for testing block simulation. | ||
|
|
||
| use super::{ | ||
| db::{TestDb, TestStateSource}, | ||
| env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}, | ||
| }; | ||
| use signet_sim::{BlockBuild, BuiltBlock, SimCache}; | ||
| use std::time::Duration; | ||
| use tokio::time::Instant; | ||
| use trevm::revm::inspector::NoOpInspector; | ||
|
|
||
| /// Test block builder type using in-memory databases. | ||
| pub type TestBlockBuild = | ||
| BlockBuild<TestDb, TestDb, TestStateSource, TestStateSource, NoOpInspector, NoOpInspector>; | ||
|
|
||
| /// Builder for creating test `BlockBuild` instances. | ||
| /// Configures all the parameters needed for block simulation | ||
| /// and provides sensible defaults for testing scenarios. | ||
| #[derive(Debug)] | ||
| pub struct TestBlockBuildBuilder { | ||
| rollup_env: Option<TestRollupEnv>, | ||
| host_env: Option<TestHostEnv>, | ||
| sim_env_builder: Option<TestSimEnvBuilder>, | ||
| sim_cache: SimCache, | ||
| deadline_duration: Duration, | ||
| concurrency_limit: usize, | ||
| max_gas: u64, | ||
| max_host_gas: u64, | ||
| } | ||
|
|
||
| impl Default for TestBlockBuildBuilder { | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl TestBlockBuildBuilder { | ||
| /// Create a new test block build builder with sensible defaults. | ||
| /// Default values: | ||
| /// - Deadline: 2 seconds | ||
| /// - Concurrency limit: 4 | ||
| /// - Max gas: 3,000,000,000 (3 billion) | ||
| /// - Max host gas: 24,000,000 | ||
| pub fn new() -> Self { | ||
| Self { | ||
| rollup_env: None, | ||
| host_env: None, | ||
| sim_env_builder: Some(TestSimEnvBuilder::new()), | ||
| sim_cache: SimCache::new(), | ||
| deadline_duration: Duration::from_secs(2), | ||
| concurrency_limit: 4, | ||
| max_gas: 3_000_000_000, | ||
| max_host_gas: 24_000_000, | ||
| } | ||
| } | ||
|
|
||
| /// Set the simulation environment builder. | ||
| /// The environments will be built from this builder when `build()` is called. | ||
| pub fn with_sim_env_builder(mut self, builder: TestSimEnvBuilder) -> Self { | ||
| self.sim_env_builder = Some(builder); | ||
| self.rollup_env = None; | ||
| self.host_env = None; | ||
| self | ||
| } | ||
|
|
||
| /// Set the rollup environment directly. | ||
| pub fn with_rollup_env(mut self, env: TestRollupEnv) -> Self { | ||
| self.rollup_env = Some(env); | ||
| self | ||
| } | ||
|
|
||
| /// Set the host environment directly. | ||
| pub fn with_host_env(mut self, env: TestHostEnv) -> Self { | ||
| self.host_env = Some(env); | ||
| self | ||
| } | ||
|
Comment on lines
+59
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be better to have a small enum like enum Env {
Builder(TestSimEnvBuilder),
Built {
rollup: TestRollupEnv,
host: TestHostEnv,
}
}and merging |
||
|
|
||
| /// Set the simulation cache. | ||
| pub fn with_cache(mut self, cache: SimCache) -> Self { | ||
| self.sim_cache = cache; | ||
| self | ||
| } | ||
|
|
||
| /// Set the deadline duration from now. | ||
| pub const fn with_deadline(mut self, duration: Duration) -> Self { | ||
| self.deadline_duration = duration; | ||
| self | ||
| } | ||
|
|
||
| /// Set the concurrency limit for parallel simulation. | ||
| pub const fn with_concurrency(mut self, limit: usize) -> Self { | ||
| self.concurrency_limit = limit; | ||
| self | ||
| } | ||
|
|
||
| /// Set the maximum gas limit for the rollup block. | ||
| pub const fn with_max_gas(mut self, gas: u64) -> Self { | ||
| self.max_gas = gas; | ||
| self | ||
| } | ||
|
|
||
| /// Set the maximum gas limit for host transactions. | ||
| pub const fn with_max_host_gas(mut self, gas: u64) -> Self { | ||
| self.max_host_gas = gas; | ||
| self | ||
| } | ||
|
|
||
| /// Build the test `BlockBuild` instance. | ||
| /// This creates a `BlockBuild` ready for simulation. | ||
| /// Call `.build().await` on the result to execute the simulation and get a `BuiltBlock`. | ||
| pub fn build(self) -> TestBlockBuild { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe renaming to |
||
| let sim_env_builder = self.sim_env_builder.unwrap_or_default(); | ||
|
|
||
| let (rollup_env, host_env, ru_source, host_source) = match (self.rollup_env, self.host_env) | ||
| { | ||
| (Some(rollup), Some(host)) => { | ||
| let (ru_source, host_source) = sim_env_builder.build_state_sources(); | ||
| (rollup, host, ru_source, host_source) | ||
| } | ||
| _ => sim_env_builder.build_with_sources(), | ||
| }; | ||
|
|
||
| let finish_by = Instant::now() + self.deadline_duration; | ||
|
|
||
| BlockBuild::new( | ||
| rollup_env, | ||
| host_env, | ||
| finish_by, | ||
| self.concurrency_limit, | ||
| self.sim_cache, | ||
| self.max_gas, | ||
| self.max_host_gas, | ||
| ru_source, | ||
| host_source, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// Convenience function to quickly build a block with a cache and optional configuration. | ||
| /// This is useful for simple test cases where you just want to simulate | ||
| /// some transactions quickly. | ||
| pub async fn quick_build_block(cache: SimCache, deadline: Duration) -> BuiltBlock { | ||
| TestBlockBuildBuilder::new().with_cache(cache).with_deadline(deadline).build().build().await | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_block_build_builder_defaults() { | ||
| let builder = TestBlockBuildBuilder::new(); | ||
| assert_eq!(builder.deadline_duration, Duration::from_secs(2)); | ||
| assert_eq!(builder.concurrency_limit, 4); | ||
| assert_eq!(builder.max_gas, 3_000_000_000); | ||
| assert_eq!(builder.max_host_gas, 24_000_000); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_block_build_builder_custom_values() { | ||
| let builder = TestBlockBuildBuilder::new() | ||
| .with_deadline(Duration::from_secs(5)) | ||
| .with_concurrency(8) | ||
| .with_max_gas(1_000_000_000) | ||
| .with_max_host_gas(10_000_000); | ||
|
|
||
| assert_eq!(builder.deadline_duration, Duration::from_secs(5)); | ||
| assert_eq!(builder.concurrency_limit, 8); | ||
| assert_eq!(builder.max_gas, 1_000_000_000); | ||
| assert_eq!(builder.max_host_gas, 10_000_000); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| //! Test database utilities for in-memory EVM state. | ||
| //! This module provides an in-memory database implementation that can be used | ||
| //! for testing block simulation without requiring network access. | ||
|
|
||
| use alloy::primitives::{Address, B256, U256}; | ||
| use signet_sim::AcctInfo; | ||
| use trevm::revm::{ | ||
| database::{CacheDB, EmptyDB}, | ||
| database_interface::DatabaseRef, | ||
| primitives::KECCAK_EMPTY, | ||
| state::AccountInfo, | ||
| }; | ||
|
|
||
| /// In-memory database for testing (no network access required). | ||
| /// This is a type alias for revm's `CacheDB<EmptyDB>`, which stores all | ||
| /// blockchain state in memory. It implements `DatabaseRef` and can be used | ||
| /// with `RollupEnv` and `HostEnv` for offline simulation testing. | ||
| pub type TestDb = CacheDB<EmptyDB>; | ||
|
|
||
| /// A [`StateSource`] backed by a [`TestDb`] for offline testing. | ||
| /// | ||
| /// This wraps an in-memory database and implements [`signet_sim::StateSource`] | ||
| /// so it can be used as the async state source parameter in [`BlockBuild::new`]. | ||
| /// | ||
| /// [`StateSource`]: signet_sim::StateSource | ||
| /// [`BlockBuild::new`]: signet_sim::BlockBuild::new | ||
| #[derive(Debug, Clone)] | ||
| pub struct TestStateSource { | ||
| db: TestDb, | ||
| } | ||
|
|
||
| impl TestStateSource { | ||
| /// Create a new [`TestStateSource`] from a [`TestDb`]. | ||
| pub const fn new(db: TestDb) -> Self { | ||
| Self { db } | ||
| } | ||
| } | ||
|
|
||
| impl signet_sim::StateSource for TestStateSource { | ||
| type Error = <TestDb as DatabaseRef>::Error; | ||
|
|
||
| async fn account_details(&self, address: &Address) -> Result<AcctInfo, Self::Error> { | ||
| let info = self.db.basic_ref(*address)?.unwrap_or_default(); | ||
| let has_code = info.code_hash() != KECCAK_EMPTY; | ||
| Ok(AcctInfo { nonce: info.nonce, balance: info.balance, has_code }) | ||
| } | ||
| } | ||
|
|
||
| /// Builder for creating pre-populated test databases. | ||
| /// Use this builder to set up blockchain state (accounts, contracts, storage) | ||
| /// before running simulations. | ||
| #[derive(Debug)] | ||
| pub struct TestDbBuilder { | ||
| db: TestDb, | ||
| } | ||
|
|
||
| impl Default for TestDbBuilder { | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl TestDbBuilder { | ||
| /// Create a new empty test database builder. | ||
| pub fn new() -> Self { | ||
| Self { db: CacheDB::new(EmptyDB::default()) } | ||
| } | ||
|
|
||
| /// Add an account with the specified balance and nonce. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `address` - The account address | ||
| /// * `balance` - The account balance in wei | ||
| /// * `nonce` - The account nonce (transaction count) | ||
| pub fn with_account(mut self, address: Address, balance: U256, nonce: u64) -> Self { | ||
| self.db.insert_account_info(address, AccountInfo { balance, nonce, ..Default::default() }); | ||
| self | ||
| } | ||
|
|
||
| /// Set a storage slot for an account. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `address` - The account address | ||
| /// * `slot` - The storage slot index | ||
| /// * `value` - The value to store | ||
| pub fn with_storage(mut self, address: Address, slot: U256, value: U256) -> Self { | ||
| // Ensure the account exists before setting storage | ||
| if !self.db.cache.accounts.contains_key(&address) { | ||
| self.db.insert_account_info(address, AccountInfo::default()); | ||
| } | ||
| let _ = self.db.insert_account_storage(address, slot, value); | ||
| self | ||
| } | ||
|
|
||
| /// Insert a block hash for a specific block number. | ||
| /// | ||
| /// This is useful for testing contracts that use the BLOCKHASH opcode. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `number` - The block number | ||
| /// * `hash` - The block hash | ||
| pub fn with_block_hash(mut self, number: u64, hash: B256) -> Self { | ||
| self.db.cache.block_hashes.insert(U256::from(number), hash); | ||
| self | ||
| } | ||
|
|
||
| /// Build the test database. | ||
| pub fn build(self) -> TestDb { | ||
| self.db | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_db_builder_creates_empty_db() { | ||
| let db = TestDbBuilder::new().build(); | ||
| assert!(db.cache.accounts.is_empty()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_db_builder_adds_account() { | ||
| let address = Address::repeat_byte(0x01); | ||
| let balance = U256::from(1000u64); | ||
| let nonce = 5u64; | ||
|
|
||
| let db = TestDbBuilder::new().with_account(address, balance, nonce).build(); | ||
|
|
||
| let account = db.cache.accounts.get(&address).unwrap(); | ||
| assert_eq!(account.info.balance, balance); | ||
| assert_eq!(account.info.nonce, nonce); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_db_builder_adds_storage() { | ||
| let address = Address::repeat_byte(0x01); | ||
| let slot = U256::from(42u64); | ||
| let value = U256::from(123u64); | ||
|
|
||
| let db = TestDbBuilder::new().with_storage(address, slot, value).build(); | ||
|
|
||
| let account = db.cache.accounts.get(&address).unwrap(); | ||
| let stored = account.storage.get(&slot).unwrap(); | ||
| assert_eq!(*stored, value); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_db_builder_adds_block_hash() { | ||
| let number = 100u64; | ||
| let hash = B256::repeat_byte(0xab); | ||
|
|
||
| let db = TestDbBuilder::new().with_block_hash(number, hash).build(); | ||
|
|
||
| let stored = db.cache.block_hashes.get(&U256::from(number)).unwrap(); | ||
| assert_eq!(*stored, hash); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mega nit, even though this is test code, but inline comments would be good