diff --git a/README.md b/README.md index d26b60d7..09ecdd02 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,17 @@ cargo run Then in another terminal you can do requests like this: ``` -./prove_block.sh testnet native 10 +./prove_block.sh taiko_a7 native 10 ``` Look into `prove_block.sh` for the available options or run the script without inputs and it will tell you. +You can also automatically sync with the tip of the chain and prove all new blocks: + +``` +./prove_block.sh taiko_a7 native sync +``` + ## Provers Provers can be enabled using features. To compile with all of them (using standard options): @@ -63,7 +69,7 @@ RISC0_DEV_MODE=1 cargo run --release --features risc0 # edit run_bonsai.sh and run run_bonsai.sh # then -prove_block.sh testnet risc0-bonsai 10 +prove_block.sh taiko_a7 risc0-bonsai 10 ``` #### CPU diff --git a/host/src/execution.rs b/host/src/execution.rs index bee4a1dc..658ec02d 100644 --- a/host/src/execution.rs +++ b/host/src/execution.rs @@ -20,6 +20,8 @@ pub async fn execute( config: &serde_json::Value, cached_input: Option, ) -> Result<(GuestInput, Proof)> { + let total_proving_time = Measurement::start("", false); + // Generate the input let input = if let Some(cached_input) = cached_input { println!("Using cached input"); @@ -68,6 +70,8 @@ pub async fn execute( measurement.stop_with("=> Proof generated"); memory::print_stats("Prover peak memory used: "); + total_proving_time.stop_with("====> Complete proof generated"); + res } diff --git a/host/src/main.rs b/host/src/main.rs index 15f9f918..83fc2559 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -163,7 +163,7 @@ mod memory { pub(crate) fn print_stats(title: &str) { let max_memory = get_max_allocated(); println!( - "{}{}.{} MB", + "{}{}.{:06} MB", title, max_memory / 1000000, max_memory % 1000000 diff --git a/host/src/preflight.rs b/host/src/preflight.rs index aa081984..b2090267 100644 --- a/host/src/preflight.rs +++ b/host/src/preflight.rs @@ -52,9 +52,10 @@ pub fn preflight( println!("\nblock.hash: {:?}", block.header.hash.unwrap()); println!("block.parent_hash: {:?}", block.header.parent_hash); + println!("block gas used: {:?}", block.header.gas_used.as_limbs()[0]); println!("block transactions: {:?}", block.transactions.len()); - let taiko_guest_input = if network != Network::Ethereum { + let taiko_guest_input = if network.is_taiko() { let http_l1 = Http::new(Url::parse(&l1_rpc_url.clone().unwrap()).expect("invalid rpc url")); let provider_l1 = ProviderBuilder::new().provider(RootProvider::new(RpcClient::new(http_l1, true))); @@ -157,6 +158,7 @@ pub fn preflight( let input = GuestInput { network, block_number, + gas_used: block.header.gas_used.try_into().unwrap(), block_hash: block.header.hash.unwrap().0.into(), beneficiary: block.header.miner, gas_limit: block.header.gas_limit.try_into().unwrap(), @@ -187,6 +189,7 @@ pub fn preflight( // Create the block builder, run the transactions and extract the DB let provider_db = ProviderDb::new( provider, + network, parent_block.header.number.unwrap().try_into().unwrap(), ); let mut builder = BlockBuilder::new(&input) @@ -199,8 +202,12 @@ pub fn preflight( // Gather inclusion proofs for the initial and final state let measurement = Measurement::start("Fetching storage proofs...", true); - let (parent_proofs, proofs) = provider_db.get_proofs()?; - measurement.stop(); + let (parent_proofs, proofs, num_storage_proofs) = provider_db.get_proofs()?; + measurement.stop_with_count(&format!( + "[{} Account/{} Storage]", + parent_proofs.len() + proofs.len(), + num_storage_proofs + )); // Construct the state trie and storage from the storage proofs. let measurement = Measurement::start("Constructing MPT...", true); @@ -416,18 +423,20 @@ fn get_block_proposed_event( fn get_transactions_from_block(block: &AlloyBlock) -> Vec { let mut transactions: Vec = Vec::new(); - match &block.transactions { - BlockTransactions::Full(txs) => { - for tx in txs { - transactions.push(from_block_tx(tx)); - } - }, - _ => unreachable!("Block is too old, please connect to an archive node or use a block that is at most 128 blocks old."), - }; - assert!( - transactions.len() == block.transactions.len(), - "unexpected number of transactions" - ); + if !block.transactions.is_empty() { + match &block.transactions { + BlockTransactions::Full(txs) => { + for tx in txs { + transactions.push(from_block_tx(tx)); + } + }, + _ => unreachable!("Block is too old, please connect to an archive node or use a block that is at most 128 blocks old."), + }; + assert!( + transactions.len() == block.transactions.len(), + "unexpected number of transactions" + ); + } transactions } diff --git a/host/src/provider_db.rs b/host/src/provider_db.rs index 8bea45b8..9b17e39c 100644 --- a/host/src/provider_db.rs +++ b/host/src/provider_db.rs @@ -19,7 +19,10 @@ use std::{ use alloy_consensus::Header as AlloyConsensusHeader; use alloy_provider::{Provider, ReqwestProvider}; use alloy_rpc_types::{BlockId, EIP1186AccountProofResponse}; -use raiko_lib::{clear_line, inplace_print, mem_db::MemDb, taiko_utils::to_header}; +use raiko_lib::{ + clear_line, consts::Network, inplace_print, mem_db::MemDb, print_duration, + taiko_utils::to_header, +}; use raiko_primitives::{Address, B256, U256}; use revm::{ primitives::{Account, AccountInfo, Bytecode, HashMap}, @@ -31,6 +34,7 @@ use crate::preflight::{batch_get_history_headers, get_block}; pub struct ProviderDb { pub provider: ReqwestProvider, + pub network: Network, pub block_number: u64, pub initial_db: MemDb, pub current_db: MemDb, @@ -38,9 +42,10 @@ pub struct ProviderDb { } impl ProviderDb { - pub fn new(provider: ReqwestProvider, block_number: u64) -> Self { + pub fn new(provider: ReqwestProvider, network: Network, block_number: u64) -> Self { ProviderDb { provider, + network, block_number, initial_db: MemDb::default(), current_db: MemDb::default(), @@ -90,6 +95,7 @@ impl ProviderDb { ( HashMap, HashMap, + usize, ), anyhow::Error, > { @@ -128,7 +134,7 @@ impl ProviderDb { num_storage_proofs, )?; - Ok((initial_proofs, latest_proofs)) + Ok((initial_proofs, latest_proofs, num_storage_proofs)) } pub fn get_ancestor_headers(&mut self) -> Result, anyhow::Error> { @@ -224,16 +230,35 @@ impl Database for ProviderDb { return Ok(block_hash); } - // Get the 256 history block hashes from the provider at first time for anchor - // transaction. let block_number = u64::try_from(number).unwrap(); - for block in batch_get_history_headers(&self.provider, &self.async_executor, block_number)? - { - let block_number = block.header.number.unwrap().try_into().unwrap(); - let block_hash = block.header.hash.unwrap(); + if self.network.is_taiko() { + // Get the 256 history block hashes from the provider at first time for anchor + // transaction. + for block in + batch_get_history_headers(&self.provider, &self.async_executor, block_number)? + { + let block_number = block.header.number.unwrap().try_into().unwrap(); + let block_hash = block.header.hash.unwrap(); + self.initial_db.insert_block_hash(block_number, block_hash); + } + self.block_hash(number) + } else { + // Get the block hash from the provider. + let block_hash = self.async_executor.block_on(async { + self.provider + .get_block_by_number(block_number.into(), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap() + .0 + .into() + }); self.initial_db.insert_block_hash(block_number, block_hash); + Ok(block_hash) } - self.block_hash(number) } fn code_by_hash(&mut self, _code_hash: B256) -> Result { @@ -278,23 +303,17 @@ impl MeasuredProviderDb { pub fn print_report(&self) { println!("db accesses: "); - println!( - "- account: {}.{} seconds ({} ops)", - self.time_basic.as_secs(), - self.time_basic.subsec_millis(), - self.num_basic + print_duration( + &format!("- account [{} ops]: ", self.num_basic), + self.time_basic, ); - println!( - "- storage: {}.{} seconds ({} ops)", - self.time_storage.as_secs(), - self.time_storage.subsec_millis(), - self.num_storage + print_duration( + &format!("- storage [{} ops]: ", self.num_storage), + self.time_storage, ); - println!( - "- block_hash: {}.{} seconds ({} ops)", - self.time_block_hash.as_secs(), - self.time_block_hash.subsec_millis(), - self.num_block_hash + print_duration( + &format!("- block_hash [{} ops]: ", self.num_block_hash), + self.time_block_hash, ); println!("- code_by_hash: {}", self.num_code_by_hash); } @@ -307,8 +326,7 @@ impl Database for MeasuredProviderDb { self.num_basic += 1; let start = Instant::now(); let res = self.provider.basic(address); - self.time_basic - .add_assign(Instant::now().duration_since(start)); + self.time_basic.add_assign(start.elapsed()); res } @@ -316,8 +334,7 @@ impl Database for MeasuredProviderDb { self.num_storage += 1; let start = Instant::now(); let res = self.provider.storage(address, index); - self.time_storage - .add_assign(Instant::now().duration_since(start)); + self.time_storage.add_assign(start.elapsed()); res } @@ -325,8 +342,7 @@ impl Database for MeasuredProviderDb { self.num_block_hash += 1; let start = Instant::now(); let res = self.provider.block_hash(number); - self.time_block_hash - .add_assign(Instant::now().duration_since(start)); + self.time_block_hash.add_assign(start.elapsed()); res } diff --git a/lib/src/builder/execute.rs b/lib/src/builder/execute.rs index fa3d18e4..cd270d18 100644 --- a/lib/src/builder/execute.rs +++ b/lib/src/builder/execute.rs @@ -36,7 +36,7 @@ use super::TxExecStrategy; use crate::{ builder::BlockBuilder, clear_line, - consts::{get_network_spec, Network, GWEI_TO_WEI}, + consts::{get_network_spec, GWEI_TO_WEI}, guest_mem_forget, inplace_print, print_duration, taiko_utils::{check_anchor_tx, generate_transactions}, time::{AddAssign, Duration, Instant}, @@ -73,14 +73,14 @@ impl TxExecStrategy for TkoTxExecStrategy { println!("spec_id: {:?}", spec_id); let network = block_builder.input.network; - let is_taiko = network != Network::Ethereum; + let is_taiko = network.is_taiko(); // generate the transactions from the tx list // For taiko blocks, insert the anchor tx as the first transaction - let anchor_tx = if block_builder.input.network == Network::Ethereum { - None - } else { + let anchor_tx = if block_builder.input.network.is_taiko() { Some(serde_json::from_str(&block_builder.input.taiko.anchor_tx.clone()).unwrap()) + } else { + None }; let mut transactions = generate_transactions( block_builder.input.taiko.block_proposed.meta.blobUsed, @@ -170,7 +170,7 @@ impl TxExecStrategy for TkoTxExecStrategy { let tx_env = &mut evm.env_mut().tx; fill_eth_tx_env(tx_env, &tx)?; // Set and check some taiko specific values - if network != Network::Ethereum { + if network.is_taiko() { // set if the tx is the anchor tx tx_env.taiko.is_anchor = is_anchor; // set the treasury address @@ -260,7 +260,7 @@ impl TxExecStrategy for TkoTxExecStrategy { #[cfg(feature = "std")] debug!(" Ok: {result:?}"); - tx_transact_duration.add_assign(Instant::now().duration_since(start)); + tx_transact_duration.add_assign(start.elapsed()); let start = Instant::now(); @@ -300,7 +300,7 @@ impl TxExecStrategy for TkoTxExecStrategy { // If we got here it means the tx is not invalid actual_tx_no += 1; - tx_misc_duration.add_assign(Instant::now().duration_since(start)); + tx_misc_duration.add_assign(start.elapsed()); } clear_line(); print_duration("Tx transact time: ", tx_transact_duration); diff --git a/lib/src/consts.rs b/lib/src/consts.rs index e1880f60..9670efc1 100644 --- a/lib/src/consts.rs +++ b/lib/src/consts.rs @@ -66,6 +66,31 @@ pub const ETH_MAINNET_CHAIN_SPEC: Lazy = Lazy::new(|| { } }); +/// The Ethereum testnet "holesky" specification. +pub const ETH_HOLESKY_CHAIN_SPEC: Lazy = Lazy::new(|| { + ChainSpec { + chain_id: 17000, + max_spec_id: SpecId::CANCUN, + hard_forks: BTreeMap::from([ + (SpecId::FRONTIER, ForkCondition::Block(0)), + // previous versions not supported + (SpecId::SHANGHAI, ForkCondition::Timestamp(1696000704)), + (SpecId::CANCUN, ForkCondition::Timestamp(1707305664)), + ]), + eip_1559_constants: Eip1559Constants { + base_fee_change_denominator: uint!(8_U256), + base_fee_max_increase_denominator: uint!(8_U256), + base_fee_max_decrease_denominator: uint!(8_U256), + elasticity_multiplier: uint!(2_U256), + }, + l1_contract: None, + l2_contract: None, + sgx_verifier_address: None, + genesis_time: 0u64, + seconds_per_slot: 1u64, + } +}); + /// The Taiko A6 specification. pub const TAIKO_A6_CHAIN_SPEC: Lazy = Lazy::new(|| ChainSpec { chain_id: 167008, @@ -115,6 +140,7 @@ pub const TAIKO_A7_CHAIN_SPEC: Lazy = Lazy::new(|| ChainSpec { pub fn get_network_spec(network: Network) -> ChainSpec { match network { Network::Ethereum => ETH_MAINNET_CHAIN_SPEC.clone(), + Network::Holesky => ETH_HOLESKY_CHAIN_SPEC.clone(), Network::TaikoA6 => TAIKO_A6_CHAIN_SPEC.clone(), Network::TaikoA7 => TAIKO_A7_CHAIN_SPEC.clone(), } @@ -234,6 +260,8 @@ pub enum Network { /// The Ethereum Mainnet #[default] Ethereum, + /// Ethereum testnet holesky + Holesky, /// Taiko A6 tesnet TaikoA6, /// Taiko A7 tesnet @@ -246,6 +274,7 @@ impl FromStr for Network { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "ethereum" => Ok(Network::Ethereum), + "holesky" => Ok(Network::Holesky), "taiko_a6" => Ok(Network::TaikoA6), "taiko_a7" => Ok(Network::TaikoA7), #[allow(clippy::needless_return)] @@ -258,12 +287,24 @@ impl ToString for Network { fn to_string(&self) -> String { match self { Network::Ethereum => String::from("ethereum"), + Network::Holesky => String::from("holesky"), Network::TaikoA6 => String::from("taiko_a6"), Network::TaikoA7 => String::from("taiko_a7"), } } } +impl Network { + pub fn is_taiko(&self) -> bool { + match self { + Network::Ethereum => false, + Network::Holesky => false, + Network::TaikoA6 => true, + Network::TaikoA7 => true, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lib/src/input.rs b/lib/src/input.rs index bf225616..6c72e172 100644 --- a/lib/src/input.rs +++ b/lib/src/input.rs @@ -41,6 +41,8 @@ pub struct GuestInput { pub network: Network, /// Block number pub block_number: u64, + /// Block gas used + pub gas_used: u64, /// Block hash - for reference! pub block_hash: B256, /// Previous block header diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 87bc125f..4d30c33c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -58,6 +58,9 @@ mod time { pub fn duration_since(&self, _instant: Instant) -> Duration { Duration::default() } + pub fn elapsed(&self) -> Duration { + Duration::default() + } } #[derive(Default, Clone, Copy)] @@ -91,7 +94,9 @@ impl Measurement { #[cfg(feature = "std")] io::stdout().flush().unwrap(); } else { - println!("{title}"); + if !title.is_empty() { + println!("{title}"); + } } Self { start: time::Instant::now(), @@ -104,18 +109,23 @@ impl Measurement { self.stop_with(&format!("{} Done", self.title)); } - pub fn stop_with(&self, title: &str) { - let time_elapsed = time::Instant::now().duration_since(self.start); + pub fn stop_with_count(&self, count: &str) { + self.stop_with(&format!("{} {} done", self.title, count)); + } + + pub fn stop_with(&self, title: &str) -> time::Duration { + let time_elapsed = self.start.elapsed(); print_duration( &format!("{}{} in ", if self.inplace { "\r" } else { "" }, title,), time_elapsed, ); + time_elapsed } } pub fn print_duration(title: &str, duration: time::Duration) { println!( - "{}{}.{} seconds", + "{}{}.{:03} seconds", title, duration.as_secs(), duration.subsec_millis() diff --git a/lib/src/protocol_instance.rs b/lib/src/protocol_instance.rs index 9f1a3e10..45952889 100644 --- a/lib/src/protocol_instance.rs +++ b/lib/src/protocol_instance.rs @@ -8,7 +8,7 @@ use super::taiko_utils::ANCHOR_GAS_LIMIT; #[cfg(not(feature = "std"))] use crate::no_std::*; use crate::{ - consts::{get_network_spec, Network}, + consts::get_network_spec, input::{BlockMetadata, EthDeposit, GuestInput, Transition}, taiko_utils::HeaderHasher, }; @@ -134,7 +134,7 @@ pub fn assemble_protocol_instance( }; // Sanity check - if input.network != Network::Ethereum { + if input.network.is_taiko() { ensure!( pi.block_metadata.abi_encode() == input.taiko.block_proposed.meta.abi_encode(), format!( diff --git a/prove_block.sh b/prove_block.sh index 2ff57ac7..730b27e8 100755 --- a/prove_block.sh +++ b/prove_block.sh @@ -1,16 +1,33 @@ #!/bin/bash +getBlockNumber() { + # Get the latest block number from the node + output=$(curl $rpc -s -X POST -H "Content-Type: application/json" --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}') + + # Extract the hexadecimal number using jq and remove the surrounding quotes + hex_number=$(echo $output | jq -r '.result') + + # Convert the hexadecimal to decimal + block_number=$(echo $((${hex_number}))) + + # Return the block number by echoing it + echo "$block_number" +} + # Use the first command line argument as the chain name chain="$1" # Use the second command line argument as the proof type proof="$2" # Use the third(/fourth) parameter(s) as the block number as a range +# Use the special value "sync" as the third parameter to follow the tip of the chain rangeStart="$3" rangeEnd="$4" -# Check the caain name and set the corresponding RPC values +# Check the chain name and set the corresponding RPC values if [ "$chain" == "ethereum" ]; then rpc="https://rpc.ankr.com/eth" +elif [ "$chain" == "holesky" ]; then + rpc="https://ethereum-holesky-rpc.publicnode.com" elif [ "$chain" == "taiko_a6" ]; then rpc="https://rpc.katla.taiko.xyz" l1Rpc="https://l1rpc.katla.taiko.xyz" @@ -68,6 +85,13 @@ else exit 1 fi + +if [ "$rangeStart" == "sync" ]; then + sync="true" + rangeStart=$(getBlockNumber) + rangeEnd=$((rangeStart + 1000000)) +fi + if [ "$rangeStart" == "" ]; then echo "Please specify a valid block range like \"10\" or \"10 20\"" exit 1 @@ -82,6 +106,16 @@ graffiti="8008500000000000000000000000000000000000000000000000000000000000" for block in $(eval echo {$rangeStart..$rangeEnd}); do + # Special sync logic to follow the tip of the chain + if [ "$sync" == "true" ]; then + block_number=$(getBlockNumber) + # While the current block is greater than the block number from the blockchain + while [ "$block" -gt "$block_number" ]; do + sleep 0.1 # Wait for 100ms + block_number=$(getBlockNumber) # Query again to get the updated block number + done + fi + echo "- proving block $block" curl --location --request POST 'http://localhost:8080' \ --header 'Content-Type: application/json' \ diff --git a/provers/sgx/prover/src/lib.rs b/provers/sgx/prover/src/lib.rs index d549afe2..45211879 100644 --- a/provers/sgx/prover/src/lib.rs +++ b/provers/sgx/prover/src/lib.rs @@ -162,7 +162,7 @@ async fn setup(cur_dir: &PathBuf, direct_mode: bool) -> ProverResult<(), String> .arg("sgx-guest.manifest") .output() .await - .map_err(|e| format!("Could not generate manfifest: {}", e))?; + .map_err(|e| handle_gramine_error("Could not generate manfifest", e))?; print_output(&output, "Generate manifest"); @@ -173,7 +173,7 @@ async fn setup(cur_dir: &PathBuf, direct_mode: bool) -> ProverResult<(), String> .arg("-f") .output() .await - .map_err(|e| format!("Could not generate SGX private key: {}", e))?; + .map_err(|e| handle_gramine_error("Could not generate SGX private key", e))?; // Sign the manifest let mut cmd = Command::new("gramine-sgx-sign"); @@ -184,7 +184,7 @@ async fn setup(cur_dir: &PathBuf, direct_mode: bool) -> ProverResult<(), String> .arg("sgx-guest.manifest.sgx") .output() .await - .map_err(|e| format!("Could not sign manfifest: {}", e))?; + .map_err(|e| handle_gramine_error("Could not sign manfifest", e))?; } Ok(()) @@ -202,7 +202,7 @@ async fn bootstrap(mut gramine_cmd: StdCommand) -> ProverResult<(), String> { let output = gramine_cmd .arg("bootstrap") .output() - .map_err(|e| format!("Could not run SGX guest boostrap: {}", e))?; + .map_err(|e| handle_gramine_error("Could not run SGX guest boostrap", e))?; print_output(&output, "Sgx bootstrap"); Ok(()) @@ -232,7 +232,7 @@ async fn prove( let output = child .wait_with_output() - .map_err(|e| format!("Could not run SGX guest prover: {}", e))?; + .map_err(|e| handle_gramine_error("Could not run SGX guest prover", e))?; print_output(&output, "Sgx execution"); if !output.status.success() { return ProverResult::Err(ProverError::GuestError(output.status.to_string())); @@ -267,6 +267,17 @@ fn parse_sgx_result(output: Vec) -> ProverResult { Ok(SgxResponse { proof, quote }) } +fn handle_gramine_error(context: &str, err: std::io::Error) -> String { + if let std::io::ErrorKind::NotFound = err.kind() { + format!( + "gramine could not be found, please install gramine first. ({})", + err + ) + } else { + format!("{}: {}", context, err) + } +} + fn print_output(output: &Output, name: &str) { println!( "{} stderr: {}",