diff --git a/cmd/ethrex_l2/src/cli.rs b/cmd/ethrex_l2/src/cli.rs index 4a2b6a257..9f2d43da6 100644 --- a/cmd/ethrex_l2/src/cli.rs +++ b/cmd/ethrex_l2/src/cli.rs @@ -1,5 +1,5 @@ use crate::{ - commands::{autocomplete, config, info, stack, test, utils, wallet}, + commands::{autocomplete, config, info, prove, stack, test, utils, wallet}, config::load_selected_config, }; use clap::{Parser, Subcommand}; @@ -37,6 +37,8 @@ enum EthrexL2Command { Autocomplete(autocomplete::Command), #[clap(subcommand, about = "Gets L2's information.")] Info(info::Command), + #[clap(about = "Read a test chain from disk and prove a block.")] + Prove(prove::Command), } pub async fn start() -> eyre::Result<()> { @@ -44,6 +46,10 @@ pub async fn start() -> eyre::Result<()> { if let EthrexL2Command::Config(cmd) = command { return cmd.run().await; } + if let EthrexL2Command::Prove(cmd) = command { + return cmd.run(); + } + let cfg = load_selected_config().await?; match command { EthrexL2Command::Stack(cmd) => cmd.run(cfg).await?, @@ -53,6 +59,7 @@ pub async fn start() -> eyre::Result<()> { EthrexL2Command::Config(_) => unreachable!(), EthrexL2Command::Test(cmd) => cmd.run(cfg).await?, EthrexL2Command::Info(cmd) => cmd.run(cfg).await?, + EthrexL2Command::Prove(_) => unreachable!(), }; Ok(()) } diff --git a/cmd/ethrex_l2/src/commands/mod.rs b/cmd/ethrex_l2/src/commands/mod.rs index e202cef57..b17b4bd9f 100644 --- a/cmd/ethrex_l2/src/commands/mod.rs +++ b/cmd/ethrex_l2/src/commands/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod autocomplete; pub(crate) mod config; pub(crate) mod info; +pub(crate) mod prove; pub(crate) mod stack; pub(crate) mod test; pub(crate) mod utils; diff --git a/cmd/ethrex_l2/src/commands/prove.rs b/cmd/ethrex_l2/src/commands/prove.rs new file mode 100644 index 000000000..9a6480c78 --- /dev/null +++ b/cmd/ethrex_l2/src/commands/prove.rs @@ -0,0 +1,43 @@ +use clap::Args; +use ethrex_l2::utils::test_data_io::{generate_program_input, read_chain_file, read_genesis_file}; +use ethrex_prover_lib::prover::Prover; + +#[derive(Args)] +pub(crate) struct Command { + #[clap( + short = 'g', + long = "genesis", + help = "Path to the file containing the genesis block." + )] + genesis: String, + #[clap( + short = 'c', + long = "chain", + help = "Path to the file containing the test chain." + )] + chain: String, + #[clap( + short = 'n', + long = "block-number", + help = "Number of the block in the test chain to prove." + )] + block_number: usize, +} + +impl Command { + pub fn run(self) -> eyre::Result<()> { + let genesis = read_genesis_file(&self.genesis); + let chain = read_chain_file(&self.chain); + let program_input = generate_program_input(genesis, chain, self.block_number)?; + + let mut prover = Prover::new(); + prover.prove(program_input).expect("proving failed"); + println!( + "Total gas consumption: {}", + prover + .get_gas() + .expect("failed to deserialize gas consumption") + ); + Ok(()) + } +} diff --git a/crates/l2/Cargo.toml b/crates/l2/Cargo.toml index 1be80ccc1..6bcb29a5a 100644 --- a/crates/l2/Cargo.toml +++ b/crates/l2/Cargo.toml @@ -27,6 +27,7 @@ secp256k1.workspace = true keccak-hash = "0.10.0" envy = "0.4.2" thiserror.workspace = true +zkvm_interface = { path = "./prover/zkvm/interface/", default-features = false } # risc0 risc0-zkvm = { version = "1.1.2" } diff --git a/crates/l2/prover/.gitignore b/crates/l2/prover/.gitignore new file mode 100644 index 000000000..ea8abc212 --- /dev/null +++ b/crates/l2/prover/.gitignore @@ -0,0 +1,2 @@ +rsp/ +target/ diff --git a/crates/l2/prover/Makefile b/crates/l2/prover/Makefile index 280d07e1f..f754dad2b 100644 --- a/crates/l2/prover/Makefile +++ b/crates/l2/prover/Makefile @@ -1,10 +1,44 @@ +.PHONY: perf_test_proving perf_gpu rsp_comparison + +ROOT_DIRECTORY := ../../.. + RISC0_DEV_MODE?=1 RUST_LOG?="info" perf_test_proving: @echo "Using RISC0_DEV_MODE: ${RISC0_DEV_MODE}" RISC0_DEV_MODE=${RISC0_DEV_MODE} RUST_LOG=${RUST_LOG} cargo test --release --test perf_zkvm --features build_zkvm -- --show-output -.PHONY: perf_test_proving perf_gpu: RUSTFLAGS="-C target-cpu=native" RISC0_DEV_MODE=0 RUST_LOG="debug" cargo test --release --test perf_zkvm --features "build_zkvm,gpu" -- --show-output -.PHONY: perf_gpu + +# L2 Prover comparison with rsp. Uses GPU by default. + +ETHREX_L2_BIN := ./target/release/ethrex_l2 +RSP_BIN := ./target/release/rsp + +GENESIS_FILE := $(ROOT_DIRECTORY)/test_data/genesis-l2-old.json +CHAIN_FILE := $(ROOT_DIRECTORY)/test_data/l2-loadtest.rlp +RSP_CACHE := $(ROOT_DIRECTORY)/test_data/rsp + +rsp_comparison: $(ETHREX_L2_BIN) $(RSP_BIN) + @echo "rsp times (L1 Mainnet block, 24 MGas):" + @time $(RSP_BIN) --prove \ + --chain-id 1 \ + --block-number 21272632 \ + --cache-dir $(RSP_CACHE) \ + >/dev/null + @echo "" + @echo "ethrex_l2 times (L2 block, 25 MGas):" + @RISC0_DEV_MODE=false time $(ETHREX_L2_BIN) prove \ + --genesis $(GENESIS_FILE) \ + --chain $(CHAIN_FILE) \ + --block-number 2 \ + >/dev/null + +$(ETHREX_L2_BIN): + CARGO_TARGET_DIR=target cargo build -r --manifest-path $(ROOT_DIRECTORY)/Cargo.toml --bin ethrex_l2 --features "build_zkvm,gpu,disable-dev-mode" + +$(RSP_BIN): + - git clone https://github.com/succinctlabs/rsp.git + cd rsp; \ + CARGO_TARGET_DIR=../target cargo build -r --bin rsp --features cuda diff --git a/crates/l2/prover/src/prover.rs b/crates/l2/prover/src/prover.rs index 86bdecb95..f366a61b8 100644 --- a/crates/l2/prover/src/prover.rs +++ b/crates/l2/prover/src/prover.rs @@ -58,6 +58,12 @@ impl<'a> Prover<'a> { Ok(()) } + pub fn get_gas(&self) -> Result> { + Ok(risc0_zkvm::serde::from_slice( + self.stdout.get(..8).unwrap_or_default(), // first 8 bytes + )?) + } + pub fn get_commitment( receipt: &risc0_zkvm::Receipt, ) -> Result> { diff --git a/crates/l2/prover/zkvm/interface/guest/src/main.rs b/crates/l2/prover/zkvm/interface/guest/src/main.rs index cad02e40e..e4c2569bf 100644 --- a/crates/l2/prover/zkvm/interface/guest/src/main.rs +++ b/crates/l2/prover/zkvm/interface/guest/src/main.rs @@ -31,6 +31,13 @@ fn main() { let receipts = execute_block(&block, &mut state).expect("failed to execute block"); validate_gas_used(&receipts, &block.header).expect("invalid gas used"); + env::write( + &receipts + .last() + .expect("no receipts found") + .cumulative_gas_used, + ); + let account_updates = get_state_transitions(&mut state); // Update tries and calculate final state root hash diff --git a/crates/l2/utils/error.rs b/crates/l2/utils/error.rs new file mode 100644 index 000000000..04b9b8bb9 --- /dev/null +++ b/crates/l2/utils/error.rs @@ -0,0 +1,18 @@ +use ethrex_blockchain::error::ChainError; +use ethrex_storage::error::StoreError; +use ethrex_vm::errors::ExecutionDBError; +use keccak_hash::H256; + +#[derive(Debug, thiserror::Error)] +pub enum ProverInputError { + #[error("Invalid block number: {0}")] + InvalidBlockNumber(usize), + #[error("Invalid parent block: {0}")] + InvalidParentBlock(H256), + #[error("Store error: {0}")] + StoreError(#[from] StoreError), + #[error("Chain error: {0}")] + ChainError(#[from] ChainError), + #[error("ExecutionDB error: {0}")] + ExecutionDBError(#[from] ExecutionDBError), +} diff --git a/crates/l2/utils/mod.rs b/crates/l2/utils/mod.rs index fb8cee3ec..2ac32c2aa 100644 --- a/crates/l2/utils/mod.rs +++ b/crates/l2/utils/mod.rs @@ -5,6 +5,7 @@ use secp256k1::SecretKey; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub mod config; +pub mod error; pub mod eth_client; pub mod merkle_tree; pub mod test_data_io; diff --git a/crates/l2/utils/test_data_io.rs b/crates/l2/utils/test_data_io.rs index d24e75917..9773a2de6 100644 --- a/crates/l2/utils/test_data_io.rs +++ b/crates/l2/utils/test_data_io.rs @@ -1,9 +1,13 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)] + +use ethrex_blockchain::add_block; use ethrex_core::types::{Block, Genesis}; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; -use ethrex_storage::Store; +use ethrex_storage::{EngineType, Store}; +use ethrex_vm::execution_db::ExecutionDB; use tracing::info; +use zkvm_interface::io::ProgramInput; use std::{ fs::File, @@ -11,6 +15,8 @@ use std::{ path::PathBuf, }; +use super::error::ProverInputError; + // From cmd/ethrex pub fn read_chain_file(chain_rlp_path: &str) -> Vec { let chain_file = File::open(chain_rlp_path).expect("Failed to open chain rlp file"); @@ -53,6 +59,37 @@ pub fn generate_rlp( Ok(()) } +pub fn generate_program_input( + genesis: Genesis, + chain: Vec, + block_number: usize, +) -> Result { + let block = chain + .get(block_number) + .ok_or(ProverInputError::InvalidBlockNumber(block_number))? + .clone(); + + // create store + let store = Store::new("memory", EngineType::InMemory)?; + store.add_initial_state(genesis)?; + for block in chain { + add_block(&block, &store)?; + } + + let parent_block_header = store + .get_block_header_by_hash(block.header.parent_hash)? + .ok_or(ProverInputError::InvalidParentBlock( + block.header.parent_hash, + ))?; + let db = ExecutionDB::from_exec(&block, &store)?; + + Ok(ProgramInput { + db, + block, + parent_block_header, + }) +} + // From cmd/ethrex/decode.rs fn _chain_file(file: File) -> Result, Box> { let mut chain_rlp_reader = BufReader::new(file); diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 36fe2b863..c0b11c896 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -1,5 +1,5 @@ pub mod db; -mod errors; +pub mod errors; pub mod execution_db; mod execution_result; #[cfg(feature = "l2")] diff --git a/test_data/rsp/input/1/21272632.bin b/test_data/rsp/input/1/21272632.bin new file mode 100644 index 000000000..af129a428 Binary files /dev/null and b/test_data/rsp/input/1/21272632.bin differ