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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ cargo bench --features global-alloc --bench gigagas
3. **Multi-version memory** (`mv_memory.rs`) — stores all write sets indexed by `(location, tx_index, incarnation)`; lets each transaction read the latest prior write without locking.
4. **Pevm** (`pevm.rs`) — top-level orchestrator; sets up the multi-version memory, spawns worker threads via `std::thread::scope`, collects receipts, applies block rewards.
5. **Storage** (`storage/`) — abstraction over chain state; backends: `InMemoryStorage` (tests) and `RpcStorage` (live chain, feature-gated).
6. **Chain** (`chain/`) — per-chain policies: `PevmEthereum` for Ethereum mainnet, `PevmOptimism` for OP-EVM chains (RISE, Base, and any other OP Stack chain). Handles block env construction, tx parsing, and reward application.
6. **Chain** (`chain/`) — per-chain policies: `PevmEthereum` for Ethereum mainnet, `PevmRise` for RISE (OP Stack chain with fixed base fees and no DA footprint). Handles block env construction, tx parsing, and reward application.

### Key design choices

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bins/fetch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ alloy-consensus.workspace = true
alloy-primitives.workspace = true
alloy-provider.workspace = true
alloy-rpc-types-eth.workspace = true
op-alloy-network.workspace = true

bincode.workspace = true
clap.workspace = true
color-eyre.workspace = true
flate2.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true

Expand Down
116 changes: 77 additions & 39 deletions bins/fetch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,76 @@ use std::{

use alloy_consensus::constants::KECCAK_EMPTY;
use alloy_primitives::{Address, B256};
use alloy_provider::{Provider, ProviderBuilder, network::Ethereum};
use alloy_provider::{Provider, ProviderBuilder, RootProvider, network::Ethereum};
use alloy_rpc_types_eth::BlockId;
use clap::Parser;
use color_eyre::eyre::{Result, WrapErr, eyre};
use flate2::{Compression, bufread::GzDecoder, write::GzEncoder};
use op_alloy_network::Optimism;
use pevm::{
EvmAccount, EvmCode, Pevm, RpcStorage,
chain::{PevmChain, PevmEthereum},
chain::{PevmChain, PevmEthereum, PevmRise},
};
use reqwest::Url;
use serde::Serialize;

#[derive(clap::ValueEnum, Debug, Clone)]
enum ChainChoice {
Ethereum,
Rise,
}

#[derive(Parser, Debug)]
/// Fetch is a CLI tool to fetch a block from an RPC provider, and snapshot that block to disk.
struct Fetch {
#[arg(long, value_enum, default_value = "ethereum")]
chain: ChainChoice,
rpc_url: Url,
block_id: BlockId,
}

// TODO: Binary formats to save disk?
// TODO: Test block after fetching it.
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;

let Fetch { block_id, rpc_url } = Fetch::parse();

// Define provider.
let provider = ProviderBuilder::<_, _, Ethereum>::default().connect_http(rpc_url);

/// Fetch a block and snapshot it to `{data_dir}/blocks/{block_number}/`.
/// Bytecodes and block hashes are accumulated in `{data_dir}/`.
async fn run<C>(
chain: C,
provider: RootProvider<C::Network>,
block_id: BlockId,
data_dir: &str,
) -> Result<()>
where
C: PevmChain + Send + Sync,
C::Transaction: Serialize,
{
// Retrieve block from provider.
let block = provider
.get_block(block_id)
.full()
.await
.context("Failed to fetch block from provider")?
.ok_or_else(|| eyre!("No block found for ID: {block_id:?}"))?;

// TODO: parameterize `chain` to add support for `OP`, `RISE`, and more.
let chain = PevmEthereum::mainnet();
let spec_id = chain.get_block_spec(&block.header).with_context(|| {
format!(
"Failed to get block spec for block: {}",
block.header.number
)
})?;
.ok_or_else(|| eyre!("No block found for ID: {block_id:?}"))?
.into();

let spec_id = chain
.get_block_spec(&block.header)
.map_err(|e| {
eyre!(
"Failed to get block spec for block {}: {e}",
block.header.number
)
})?
.into();

let storage = RpcStorage::new(provider, spec_id, BlockId::number(block.header.number - 1));

// Execute the block and track the pre-state in the RPC storage.
Pevm::default()
.execute(&chain, &storage, &block, NonZeroUsize::MIN, true)
.context("Failed to execute block")?;
.map_err(|e| eyre!("Failed to execute block: {e:?}"))?;
Comment thread
hai-rise marked this conversation as resolved.

let block_dir = format!("data/blocks/{}", block.header.number);
let block_dir = format!("{data_dir}/blocks/{}", block.header.number);

// Create block directory.
fs::create_dir_all(block_dir.clone()).context("Failed to create block directory")?;
fs::create_dir_all(&block_dir).context("Failed to create block directory")?;

// Write block to disk.
let block_file =
Expand All @@ -72,7 +86,8 @@ async fn main() -> Result<()> {

// Populate bytecodes and state from RPC storage.
// TODO: Deduplicate logic with [for_each_block_from_disk] when there is more usage
let mut bytecodes: BTreeMap<B256, EvmCode> = match File::open("data/bytecodes.bincode.gz") {
let bytecodes_path = format!("{data_dir}/bytecodes.bincode.gz");
let mut bytecodes: BTreeMap<B256, EvmCode> = match File::open(&bytecodes_path) {
Ok(compressed_file) => bincode::serde::decode_from_std_read(
&mut GzDecoder::new(BufReader::new(compressed_file)),
bincode::config::standard(),
Expand All @@ -96,7 +111,7 @@ async fn main() -> Result<()> {
}

// Write compressed bytecodes to disk.
let mut writer_bytecodes = File::create("data/bytecodes.bincode.gz")
let mut writer_bytecodes = File::create(&bytecodes_path)
.map(|f| GzEncoder::new(f, Compression::default()))
.context("Failed to create compressed bytecodes file")?;
bincode::serde::encode_into_std_write(
Expand All @@ -112,22 +127,20 @@ async fn main() -> Result<()> {
serde_json::to_writer(file_state, &state).context("Failed to write pre-state to file")?;

// TODO: Deduplicate logic with [for_each_block_from_disk] when there is more usage
let mut block_hashes = match File::open("data/block_hashes.bincode") {
Ok(mut compressed_file) => {
bincode::serde::decode_from_std_read::<BTreeMap<u64, B256>, _, _>(
&mut compressed_file,
bincode::config::standard(),
)
.context("Failed to deserialize block hashes from file")?
}
let block_hashes_path = format!("{data_dir}/block_hashes.bincode");
let mut block_hashes = match File::open(&block_hashes_path) {
Ok(mut file) => bincode::serde::decode_from_std_read::<BTreeMap<u64, B256>, _, _>(
&mut file,
bincode::config::standard(),
)
.context("Failed to deserialize block hashes from file")?,
Err(_) => BTreeMap::new(),
};
block_hashes.extend(cached_block_hashes);

if !block_hashes.is_empty() {
// Write compressed block hashes to disk
let mut file = File::create("data/block_hashes.bincode")
.context("Failed to create block hashes file")?;
let mut file =
File::create(&block_hashes_path).context("Failed to create block hashes file")?;
bincode::serde::encode_into_std_write(
&block_hashes,
&mut file,
Expand All @@ -136,5 +149,30 @@ async fn main() -> Result<()> {
.context("Failed to write block hashes to file")?;
}

println!("Fetched block {}.", block.header.number,);

Ok(())
}

// TODO: Test block after fetching it.
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;

let Fetch {
block_id,
rpc_url,
chain,
} = Fetch::parse();

match chain {
ChainChoice::Ethereum => {
let provider = ProviderBuilder::<_, _, Ethereum>::default().connect_http(rpc_url);
run(PevmEthereum::mainnet(), provider, block_id, "data/ethereum").await
}
ChainChoice::Rise => {
let provider = ProviderBuilder::<_, _, Optimism>::default().connect_http(rpc_url);
run(PevmRise, provider, block_id, "data/rise").await
}
}
}
2 changes: 1 addition & 1 deletion crates/pevm/benches/mainnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
);
let mut pevm = Pevm::default();

common::for_each_block_from_disk(|block, storage| {
common::for_each_block_from_disk("ethereum", |block, storage| {
let mut group = c.benchmark_group(format!(
"Block {}({} txs, {} gas)",
block.header.number,
Expand Down
15 changes: 11 additions & 4 deletions crates/pevm/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,25 @@ pub trait PevmChain: Debug {
/// Get a reference to the base [`TxEnv`] from a chain-specific transaction
fn tx_env<'a>(&self, tx: &'a Self::EvmTx) -> &'a TxEnv;

/// Whether this transaction has a nonce. Return false for types that have no nonce
/// (e.g. OP deposits) so pevm's sender-nonce ordering check is skipped. Implementations
/// may also adjust EVM cfg as a side effect (e.g. setting `disable_nonce_check`).
fn has_nonce<DB: Database>(&self, _: &mut Self::Evm<DB>, _: &Self::EvmTx) -> bool {
true
}

/// Build [`MvMemory`]
fn build_mv_memory(&self, _block_env: &BlockEnv, txs: &[Self::EvmTx]) -> MvMemory {
MvMemory::new(txs.len(), [], [])
}

/// Get rewards (balance increments) to beneficiary accounts, etc.
fn get_rewards<DB: Database>(
fn get_rewards(
&self,
beneficiary_location_hash: u64,
gas_used: U256,
gas_price: U256,
evm: &mut Self::Evm<DB>,
basefee: u64,
tx: &Self::EvmTx,
) -> SmallVec<[(MemoryLocationHash, U256); 1]>;

Expand All @@ -166,5 +173,5 @@ pub trait PevmChain: Debug {
mod ethereum;
pub use ethereum::PevmEthereum;

mod optimism;
pub use optimism::PevmOptimism;
mod rise;
pub use rise::{PevmRise, RiseTransactionParsingError};
4 changes: 2 additions & 2 deletions crates/pevm/src/chain/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,12 @@ impl PevmChain for PevmEthereum {
MvMemory::new(block_size, estimated_locations, [block_env.beneficiary])
}

fn get_rewards<DB: Database>(
fn get_rewards(
&self,
beneficiary_location_hash: u64,
gas_used: U256,
gas_price: U256,
_: &mut Self::Evm<DB>,
_: u64,
_: &Self::EvmTx,
) -> SmallVec<[(MemoryLocationHash, U256); 1]> {
smallvec::smallvec![(
Expand Down
Loading
Loading