From 6f4695ca79db52ab6e900fd9753410d341b1bd17 Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 12 Mar 2024 10:38:16 -0600 Subject: [PATCH 01/10] Introduce native tracer support --- .gitignore | 3 + Cargo.lock | 25 +-- Cargo.toml | 13 +- README.md | 47 +++++- leader/src/cli.rs | 37 ++++- leader/src/client.rs | 70 ++++++++ leader/src/jerigon.rs | 94 ----------- leader/src/main.rs | 33 +++- rpc/Cargo.toml | 7 +- rpc/src/compat.rs | 69 ++++++++ rpc/src/jerigon.rs | 58 +++++++ rpc/src/main.rs | 80 ++++++++- rpc/src/metadata.rs | 109 ++++++++++++ rpc/src/native/block.rs | 34 ++++ rpc/src/native/mod.rs | 32 ++++ rpc/src/native/state.rs | 193 ++++++++++++++++++++++ rpc/src/native/txn.rs | 355 ++++++++++++++++++++++++++++++++++++++++ rpc/src/retry.rs | 148 +++++++++++++++++ tools/debug_block.sh | 42 +++++ tools/debug_blocks.sh | 31 ++++ tools/prove_blocks.sh | 65 ++++++++ 21 files changed, 1429 insertions(+), 116 deletions(-) create mode 100644 leader/src/client.rs delete mode 100644 leader/src/jerigon.rs create mode 100644 rpc/src/compat.rs create mode 100644 rpc/src/jerigon.rs create mode 100644 rpc/src/metadata.rs create mode 100644 rpc/src/native/block.rs create mode 100644 rpc/src/native/mod.rs create mode 100644 rpc/src/native/state.rs create mode 100644 rpc/src/native/txn.rs create mode 100644 rpc/src/retry.rs create mode 100755 tools/debug_block.sh create mode 100755 tools/debug_blocks.sh create mode 100755 tools/prove_blocks.sh diff --git a/.gitignore b/.gitignore index b50b613e..7fd20496 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ verifier_state_* # Ignore IntelliJ IDEA/RustRover/Clion metadata .idea/ + +# System files +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 64740e5f..9939ad20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,9 +65,11 @@ dependencies = [ "alloy-core", "alloy-eips", "alloy-genesis", + "alloy-json-rpc", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", + "alloy-rpc-types-trace", "alloy-serde", "alloy-transport", "alloy-transport-http", @@ -106,6 +108,7 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", + "alloy-rlp", "alloy-sol-types", ] @@ -1911,8 +1914,8 @@ dependencies = [ [[package]] name = "evm_arithmetization" -version = "0.2.0" -source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" +version = "0.1.3" +source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" dependencies = [ "anyhow", "bytes", @@ -2944,8 +2947,8 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "mpt_trie" -version = "0.3.0" -source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" +version = "0.2.1" +source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" dependencies = [ "bytes", "enum-as-inner", @@ -3687,8 +3690,8 @@ dependencies = [ [[package]] name = "proof_gen" -version = "0.2.0" -source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" +version = "0.1.3" +source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" dependencies = [ "ethereum-types", "evm_arithmetization", @@ -3989,12 +3992,14 @@ dependencies = [ "hex", "hex-literal", "itertools 0.13.0", + "mpt_trie", "primitive-types 0.12.2", "prover", "serde", "serde_json", "thiserror", "tokio", + "tower", "trace_decoder", "tracing", "tracing-subscriber", @@ -4922,8 +4927,8 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "trace_decoder" -version = "0.4.0" -source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" +version = "0.3.1" +source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" dependencies = [ "bytes", "ciborium", @@ -5092,9 +5097,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index 8433d332..a776f6a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,13 @@ thiserror = "1.0.50" futures = "0.3.29" keccak-hash = "0.10.0" alloy = { git = "https://github.com/alloy-rs/alloy", features = [ + "consensus", + "json-rpc", + "rlp", + "rpc", + "rpc-client", "rpc-types-eth", + "rpc-types-trace", "providers", "transports", "transport-http", @@ -26,9 +32,10 @@ alloy = { git = "https://github.com/alloy-rs/alloy", features = [ # zk-evm dependencies plonky2 = "0.2.2" -evm_arithmetization = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } -trace_decoder = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } -proof_gen = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } +evm_arithmetization = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } +mpt_trie = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } +trace_decoder = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } +proof_gen = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } [workspace.package] edition = "2021" diff --git a/README.md b/README.md index 39329809..771a9f71 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Usage: leader [OPTIONS] Commands: stdio Reads input from stdin and writes output to stdout jerigon Reads input from a Jerigon node and writes output to stdout + native Reads input from a native node and writes output to stdout http Reads input from HTTP and writes output to a directory help Print this message or the help of the given subcommand(s) @@ -218,10 +219,14 @@ Options: The previous proof output -o, --proof-output-path If provided, write the generated proof to this file instead of stdout + -s, --save-inputs-on-error + If true, save the public inputs to disk on error + --backoff + Backoff in milliseconds for request retries [default: 0] + --max-retries + The maximum number of retries [default: 0] -h, --help Print help - -s, --save-inputs-on-error - If provided, save the public inputs to disk on error ``` Prove a block. @@ -230,6 +235,44 @@ Prove a block. cargo r --release --bin leader -- -r in-memory jerigon -u -b 16 > ./output/proof_16.json ``` +### Native + +The native command reads proof input from a native node and writes output to stdout. + +``` +cargo r --release --bin leader native --help + +Reads input from a native node and writes output to stdout + +Usage: leader native [OPTIONS] --rpc-url --block-number + +Options: + -u, --rpc-url + + -b, --block-number + The block number for which to generate a proof + -c, --checkpoint-block-number + The checkpoint block number [default: 0] + -f, --previous-proof + The previous proof output + -o, --proof-output-path + If provided, write the generated proof to this file instead of stdout + -s, --save-inputs-on-error + If true, save the public inputs to disk on error + --backoff + Backoff in milliseconds for request retries [default: 0] + --max-retries + The maximum number of retries [default: 0] + -h, --help + Print help +``` + +Prove a block. + +```bash +cargo r --release --bin leader -- -r in-memory native -u -b 16 > ./output/proof_16.json +``` + ### HTTP The HTTP command reads proof input from HTTP and writes output to a directory. diff --git a/leader/src/cli.rs b/leader/src/cli.rs index 96f2c072..bc318fac 100644 --- a/leader/src/cli.rs +++ b/leader/src/cli.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use alloy::transports::http::reqwest::Url; use clap::{Parser, Subcommand, ValueHint}; use common::prover_state::cli::CliProverStateConfig; @@ -33,7 +34,35 @@ pub(crate) enum Command { Jerigon { // The Jerigon RPC URL. #[arg(long, short = 'u', value_hint = ValueHint::Url)] - rpc_url: String, + rpc_url: Url, + /// The block number for which to generate a proof. + #[arg(short, long)] + block_number: u64, + /// The checkpoint block number. + #[arg(short, long, default_value_t = 0)] + checkpoint_block_number: u64, + /// The previous proof output. + #[arg(long, short = 'f', value_hint = ValueHint::FilePath)] + previous_proof: Option, + /// If provided, write the generated proof to this file instead of + /// stdout. + #[arg(long, short = 'o', value_hint = ValueHint::FilePath)] + proof_output_path: Option, + /// If true, save the public inputs to disk on error. + #[arg(short, long, default_value_t = false)] + save_inputs_on_error: bool, + /// Backoff in milliseconds for request retries + #[arg(long, default_value_t = 0)] + backoff: u64, + /// The maximum number of retries + #[arg(long, default_value_t = 0)] + max_retries: u32, + }, + /// Reads input from a native node and writes output to stdout. + Native { + // The native RPC URL. + #[arg(long, short = 'u', value_hint = ValueHint::Url)] + rpc_url: Url, /// The block interval for which to generate a proof. #[arg(long, short = 'i')] block_interval: String, @@ -63,6 +92,12 @@ pub(crate) enum Command { default_value_t = false )] keep_intermediate_proofs: bool, + /// Backoff in milliseconds for request retries + #[arg(long, default_value_t = 0)] + backoff: u64, + /// The maximum number of retries + #[arg(long, default_value_t = 0)] + max_retries: u32, }, /// Reads input from HTTP and writes output to a directory. Http { diff --git a/leader/src/client.rs b/leader/src/client.rs new file mode 100644 index 00000000..ff128c48 --- /dev/null +++ b/leader/src/client.rs @@ -0,0 +1,70 @@ +use std::{ + fs::{create_dir_all, File}, + io::Write, + path::PathBuf, + sync::Arc, +}; + +use alloy::transports::http::reqwest::Url; +use paladin::runtime::Runtime; +use proof_gen::types::PlonkyProofIntern; +use rpc::retry::build_http_retry_provider; + +/// The main function for the jerigon mode. +#[allow(clippy::too_many_arguments)] +pub(crate) async fn rpc_main( + rpc_type: &str, + rpc_url: Url, + runtime: Runtime, + block_number: u64, + checkpoint_block_number: u64, + previous: Option, + proof_output_path_opt: Option, + save_inputs_on_error: bool, + backoff: u64, + max_retries: u32, +) -> anyhow::Result<()> { + let prover_input = match rpc_type { + "jerigon" => { + rpc::jerigon::prover_input( + build_http_retry_provider(rpc_url, backoff, max_retries), + block_number.into(), + checkpoint_block_number.into(), + ) + .await? + } + "native" => { + rpc::native::prover_input( + Arc::new(build_http_retry_provider(rpc_url, backoff, max_retries)), + block_number.into(), + checkpoint_block_number.into(), + ) + .await? + } + _ => unreachable!(), + }; + + let proof = prover_input + .prove(&runtime, previous, save_inputs_on_error) + .await; + runtime.close().await?; + + let proof = serde_json::to_vec(&proof?.intern)?; + write_proof(proof, proof_output_path_opt) +} + +fn write_proof(proof: Vec, proof_output_path_opt: Option) -> anyhow::Result<()> { + match proof_output_path_opt { + Some(p) => { + if let Some(parent) = p.parent() { + create_dir_all(parent)?; + } + + let mut f = File::create(p)?; + f.write_all(&proof)?; + } + None => std::io::stdout().write_all(&proof)?, + } + + Ok(()) +} diff --git a/leader/src/jerigon.rs b/leader/src/jerigon.rs deleted file mode 100644 index a19dd45c..00000000 --- a/leader/src/jerigon.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::io::Write; -use std::path::PathBuf; - -use alloy::providers::RootProvider; -use anyhow::Result; -use common::block_interval::BlockInterval; -use common::fs::generate_block_proof_file_name; -use paladin::runtime::Runtime; -use proof_gen::proof_types::GeneratedBlockProof; -use tracing::{error, info, warn}; - -#[derive(Debug, Default)] -pub struct ProofParams { - pub checkpoint_block_number: u64, - pub previous_proof: Option, - pub proof_output_dir: Option, - pub save_inputs_on_error: bool, - pub keep_intermediate_proofs: bool, -} - -/// The main function for the jerigon mode. -pub(crate) async fn jerigon_main( - runtime: Runtime, - rpc_url: &str, - block_interval: BlockInterval, - mut params: ProofParams, -) -> Result<()> { - let prover_input = rpc::prover_input( - RootProvider::new_http(rpc_url.parse()?), - block_interval, - params.checkpoint_block_number.into(), - ) - .await?; - - if cfg!(feature = "test_only") { - info!("All proof witnesses have been generated successfully."); - } else { - info!("All proofs have been generated successfully."); - } - - // If `keep_intermediate_proofs` is not set we only keep the last block - // proof from the interval. It contains all the necessary information to - // verify the whole sequence. - let proved_blocks = prover_input - .prove( - &runtime, - params.previous_proof.take(), - params.save_inputs_on_error, - params.proof_output_dir.clone(), - ) - .await; - runtime.close().await?; - let proved_blocks = proved_blocks?; - - if params.keep_intermediate_proofs { - if params.proof_output_dir.is_some() { - // All proof files (including intermediary) are written to disk and kept - warn!("Skipping cleanup, intermediate proof files are kept"); - } else { - // Output all proofs to stdout - std::io::stdout().write_all(&serde_json::to_vec( - &proved_blocks - .into_iter() - .filter_map(|(_, block)| block) - .collect::>(), - )?)?; - } - } else if let Some(proof_output_dir) = params.proof_output_dir.as_ref() { - // Remove intermediary proof files - proved_blocks - .into_iter() - .rev() - .skip(1) - .map(|(block_number, _)| { - generate_block_proof_file_name(&proof_output_dir.to_str(), block_number) - }) - .for_each(|path| { - if let Err(e) = std::fs::remove_file(path) { - error!("Failed to remove intermediate proof file: {e}"); - } - }); - } else { - // Output only last proof to stdout - if let Some(last_block) = proved_blocks - .into_iter() - .filter_map(|(_, block)| block) - .last() - { - std::io::stdout().write_all(&serde_json::to_vec(&last_block)?)?; - } - } - - Ok(()) -} diff --git a/leader/src/main.rs b/leader/src/main.rs index ab4daba0..af0a9d7d 100644 --- a/leader/src/main.rs +++ b/leader/src/main.rs @@ -15,9 +15,9 @@ use crate::jerigon::{jerigon_main, ProofParams}; use crate::utils::get_package_version; mod cli; +mod client; mod http; mod init; -mod jerigon; mod stdio; mod utils; @@ -101,6 +101,34 @@ async fn main() -> Result<()> { save_inputs_on_error, block_time, keep_intermediate_proofs, + backoff, + max_retries, + } => { + let previous_proof = get_previous_proof(previous_proof)?; + + client::rpc_main( + "jerigon", + rpc_url, + runtime, + block_number, + checkpoint_block_number, + previous_proof, + proof_output_path, + save_inputs_on_error, + backoff, + max_retries, + ) + .await?; + } + Command::Native { + rpc_url, + block_number, + checkpoint_block_number, + previous_proof, + proof_output_path, + save_inputs_on_error, + backoff, + max_retries, } => { let previous_proof = get_previous_proof(previous_proof)?; let mut block_interval = BlockInterval::new(&block_interval)?; @@ -116,7 +144,6 @@ async fn main() -> Result<()> { info!("Proving interval {block_interval}"); jerigon_main( runtime, - &rpc_url, block_interval, ProofParams { checkpoint_block_number, @@ -125,6 +152,8 @@ async fn main() -> Result<()> { save_inputs_on_error, keep_intermediate_proofs, }, + backoff, + max_retries, ) .await?; } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 330b0643..477b739e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -18,6 +18,7 @@ trace_decoder = { workspace = true } serde_json = { workspace = true } clap = { workspace = true } evm_arithmetization = { workspace = true } +mpt_trie = { workspace = true } thiserror = { workspace = true } alloy.workspace = true futures = { workspace = true } @@ -25,6 +26,10 @@ hex = "0.4.3" hex-literal = "0.4.1" itertools = "0.13.0" url = "2.5.0" +__compat_primitive_types = { version = "0.12.2", package = "primitive-types" } +tower = { version = "0.4" , features = ["retry"] } + +# Local dependencies common = { path = "../common" } prover = { path = "../prover" } -__compat_primitive-types = { version = "0.12.2", package = "primitive-types" } + diff --git a/rpc/src/compat.rs b/rpc/src/compat.rs new file mode 100644 index 00000000..4f21cc0a --- /dev/null +++ b/rpc/src/compat.rs @@ -0,0 +1,69 @@ +pub trait ToPrimitive { + fn to_primitive(self) -> Out; +} + +impl ToPrimitive for alloy::primitives::Address { + fn to_primitive(self) -> primitive_types::H160 { + let alloy::primitives::Address(alloy::primitives::FixedBytes(arr)) = self; + primitive_types::H160(arr) + } +} + +impl ToPrimitive for alloy::primitives::B256 { + fn to_primitive(self) -> primitive_types::H256 { + let alloy::primitives::FixedBytes(arr) = self; + primitive_types::H256(arr) + } +} + +impl ToPrimitive<[primitive_types::U256; 8]> for alloy::primitives::Bloom { + fn to_primitive(self) -> [primitive_types::U256; 8] { + let alloy::primitives::Bloom(alloy::primitives::FixedBytes(src)) = self; + // have u8 * 256 + // want U256 * 8 + // (no unsafe, no unstable) + let mut chunks = src.chunks_exact(32); + let dst = core::array::from_fn(|_ix| { + // This is a bit spicy because we're going from an uninterpeted array of bytes + // to wide integers, but we trust this `From` impl to do the right thing + primitive_types::U256::from(<[u8; 32]>::try_from(chunks.next().unwrap()).unwrap()) + }); + assert_eq!(chunks.len(), 0); + dst + } +} + +impl ToPrimitive for alloy::primitives::U256 { + fn to_primitive(self) -> primitive_types::U256 { + primitive_types::U256(self.into_limbs()) + } +} + +impl ToPrimitive>> for Vec { + fn to_primitive(self) -> Vec> { + self.into_iter().map(|x| x.to_vec()).collect() + } +} + +pub trait ToAlloy { + fn to_alloy(self) -> Out; +} + +impl ToAlloy for primitive_types::H160 { + fn to_alloy(self) -> alloy::primitives::Address { + let primitive_types::H160(arr) = self; + alloy::primitives::Address(alloy::primitives::FixedBytes(arr)) + } +} + +impl ToAlloy for primitive_types::H256 { + fn to_alloy(self) -> alloy::primitives::StorageKey { + let primitive_types::H256(arr) = self; + alloy::primitives::FixedBytes(arr) + } +} + +#[test] +fn bloom() { + let _did_not_panic = alloy::primitives::Bloom::ZERO.to_primitive(); +} diff --git a/rpc/src/jerigon.rs b/rpc/src/jerigon.rs new file mode 100644 index 00000000..3ac6670c --- /dev/null +++ b/rpc/src/jerigon.rs @@ -0,0 +1,58 @@ +use alloy::{providers::Provider, rpc::types::eth::BlockId, transports::Transport}; +use anyhow::Context as _; +use itertools::{Either, Itertools as _}; +use prover::ProverInput; +use serde::Deserialize; +use serde_json::json; +use trace_decoder::trace_protocol::{BlockTrace, BlockTraceTriePreImages, TxnInfo}; + +use super::metadata::fetch_other_block_data; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +#[allow(clippy::large_enum_variant)] +enum ZeroTrace { + Result(TxnInfo), + BlockWitness(BlockTraceTriePreImages), +} + +/// Fetches the prover input for the given BlockId. +pub async fn prover_input( + provider: ProviderT, + target_block_id: BlockId, + checkpoint_block_id: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + // Grab trace information + ///////////////////////// + let traces = provider + .raw_request::<_, Vec>( + "debug_traceBlockByNumber".into(), + (target_block_id, json!({"tracer": "zeroTracer"})), + ) + .await?; + + let (txn_info, mut pre_images) = + traces + .into_iter() + .partition_map::, Vec<_>, _, _, _>(|it| match it { + ZeroTrace::Result(it) => Either::Left(it), + ZeroTrace::BlockWitness(it) => Either::Right(it), + }); + + let other_data = fetch_other_block_data(provider, target_block_id, checkpoint_block_id).await?; + + // Assemble + /////////// + Ok(ProverInput { + block_trace: BlockTrace { + trie_pre_images: pre_images.pop().context("trace had no BlockWitness")?, + code_db: None, + txn_info, + }, + other_data, + }) +} diff --git a/rpc/src/main.rs b/rpc/src/main.rs index bd84b113..87aa7d0f 100644 --- a/rpc/src/main.rs +++ b/rpc/src/main.rs @@ -1,15 +1,21 @@ -use std::io; +use std::{io, sync::Arc}; +<<<<<<< HEAD use alloy::{providers::RootProvider, rpc::types::eth::BlockId}; use clap::{Parser, ValueHint}; use common::block_interval::BlockInterval; +======= +use clap::{Parser, ValueEnum, ValueHint}; +use rpc::retry::build_http_retry_provider; +>>>>>>> 7cd3d62 (Introduce native tracer support) use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; #[derive(Parser)] -pub enum Args { - /// Fetch and generate prover input from the RPC endpoint. +pub enum Cli { + /// Fetch and generate prover input from the RPC endpoint Fetch { +<<<<<<< HEAD // Starting block of interval to fetch #[arg(short, long)] start_block: u64, @@ -23,9 +29,73 @@ pub enum Args { /// block before the `start_block` is the checkpoint #[arg(short, long)] checkpoint_block_number: Option, +======= + /// The RPC URL + #[arg(short = 'u', long, value_hint = ValueHint::Url)] + rpc_url: Url, + /// The RPC Tracer Type + #[arg(short = 't', long, default_value = "jerigon")] + rpc_type: RpcType, + /// The block number + #[arg(short, long)] + block_number: u64, + /// The checkpoint block number + #[arg(short, long, default_value_t = 0)] + checkpoint_block_number: u64, + /// Backoff in milliseconds for request retries + #[arg(long, default_value_t = 0)] + backoff: u64, + /// The maximum number of retries + #[arg(long, default_value_t = 0)] + max_retries: u32, +>>>>>>> 7cd3d62 (Introduce native tracer support) }, } +/// The RPC type. +#[derive(ValueEnum, Clone)] +pub enum RpcType { + Jerigon, + Native, +} + +impl Cli { + /// Execute the cli command. + pub async fn execute(self) -> anyhow::Result<()> { + match self { + Self::Fetch { + rpc_url, + rpc_type, + block_number, + checkpoint_block_number, + backoff, + max_retries, + } => { + let prover_input = match rpc_type { + RpcType::Jerigon => { + rpc::jerigon::prover_input( + build_http_retry_provider(rpc_url, backoff, max_retries), + block_number.into(), + checkpoint_block_number.into(), + ) + .await? + } + RpcType::Native => { + rpc::native::prover_input( + Arc::new(build_http_retry_provider(rpc_url, backoff, max_retries)), + block_number.into(), + checkpoint_block_number.into(), + ) + .await? + } + }; + serde_json::to_writer_pretty(io::stdout(), &prover_input)?; + } + } + Ok(()) + } +} + #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::Registry::default() @@ -37,6 +107,7 @@ async fn main() -> anyhow::Result<()> { ) .init(); +<<<<<<< HEAD let Args::Fetch { start_block, end_block, @@ -57,5 +128,8 @@ async fn main() -> anyhow::Result<()> { serde_json::to_writer_pretty(io::stdout(), &prover_input.blocks)?; +======= + Cli::parse().execute().await?; +>>>>>>> 7cd3d62 (Introduce native tracer support) Ok(()) } diff --git a/rpc/src/metadata.rs b/rpc/src/metadata.rs new file mode 100644 index 00000000..9f1c15ad --- /dev/null +++ b/rpc/src/metadata.rs @@ -0,0 +1,109 @@ +use alloy::{ + providers::Provider, + rpc::types::eth::{BlockId, BlockTransactionsKind, Withdrawal}, + transports::Transport, +}; +use anyhow::Context as _; +use evm_arithmetization::proof::{BlockHashes, BlockMetadata}; +use futures::{StreamExt as _, TryStreamExt as _}; +use trace_decoder::types::{BlockLevelData, OtherBlockData}; + +use super::compat::ToPrimitive; + +pub async fn fetch_other_block_data( + provider: ProviderT, + target_block_id: BlockId, + checkpoint_block_id: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let target_block = provider + .get_block(target_block_id, BlockTransactionsKind::Hashes) + .await? + .context("target block does not exist")?; + let target_block_number = target_block + .header + .number + .context("target block is missing field `number`")?; + let chain_id = provider.get_chain_id().await?; + let checkpoint_state_trie_root = provider + .get_block(checkpoint_block_id, BlockTransactionsKind::Hashes) + .await? + .context("checkpoint block does not exist")? + .header + .state_root; + + let mut prev_hashes = [alloy::primitives::B256::ZERO; 256]; + let concurrency = prev_hashes.len(); + futures::stream::iter( + prev_hashes + .iter_mut() + .rev() // fill RTL + .zip(std::iter::successors(Some(target_block_number), |it| { + it.checked_sub(1) + })) + .map(|(dst, n)| { + let provider = &provider; + async move { + let block = provider + .get_block(n.into(), BlockTransactionsKind::Hashes) + .await + .context("couldn't get block")? + .context("no such block")?; + *dst = block.header.parent_hash; + anyhow::Ok(()) + } + }), + ) + .buffered(concurrency) + .try_collect::<()>() + .await + .context("couldn't fill previous hashes")?; + + let other_data = OtherBlockData { + b_data: BlockLevelData { + b_meta: BlockMetadata { + block_beneficiary: target_block.header.miner.to_primitive(), + block_timestamp: target_block.header.timestamp.into(), + block_number: target_block_number.into(), + block_difficulty: target_block.header.difficulty.into(), + block_random: target_block + .header + .mix_hash + .context("target block is missing field `mix_hash`")? + .to_primitive(), + block_gaslimit: target_block.header.gas_limit.into(), + block_chain_id: chain_id.into(), + block_base_fee: target_block + .header + .base_fee_per_gas + .context("target block is missing field `base_fee_per_gas`")? + .into(), + block_gas_used: target_block.header.gas_used.into(), + block_bloom: target_block.header.logs_bloom.to_primitive(), + }, + b_hashes: BlockHashes { + prev_hashes: prev_hashes.map(|it| it.to_primitive()).into(), + cur_hash: target_block + .header + .hash + .context("target block is missing field `hash`")? + .to_primitive(), + }, + withdrawals: target_block + .withdrawals + .into_iter() + .flatten() + .map( + |Withdrawal { + address, amount, .. + }| { (address.to_primitive(), amount.into()) }, + ) + .collect(), + }, + checkpoint_state_trie_root: checkpoint_state_trie_root.to_primitive(), + }; + Ok(other_data) +} diff --git a/rpc/src/native/block.rs b/rpc/src/native/block.rs new file mode 100644 index 00000000..7ba57f5c --- /dev/null +++ b/rpc/src/native/block.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; + +use alloy::{ + providers::Provider, + rpc::types::eth::{BlockId, BlockTransactionsKind}, + transports::Transport, +}; +use anyhow::Context as _; +use trace_decoder::trace_protocol::BlockTrace; + +/// Processes the block with the given block number and returns the block trace. +pub async fn process_block_trace( + provider: Arc, + block_number: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let block = provider + .get_block(block_number, BlockTransactionsKind::Full) + .await? + .context("target block does not exist")?; + + let (code_db, txn_info) = super::txn::process_transactions(&block, provider.clone()).await?; + let trie_pre_images = + super::state::process_state_witness(provider.clone(), block, &txn_info).await?; + + Ok(BlockTrace { + txn_info, + code_db: Option::from(code_db).filter(|x| !x.is_empty()), + trie_pre_images, + }) +} diff --git a/rpc/src/native/mod.rs b/rpc/src/native/mod.rs new file mode 100644 index 00000000..499c01b2 --- /dev/null +++ b/rpc/src/native/mod.rs @@ -0,0 +1,32 @@ +use std::{collections::HashMap, sync::Arc}; + +use alloy::{providers::Provider, rpc::types::eth::BlockId, transports::Transport}; +use futures::try_join; +use prover::ProverInput; + +mod block; +mod state; +mod txn; + +type CodeDb = HashMap>; + +/// Fetches the prover input for the given BlockId. +pub async fn prover_input( + provider: Arc, + block_number: BlockId, + checkpoint_block_number: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let (block_trace, other_data) = try_join!( + block::process_block_trace(provider.clone(), block_number), + crate::metadata::fetch_other_block_data(provider, block_number, checkpoint_block_number,) + )?; + + Ok(ProverInput { + block_trace, + other_data, + }) +} diff --git a/rpc/src/native/state.rs b/rpc/src/native/state.rs new file mode 100644 index 00000000..52021f8a --- /dev/null +++ b/rpc/src/native/state.rs @@ -0,0 +1,193 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +use alloy::{ + primitives::{keccak256, Address, StorageKey, B256}, + providers::Provider, + rpc::types::eth::{Block, BlockTransactionsKind, EIP1186AccountProofResponse}, + transports::Transport, +}; +use anyhow::Context as _; +use futures::future::{try_join, try_join_all}; +use mpt_trie::{builder::PartialTrieBuilder, partial_trie::HashedPartialTrie}; +use trace_decoder::trace_protocol::{ + BlockTraceTriePreImages, SeparateStorageTriesPreImage, SeparateTriePreImage, + SeparateTriePreImages, TrieDirect, TxnInfo, +}; + +use crate::compat::{ToAlloy, ToPrimitive}; + +/// Processes the state witness for the given block. +pub async fn process_state_witness( + provider: Arc, + block: Block, + txn_infos: &[TxnInfo], +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let state_access = process_states_access(txn_infos, &block)?; + + let block_number = block + .header + .number + .context("Block number not returned with block")?; + let prev_state_root = provider + .get_block((block_number - 1).into(), BlockTransactionsKind::Hashes) + .await? + .context("Failed to get previous block")? + .header + .state_root; + + let (state, storage_proofs) = + generate_state_witness(prev_state_root, state_access, provider, block_number).await?; + + Ok(BlockTraceTriePreImages::Separate(SeparateTriePreImages { + state: SeparateTriePreImage::Direct(TrieDirect(state.build())), + storage: SeparateStorageTriesPreImage::MultipleTries( + storage_proofs + .into_iter() + .map(|(a, m)| { + ( + a.to_primitive(), + SeparateTriePreImage::Direct(TrieDirect(m.build())), + ) + }) + .collect(), + ), + })) +} + +/// Iterate over the tx_infos and process the state access for each address. +/// Also includes the state access for withdrawals and the block author. +/// +/// Returns a map from address to the set of storage keys accessed by that +/// address. +pub fn process_states_access( + tx_infos: &[TxnInfo], + block: &Block, +) -> anyhow::Result>> { + let mut state_access = HashMap::>::new(); + + if let Some(w) = block.withdrawals.as_ref() { + w.iter().for_each(|w| { + state_access.insert(w.address, Default::default()); + }) + }; + state_access.insert(block.header.miner, Default::default()); + + for txn_info in tx_infos { + for (address, trace) in txn_info.traces.iter() { + let address_storage_access = state_access.entry((*address).to_alloy()).or_default(); + + if let Some(read_keys) = trace.storage_read.as_ref() { + address_storage_access.extend(read_keys.iter().copied().map(ToAlloy::to_alloy)); + } + + if let Some(written_keys) = trace.storage_written.as_ref() { + address_storage_access.extend(written_keys.keys().copied().map(ToAlloy::to_alloy)); + } + } + } + + Ok(state_access) +} + +/// Generates the state witness for the given block. +async fn generate_state_witness( + prev_state_root: B256, + accounts_state: HashMap>, + provider: Arc, + block_number: u64, +) -> anyhow::Result<( + PartialTrieBuilder, + HashMap>, +)> +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let mut state = PartialTrieBuilder::new(prev_state_root.to_primitive(), Default::default()); + let mut storage_proofs = HashMap::>::new(); + + let (account_proofs, next_account_proofs) = + fetch_proof_data(accounts_state, provider, block_number).await?; + + // Insert account proofs + for (address, proof) in account_proofs.into_iter() { + state.insert_proof(proof.account_proof.to_primitive()); + + let storage_mpt = + storage_proofs + .entry(keccak256(address)) + .or_insert(PartialTrieBuilder::new( + proof.storage_hash.to_primitive(), + Default::default(), + )); + for proof in proof.storage_proof { + storage_mpt.insert_proof(proof.proof.to_primitive()); + } + } + + // Insert short node variants from next proofs + for (address, proof) in next_account_proofs.into_iter() { + state.insert_short_node_variants_from_proof(proof.account_proof.to_primitive()); + + if let Some(storage_mpt) = storage_proofs.get_mut(&keccak256(address)) { + for proof in proof.storage_proof { + storage_mpt.insert_short_node_variants_from_proof(proof.proof.to_primitive()); + } + } + } + + Ok((state, storage_proofs)) +} + +/// Fetches the proof data for the given accounts and associated storage keys. +async fn fetch_proof_data( + accounts_state: HashMap>, + provider: Arc, + block_number: u64, +) -> anyhow::Result<( + Vec<(Address, EIP1186AccountProofResponse)>, + Vec<(Address, EIP1186AccountProofResponse)>, +)> +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let account_proofs_fut = accounts_state + .clone() + .into_iter() + .map(|(address, keys)| { + let provider = provider.clone(); + async move { + let proof = provider + .get_proof(address, keys.into_iter().collect()) + .block_id((block_number - 1).into()) + .await + .context("Failed to get proof for account")?; + anyhow::Result::Ok((address, proof)) + } + }) + .collect::>(); + + let next_account_proofs_fut = accounts_state.into_iter().map(|(address, keys)| { + let provider = provider.clone(); + async move { + let proof = provider + .get_proof(address, keys.into_iter().collect()) + .block_id(block_number.into()) + .await + .context("Failed to get proof for account")?; + anyhow::Result::Ok((address, proof)) + } + }); + + try_join( + try_join_all(account_proofs_fut), + try_join_all(next_account_proofs_fut), + ) + .await +} diff --git a/rpc/src/native/txn.rs b/rpc/src/native/txn.rs new file mode 100644 index 00000000..dd9c8284 --- /dev/null +++ b/rpc/src/native/txn.rs @@ -0,0 +1,355 @@ +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use alloy::{ + primitives::{keccak256, Address, B256}, + providers::{ + ext::DebugApi as _, + network::{eip2718::Encodable2718, Ethereum, Network}, + Provider, + }, + rpc::types::{ + eth::Transaction, + eth::{AccessList, Block}, + trace::geth::{ + AccountState, DiffMode, GethDebugBuiltInTracerType, GethTrace, PreStateConfig, + PreStateFrame, PreStateMode, + }, + trace::geth::{GethDebugTracerType, GethDebugTracingOptions}, + }, + transports::Transport, +}; +use anyhow::Context as _; +use futures::stream::{FuturesOrdered, TryStreamExt}; +use primitive_types::{H256, U256}; +use trace_decoder::trace_protocol::{ContractCodeUsage, TxnInfo, TxnMeta, TxnTrace}; + +use super::CodeDb; +use crate::compat::ToPrimitive; + +/// Processes the transactions in the given block and updates the code db. +pub(super) async fn process_transactions( + block: &Block, + provider: Arc, +) -> anyhow::Result<(CodeDb, Vec)> +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let mut futures_ordered = FuturesOrdered::new(); + + for tx in block + .transactions + .as_transactions() + .context("No transactions in block")? + { + let provider = provider.clone(); + futures_ordered + .push_back(async move { super::txn::process_transaction(provider, tx).await }); + } + + let result = futures_ordered + .try_collect::>() + .await? + .into_iter() + .fold( + (HashMap::new(), Vec::new()), + |(mut code_db, mut txn_infos), (tx_code_db, txn_info)| { + code_db.extend(tx_code_db); + txn_infos.push(txn_info); + (code_db, txn_infos) + }, + ); + + Ok(result) +} + +/// Processes the transaction with the given transaction hash and updates the +/// accounts state. +async fn process_transaction( + provider: Arc, + tx: &Transaction, +) -> anyhow::Result<(CodeDb, TxnInfo)> +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let (tx_receipt, pre_trace, diff_trace) = fetch_tx_data(provider, &tx.hash).await?; + let tx_receipt = tx_receipt.map_inner(rlp::map_receipt_envelope); + let access_list = parse_access_list(tx.access_list.as_ref()); + + let tx_meta = TxnMeta { + byte_code: ::TxEnvelope::try_from(tx.clone())?.encoded_2718(), + new_txn_trie_node_byte: vec![], + new_receipt_trie_node_byte: alloy::rlp::encode(tx_receipt.inner), + gas_used: tx_receipt.gas_used as u64, + }; + + let (code_db, tx_traces) = match (pre_trace, diff_trace) { + ( + GethTrace::PreStateTracer(PreStateFrame::Default(read)), + GethTrace::PreStateTracer(PreStateFrame::Diff(diff)), + ) => process_tx_traces(access_list, read, diff).await?, + _ => unreachable!(), + }; + + Ok(( + code_db, + TxnInfo { + meta: tx_meta, + traces: tx_traces + .into_iter() + .map(|(k, v)| (k.to_primitive(), v)) + .collect(), + }, + )) +} + +/// Fetches the transaction data for the given transaction hash. +async fn fetch_tx_data( + provider: Arc, + tx_hash: &B256, +) -> anyhow::Result<(::ReceiptResponse, GethTrace, GethTrace), anyhow::Error> +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let tx_receipt_fut = provider.get_transaction_receipt(*tx_hash); + let pre_trace_fut = provider.debug_trace_transaction(*tx_hash, prestate_tracing_options(false)); + let diff_trace_fut = provider.debug_trace_transaction(*tx_hash, prestate_tracing_options(true)); + + let (tx_receipt, pre_trace, diff_trace) = + futures::try_join!(tx_receipt_fut, pre_trace_fut, diff_trace_fut,)?; + + Ok(( + tx_receipt.context("Transaction receipt not found.")?, + pre_trace, + diff_trace, + )) +} + +/// Parse the access list data into a hashmap. +fn parse_access_list(access_list: Option<&AccessList>) -> HashMap> { + let mut result = HashMap::new(); + + if let Some(access_list) = access_list { + for item in access_list.0.clone() { + result + .entry(item.address) + .or_insert_with(HashSet::new) + .extend(item.storage_keys.into_iter().map(ToPrimitive::to_primitive)); + } + } + + result +} + +/// Processes the transaction traces and updates the accounts state. +async fn process_tx_traces( + mut access_list: HashMap>, + read_trace: PreStateMode, + diff_trace: DiffMode, +) -> anyhow::Result<(CodeDb, HashMap)> { + let DiffMode { + pre: pre_trace, + post: post_trace, + } = diff_trace; + + let addresses: HashSet<_> = read_trace + .0 + .keys() + .chain(post_trace.keys()) + .chain(pre_trace.keys()) + .chain(access_list.keys()) + .copied() + .collect(); + + let mut traces = HashMap::new(); + let mut code_db: CodeDb = HashMap::new(); + + for address in addresses { + let read_state = read_trace.0.get(&address); + let pre_state = pre_trace.get(&address); + let post_state = post_trace.get(&address); + + let balance = post_state.and_then(|x| x.balance.map(ToPrimitive::to_primitive)); + let (storage_read, storage_written) = process_storage( + access_list.remove(&address).unwrap_or_default(), + read_state, + post_state, + pre_state, + ); + let code = process_code(post_state, read_state, &mut code_db).await; + let nonce = process_nonce(post_state, &code); + let self_destructed = process_self_destruct(post_state, pre_state); + + let result = TxnTrace { + balance, + nonce, + storage_read, + storage_written, + code_usage: code, + self_destructed, + }; + + traces.insert(address, result); + } + + Ok((code_db, traces)) +} + +/// Processes the nonce for the given account state. +/// +/// If a contract is created, the nonce is set to 1. +fn process_nonce( + post_state: Option<&AccountState>, + code_usage: &Option, +) -> Option { + post_state + .and_then(|x| x.nonce.map(Into::into)) + .or_else(|| { + if let Some(ContractCodeUsage::Write(_)) = code_usage.as_ref() { + Some(U256::from(1)) + } else { + None + } + }) +} + +/// Processes the storage for the given account state. +/// +/// Returns the storage read and written for the given account in the +/// transaction and updates the storage keys. +fn process_storage( + access_list: HashSet, + acct_state: Option<&AccountState>, + post_acct: Option<&AccountState>, + pre_acct: Option<&AccountState>, +) -> (Option>, Option>) { + let mut storage_read = access_list; + storage_read.extend( + acct_state + .map(|acct| { + acct.storage + .keys() + .copied() + .map(ToPrimitive::to_primitive) + .collect::>() + }) + .unwrap_or_default(), + ); + + let mut storage_written: HashMap = post_acct + .map(|x| { + x.storage + .iter() + .map(|(k, v)| ((*k).to_primitive(), U256::from_big_endian(&v.0))) + .collect() + }) + .unwrap_or_default(); + + // Add the deleted keys to the storage written + if let Some(pre_acct) = pre_acct { + for key in pre_acct.storage.keys() { + storage_written + .entry((*key).to_primitive()) + .or_insert(U256::zero()); + } + }; + + ( + Option::from(storage_read.into_iter().collect::>()).filter(|v| !v.is_empty()), + Option::from(storage_written).filter(|v| !v.is_empty()), + ) +} + +/// Processes the code usage for the given account state. +async fn process_code( + post_state: Option<&AccountState>, + read_state: Option<&AccountState>, + code_db: &mut CodeDb, +) -> Option { + match ( + post_state.and_then(|x| x.code.as_ref()), + read_state.and_then(|x| x.code.as_ref()), + ) { + (Some(post_code), _) => { + let code_hash = keccak256(post_code).to_primitive(); + code_db.insert(code_hash, post_code.to_vec()); + Some(ContractCodeUsage::Write(post_code.to_vec().into())) + } + (_, Some(read_code)) => { + let code_hash = keccak256(read_code).to_primitive(); + code_db.insert(code_hash, read_code.to_vec()); + + Some(ContractCodeUsage::Read(code_hash)) + } + _ => None, + } +} + +/// Processes the self destruct for the given account state. +fn process_self_destruct( + post_state: Option<&AccountState>, + pre_state: Option<&AccountState>, +) -> Option { + if post_state.is_none() && pre_state.is_some() { + Some(true) + } else { + None + } +} + +mod rlp { + use alloy::consensus::{Receipt, ReceiptEnvelope}; + use alloy::rpc::types::eth::ReceiptWithBloom; + + pub fn map_receipt_envelope( + rpc: ReceiptEnvelope, + ) -> ReceiptEnvelope { + match rpc { + ReceiptEnvelope::Legacy(it) => ReceiptEnvelope::Legacy(map_receipt_with_bloom(it)), + ReceiptEnvelope::Eip2930(it) => ReceiptEnvelope::Eip2930(map_receipt_with_bloom(it)), + ReceiptEnvelope::Eip1559(it) => ReceiptEnvelope::Eip1559(map_receipt_with_bloom(it)), + ReceiptEnvelope::Eip4844(it) => ReceiptEnvelope::Eip4844(map_receipt_with_bloom(it)), + other => panic!("unsupported receipt type: {:?}", other), + } + } + fn map_receipt_with_bloom( + rpc: ReceiptWithBloom, + ) -> ReceiptWithBloom { + let ReceiptWithBloom { + receipt: + Receipt { + status, + cumulative_gas_used, + logs, + }, + logs_bloom, + } = rpc; + ReceiptWithBloom { + receipt: Receipt { + status, + cumulative_gas_used, + logs: logs.into_iter().map(|it| it.inner).collect(), + }, + logs_bloom, + } + } +} + +/// Tracing options for the debug_traceTransaction call. +fn prestate_tracing_options(diff_mode: bool) -> GethDebugTracingOptions { + GethDebugTracingOptions { + tracer_config: PreStateConfig { + diff_mode: Some(diff_mode), + } + .into(), + tracer: Some(GethDebugTracerType::BuiltInTracer( + GethDebugBuiltInTracerType::PreStateTracer, + )), + ..GethDebugTracingOptions::default() + } +} diff --git a/rpc/src/retry.rs b/rpc/src/retry.rs new file mode 100644 index 00000000..0c6db229 --- /dev/null +++ b/rpc/src/retry.rs @@ -0,0 +1,148 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use alloy::{ + providers::{ProviderBuilder, RootProvider}, + rpc::{ + client::ClientBuilder, + json_rpc::{RequestPacket, ResponsePacket}, + }, + transports::TransportError, +}; +use tower::{retry::Policy, Layer, Service}; + +#[derive(Debug)] +pub struct RetryPolicy { + backoff: tokio::time::Duration, + retries: u32, + max_retries: u32, +} + +impl Clone for RetryPolicy { + fn clone(&self) -> Self { + Self { + backoff: self.backoff, + retries: self.retries, + max_retries: self.max_retries, + } + } +} + +impl RetryPolicy { + pub fn new(backoff: tokio::time::Duration, max_retries: u32) -> Self { + Self { + backoff, + retries: 0, + max_retries, + } + } + + pub fn backoff(&self) -> tokio::time::Sleep { + tokio::time::sleep(self.backoff) + } +} + +impl Policy for RetryPolicy { + type Future = Pin + Send + 'static>>; + + fn retry( + &self, + _req: &RequestPacket, + result: Result<&ResponsePacket, &TransportError>, + ) -> Option { + // TODO: Use rate-limit specific errors/codes and retry accordingly. + if result.is_err() && self.retries < self.max_retries { + let mut policy = self.clone(); + Some(Box::pin(async move { + policy.backoff().await; + policy.retries += 1; + policy + })) + } else { + None + } + } + + fn clone_request(&self, req: &RequestPacket) -> Option { + Some(req.clone()) + } +} + +/// RetryLayer +pub struct RetryLayer { + policy: RetryPolicy, +} + +impl RetryLayer { + pub const fn new(policy: RetryPolicy) -> Self { + Self { policy } + } +} + +impl Layer for RetryLayer { + type Service = RetryService; + + fn layer(&self, inner: S) -> Self::Service { + RetryService { + inner, + policy: self.policy.clone(), + } + } +} + +/// RetryService +#[derive(Debug, Clone)] +pub struct RetryService { + inner: S, + policy: RetryPolicy, +} + +impl Service for RetryService +where + S: Service + + Send + + 'static + + Clone, + S::Future: Send + 'static, +{ + type Response = ResponsePacket; + type Error = TransportError; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: RequestPacket) -> Self::Future { + let inner = self.inner.clone(); + let mut policy = self.policy.clone(); + + let mut inner = std::mem::replace(&mut self.inner, inner); + Box::pin(async move { + let mut res = inner.call(req.clone()).await; + + while let Some(new_policy) = policy.retry(&req, res.as_ref()) { + policy = new_policy.await; + res = inner.call(req.clone()).await; + } + + res + }) + } +} + +pub fn build_http_retry_provider( + rpc_url: url::Url, + backoff: u64, + max_retries: u32, +) -> RootProvider> { + let retry_policy = RetryLayer::new(RetryPolicy::new( + tokio::time::Duration::from_millis(backoff), + max_retries, + )); + let client = ClientBuilder::default().layer(retry_policy).http(rpc_url); + ProviderBuilder::new().on_client(client) +} diff --git a/tools/debug_block.sh b/tools/debug_block.sh new file mode 100755 index 00000000..61230dec --- /dev/null +++ b/tools/debug_block.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Args: +# 1 --> Block idx +# 2 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) +# 3 --> Rpc type (eg. jerigon / native) +# 4 --> Backoff time (in milliseconds) for the RPC requests +# 5 --> Number of retries for the RPC requests + +export RUST_BACKTRACE=1 +export RUST_MIN_STACK=8388608 +export RUST_LOG=mpt_trie=info,trace_decoder=info,plonky2=info,evm_arithmetization=trace,leader=info +export RUSTFLAGS='-Ctarget-cpu=native' + +# Speciying smallest ranges, as we won't need them anyway. +export ARITHMETIC_CIRCUIT_SIZE="16..17" +export BYTE_PACKING_CIRCUIT_SIZE="9..10" +export CPU_CIRCUIT_SIZE="12..13" +export KECCAK_CIRCUIT_SIZE="14..15" +export KECCAK_SPONGE_CIRCUIT_SIZE="9..10" +export LOGIC_CIRCUIT_SIZE="12..13" +export MEMORY_CIRCUIT_SIZE="17..18" + +OUTPUT_DIR="debug" +OUT_DUMMY_PROOF_PATH="${OUTPUT_DIR}/b${1}.zkproof" +OUT_LOG_PATH="${OUTPUT_DIR}/b${1}.log" + +echo "Testing block ${1}..." +mkdir -p $OUTPUT_DIR + +cargo r --release --features test_only --bin leader -- -n 1 --runtime in-memory "$3" --rpc-url "$2" --block-number "$1" --checkpoint-block-number "$(($1-1))" --proof-output-path $OUT_DUMMY_PROOF_PATH --backoff "${4:-0}" --max-retries "${5:-0}" > $OUT_LOG_PATH 2>&1 +retVal=$? +if [ $retVal -ne 0 ]; then + # Some error occured. + echo "Witness generation for block ${1} errored. See ${OUT_LOG_PATH} for more details." +else + echo "Witness generation for block ${1} succeeded." + # Remove the log / dummy proof on success. + rm $OUT_DUMMY_PROOF_PATH + rm $OUT_LOG_PATH +fi + diff --git a/tools/debug_blocks.sh b/tools/debug_blocks.sh new file mode 100755 index 00000000..767f9ad3 --- /dev/null +++ b/tools/debug_blocks.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Check if the correct number of arguments was provided +if [ "$#" -lt 4 ]; then + echo "Usage: $0 INITIAL_BLOCK NUM_BLOCKS RPC_ENDPOINT RPC_TYPE BACKOFF RETRIES" + exit 1 +fi + +# Read the arguments +# 1 --> initial block number +# 2 --> number of blocks to debug +# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) +# 4 --> Rpc type (eg. jerigon / native) +# 5 --> Backoff time (in milliseconds) for the RPC requests +# 6 --> Number of retries for the RPC requests +INITIAL_BLOCK=$1 +NUM_BLOCKS=$2 +RPC_ENDPOINT=$3 +RPC_TYPE=$4 +BACKOFF=${5:-0} +RETRIES=${6:-0} + +# Get the directory of the current script +script_dir=$(dirname "$0") + +# Loop and call the existing script +for (( i=0; i Start block idx +# 2 --> End block index (inclusive) +# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) +# 4 --> Rpc type (eg. jerigon / native) +# 5 --> Ignore previous proofs (boolean) + +export RUST_BACKTRACE=1 +export RUST_LOG=mpt_trie=info,trace_decoder=info,plonky2=info,evm_arithmetization=trace,leader=info +export RUSTFLAGS='-Ctarget-cpu=native' + +export ARITHMETIC_CIRCUIT_SIZE="16..23" +export BYTE_PACKING_CIRCUIT_SIZE="9..21" +export CPU_CIRCUIT_SIZE="12..25" +export KECCAK_CIRCUIT_SIZE="14..20" +export KECCAK_SPONGE_CIRCUIT_SIZE="9..15" +export LOGIC_CIRCUIT_SIZE="12..18" +export MEMORY_CIRCUIT_SIZE="17..28" + +PROOF_OUTPUT_DIR="proofs" +ALWAYS_WRITE_LOGS=0 # Change this to `1` if you always want logs to be written. + +TOT_BLOCKS=$(($2-$1+1)) +RPC_TYPE=$4 +IGNORE_PREVIOUS_PROOFS=$5 + +echo "Proving blocks ${1}..=${2}... (Total: ${TOT_BLOCKS})" +mkdir -p $PROOF_OUTPUT_DIR + +for ((i=$1; i<=$2; i++)) +do + echo "Proving block ${i}..." + + OUT_PROOF_PATH="${PROOF_OUTPUT_DIR}/b${i}.zkproof" + OUT_LOG_PATH="${PROOF_OUTPUT_DIR}/b${i}.log" + + if [ $IGNORE_PREVIOUS_PROOFS ]; then + # Set checkpoint height to previous block number + prev_proof_num=$((i-1)) + PREV_PROOF_EXTRA_ARG="--checkpoint-block-number ${prev_proof_num}" + else + if [ $i -gt 1 ]; then + prev_proof_num=$((i-1)) + PREV_PROOF_EXTRA_ARG="-f ${PROOF_OUTPUT_DIR}/b${prev_proof_num}.zkproof" + fi + fi + + cargo r --release --bin leader -- --runtime in-memory "$RPC_TYPE" --rpc-url "$3" --block-number $i --proof-output-path $OUT_PROOF_PATH $PREV_PROOF_EXTRA_ARG > $OUT_LOG_PATH 2>&1 + + retVal=$? + if [ $retVal -ne 0 ]; then + # Some error occured. + echo "Block ${i} errored. See ${OUT_LOG_PATH} for more details." + exit $retVal + else + # Remove the log on success if we don't want to keep it. + if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then + rm $OUT_LOG_PATH + fi + fi +done + +echo "Successfully generated ${TOT_BLOCKS} proofs!" \ No newline at end of file From cc81f4d0fd674b725a857ecf1ced212afb684af1 Mon Sep 17 00:00:00 2001 From: frisitano Date: Sun, 9 Jun 2024 18:55:13 +0400 Subject: [PATCH 02/10] remove arc and refactor code --- rpc/src/compat.rs | 52 +++++++++---------- rpc/src/jerigon.rs | 2 +- rpc/src/metadata.rs | 109 ---------------------------------------- rpc/src/native/block.rs | 34 ------------- rpc/src/native/mod.rs | 39 ++++++++++++-- rpc/src/native/state.rs | 55 +++++++++----------- rpc/src/native/txn.rs | 61 +++++++++------------- 7 files changed, 109 insertions(+), 243 deletions(-) delete mode 100644 rpc/src/metadata.rs delete mode 100644 rpc/src/native/block.rs diff --git a/rpc/src/compat.rs b/rpc/src/compat.rs index 4f21cc0a..7fb0a156 100644 --- a/rpc/src/compat.rs +++ b/rpc/src/compat.rs @@ -1,23 +1,23 @@ -pub trait ToPrimitive { - fn to_primitive(self) -> Out; +pub trait Compat { + fn compat(self) -> Out; } -impl ToPrimitive for alloy::primitives::Address { - fn to_primitive(self) -> primitive_types::H160 { +impl Compat<__compat_primitive_types::H160> for alloy::primitives::Address { + fn compat(self) -> __compat_primitive_types::H160 { let alloy::primitives::Address(alloy::primitives::FixedBytes(arr)) = self; - primitive_types::H160(arr) + __compat_primitive_types::H160(arr) } } -impl ToPrimitive for alloy::primitives::B256 { - fn to_primitive(self) -> primitive_types::H256 { +impl Compat<__compat_primitive_types::H256> for alloy::primitives::B256 { + fn compat(self) -> __compat_primitive_types::H256 { let alloy::primitives::FixedBytes(arr) = self; - primitive_types::H256(arr) + __compat_primitive_types::H256(arr) } } -impl ToPrimitive<[primitive_types::U256; 8]> for alloy::primitives::Bloom { - fn to_primitive(self) -> [primitive_types::U256; 8] { +impl Compat<[__compat_primitive_types::U256; 8]> for alloy::primitives::Bloom { + fn compat(self) -> [__compat_primitive_types::U256; 8] { let alloy::primitives::Bloom(alloy::primitives::FixedBytes(src)) = self; // have u8 * 256 // want U256 * 8 @@ -26,44 +26,42 @@ impl ToPrimitive<[primitive_types::U256; 8]> for alloy::primitives::Bloom { let dst = core::array::from_fn(|_ix| { // This is a bit spicy because we're going from an uninterpeted array of bytes // to wide integers, but we trust this `From` impl to do the right thing - primitive_types::U256::from(<[u8; 32]>::try_from(chunks.next().unwrap()).unwrap()) + __compat_primitive_types::U256::from( + <[u8; 32]>::try_from(chunks.next().unwrap()).unwrap(), + ) }); assert_eq!(chunks.len(), 0); dst } } -impl ToPrimitive for alloy::primitives::U256 { - fn to_primitive(self) -> primitive_types::U256 { - primitive_types::U256(self.into_limbs()) +impl Compat<__compat_primitive_types::U256> for alloy::primitives::U256 { + fn compat(self) -> __compat_primitive_types::U256 { + __compat_primitive_types::U256(self.into_limbs()) } } -impl ToPrimitive>> for Vec { - fn to_primitive(self) -> Vec> { +impl Compat>> for Vec { + fn compat(self) -> Vec> { self.into_iter().map(|x| x.to_vec()).collect() } } -pub trait ToAlloy { - fn to_alloy(self) -> Out; -} - -impl ToAlloy for primitive_types::H160 { - fn to_alloy(self) -> alloy::primitives::Address { - let primitive_types::H160(arr) = self; +impl Compat for __compat_primitive_types::H160 { + fn compat(self) -> alloy::primitives::Address { + let __compat_primitive_types::H160(arr) = self; alloy::primitives::Address(alloy::primitives::FixedBytes(arr)) } } -impl ToAlloy for primitive_types::H256 { - fn to_alloy(self) -> alloy::primitives::StorageKey { - let primitive_types::H256(arr) = self; +impl Compat for __compat_primitive_types::H256 { + fn compat(self) -> alloy::primitives::StorageKey { + let __compat_primitive_types::H256(arr) = self; alloy::primitives::FixedBytes(arr) } } #[test] fn bloom() { - let _did_not_panic = alloy::primitives::Bloom::ZERO.to_primitive(); + let _did_not_panic = alloy::primitives::Bloom::ZERO.compat(); } diff --git a/rpc/src/jerigon.rs b/rpc/src/jerigon.rs index 3ac6670c..67998cb2 100644 --- a/rpc/src/jerigon.rs +++ b/rpc/src/jerigon.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use serde_json::json; use trace_decoder::trace_protocol::{BlockTrace, BlockTraceTriePreImages, TxnInfo}; -use super::metadata::fetch_other_block_data; +use super::fetch_other_block_data; #[derive(Deserialize, Debug)] #[serde(rename_all = "snake_case")] diff --git a/rpc/src/metadata.rs b/rpc/src/metadata.rs deleted file mode 100644 index 9f1c15ad..00000000 --- a/rpc/src/metadata.rs +++ /dev/null @@ -1,109 +0,0 @@ -use alloy::{ - providers::Provider, - rpc::types::eth::{BlockId, BlockTransactionsKind, Withdrawal}, - transports::Transport, -}; -use anyhow::Context as _; -use evm_arithmetization::proof::{BlockHashes, BlockMetadata}; -use futures::{StreamExt as _, TryStreamExt as _}; -use trace_decoder::types::{BlockLevelData, OtherBlockData}; - -use super::compat::ToPrimitive; - -pub async fn fetch_other_block_data( - provider: ProviderT, - target_block_id: BlockId, - checkpoint_block_id: BlockId, -) -> anyhow::Result -where - ProviderT: Provider, - TransportT: Transport + Clone, -{ - let target_block = provider - .get_block(target_block_id, BlockTransactionsKind::Hashes) - .await? - .context("target block does not exist")?; - let target_block_number = target_block - .header - .number - .context("target block is missing field `number`")?; - let chain_id = provider.get_chain_id().await?; - let checkpoint_state_trie_root = provider - .get_block(checkpoint_block_id, BlockTransactionsKind::Hashes) - .await? - .context("checkpoint block does not exist")? - .header - .state_root; - - let mut prev_hashes = [alloy::primitives::B256::ZERO; 256]; - let concurrency = prev_hashes.len(); - futures::stream::iter( - prev_hashes - .iter_mut() - .rev() // fill RTL - .zip(std::iter::successors(Some(target_block_number), |it| { - it.checked_sub(1) - })) - .map(|(dst, n)| { - let provider = &provider; - async move { - let block = provider - .get_block(n.into(), BlockTransactionsKind::Hashes) - .await - .context("couldn't get block")? - .context("no such block")?; - *dst = block.header.parent_hash; - anyhow::Ok(()) - } - }), - ) - .buffered(concurrency) - .try_collect::<()>() - .await - .context("couldn't fill previous hashes")?; - - let other_data = OtherBlockData { - b_data: BlockLevelData { - b_meta: BlockMetadata { - block_beneficiary: target_block.header.miner.to_primitive(), - block_timestamp: target_block.header.timestamp.into(), - block_number: target_block_number.into(), - block_difficulty: target_block.header.difficulty.into(), - block_random: target_block - .header - .mix_hash - .context("target block is missing field `mix_hash`")? - .to_primitive(), - block_gaslimit: target_block.header.gas_limit.into(), - block_chain_id: chain_id.into(), - block_base_fee: target_block - .header - .base_fee_per_gas - .context("target block is missing field `base_fee_per_gas`")? - .into(), - block_gas_used: target_block.header.gas_used.into(), - block_bloom: target_block.header.logs_bloom.to_primitive(), - }, - b_hashes: BlockHashes { - prev_hashes: prev_hashes.map(|it| it.to_primitive()).into(), - cur_hash: target_block - .header - .hash - .context("target block is missing field `hash`")? - .to_primitive(), - }, - withdrawals: target_block - .withdrawals - .into_iter() - .flatten() - .map( - |Withdrawal { - address, amount, .. - }| { (address.to_primitive(), amount.into()) }, - ) - .collect(), - }, - checkpoint_state_trie_root: checkpoint_state_trie_root.to_primitive(), - }; - Ok(other_data) -} diff --git a/rpc/src/native/block.rs b/rpc/src/native/block.rs deleted file mode 100644 index 7ba57f5c..00000000 --- a/rpc/src/native/block.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::sync::Arc; - -use alloy::{ - providers::Provider, - rpc::types::eth::{BlockId, BlockTransactionsKind}, - transports::Transport, -}; -use anyhow::Context as _; -use trace_decoder::trace_protocol::BlockTrace; - -/// Processes the block with the given block number and returns the block trace. -pub async fn process_block_trace( - provider: Arc, - block_number: BlockId, -) -> anyhow::Result -where - ProviderT: Provider, - TransportT: Transport + Clone, -{ - let block = provider - .get_block(block_number, BlockTransactionsKind::Full) - .await? - .context("target block does not exist")?; - - let (code_db, txn_info) = super::txn::process_transactions(&block, provider.clone()).await?; - let trie_pre_images = - super::state::process_state_witness(provider.clone(), block, &txn_info).await?; - - Ok(BlockTrace { - txn_info, - code_db: Option::from(code_db).filter(|x| !x.is_empty()), - trie_pre_images, - }) -} diff --git a/rpc/src/native/mod.rs b/rpc/src/native/mod.rs index 499c01b2..2dbd2107 100644 --- a/rpc/src/native/mod.rs +++ b/rpc/src/native/mod.rs @@ -1,14 +1,19 @@ use std::{collections::HashMap, sync::Arc}; -use alloy::{providers::Provider, rpc::types::eth::BlockId, transports::Transport}; +use alloy::{ + providers::Provider, + rpc::types::eth::{BlockId, BlockTransactionsKind}, + transports::Transport, +}; +use anyhow::Context as _; use futures::try_join; use prover::ProverInput; +use trace_decoder::trace_protocol::BlockTrace; -mod block; mod state; mod txn; -type CodeDb = HashMap>; +type CodeDb = HashMap<__compat_primitive_types::H256, Vec>; /// Fetches the prover input for the given BlockId. pub async fn prover_input( @@ -21,8 +26,8 @@ where TransportT: Transport + Clone, { let (block_trace, other_data) = try_join!( - block::process_block_trace(provider.clone(), block_number), - crate::metadata::fetch_other_block_data(provider, block_number, checkpoint_block_number,) + process_block_trace(&provider, block_number), + crate::fetch_other_block_data(&provider, block_number, checkpoint_block_number,) )?; Ok(ProverInput { @@ -30,3 +35,27 @@ where other_data, }) } + +/// Processes the block with the given block number and returns the block trace. +async fn process_block_trace( + provider: &ProviderT, + block_number: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + let block = provider + .get_block(block_number, BlockTransactionsKind::Full) + .await? + .context("target block does not exist")?; + + let (code_db, txn_info) = txn::process_transactions(&block, provider).await?; + let trie_pre_images = state::process_state_witness(provider, block, &txn_info).await?; + + Ok(BlockTrace { + txn_info, + code_db: Option::from(code_db).filter(|x| !x.is_empty()), + trie_pre_images, + }) +} diff --git a/rpc/src/native/state.rs b/rpc/src/native/state.rs index 52021f8a..b4992848 100644 --- a/rpc/src/native/state.rs +++ b/rpc/src/native/state.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, HashSet}; -use std::sync::Arc; use alloy::{ primitives::{keccak256, Address, StorageKey, B256}, @@ -15,11 +14,11 @@ use trace_decoder::trace_protocol::{ SeparateTriePreImages, TrieDirect, TxnInfo, }; -use crate::compat::{ToAlloy, ToPrimitive}; +use crate::compat::Compat; /// Processes the state witness for the given block. pub async fn process_state_witness( - provider: Arc, + provider: &ProviderT, block: Block, txn_infos: &[TxnInfo], ) -> anyhow::Result @@ -50,7 +49,7 @@ where .into_iter() .map(|(a, m)| { ( - a.to_primitive(), + a.compat(), SeparateTriePreImage::Direct(TrieDirect(m.build())), ) }) @@ -79,14 +78,14 @@ pub fn process_states_access( for txn_info in tx_infos { for (address, trace) in txn_info.traces.iter() { - let address_storage_access = state_access.entry((*address).to_alloy()).or_default(); + let address_storage_access = state_access.entry((*address).compat()).or_default(); if let Some(read_keys) = trace.storage_read.as_ref() { - address_storage_access.extend(read_keys.iter().copied().map(ToAlloy::to_alloy)); + address_storage_access.extend(read_keys.iter().copied().map(Compat::compat)); } if let Some(written_keys) = trace.storage_written.as_ref() { - address_storage_access.extend(written_keys.keys().copied().map(ToAlloy::to_alloy)); + address_storage_access.extend(written_keys.keys().copied().map(Compat::compat)); } } } @@ -98,7 +97,7 @@ pub fn process_states_access( async fn generate_state_witness( prev_state_root: B256, accounts_state: HashMap>, - provider: Arc, + provider: &ProviderT, block_number: u64, ) -> anyhow::Result<( PartialTrieBuilder, @@ -108,7 +107,7 @@ where ProviderT: Provider, TransportT: Transport + Clone, { - let mut state = PartialTrieBuilder::new(prev_state_root.to_primitive(), Default::default()); + let mut state = PartialTrieBuilder::new(prev_state_root.compat(), Default::default()); let mut storage_proofs = HashMap::>::new(); let (account_proofs, next_account_proofs) = @@ -116,27 +115,27 @@ where // Insert account proofs for (address, proof) in account_proofs.into_iter() { - state.insert_proof(proof.account_proof.to_primitive()); + state.insert_proof(proof.account_proof.compat()); let storage_mpt = storage_proofs .entry(keccak256(address)) .or_insert(PartialTrieBuilder::new( - proof.storage_hash.to_primitive(), + proof.storage_hash.compat(), Default::default(), )); for proof in proof.storage_proof { - storage_mpt.insert_proof(proof.proof.to_primitive()); + storage_mpt.insert_proof(proof.proof.compat()); } } // Insert short node variants from next proofs for (address, proof) in next_account_proofs.into_iter() { - state.insert_short_node_variants_from_proof(proof.account_proof.to_primitive()); + state.insert_short_node_variants_from_proof(proof.account_proof.compat()); if let Some(storage_mpt) = storage_proofs.get_mut(&keccak256(address)) { for proof in proof.storage_proof { - storage_mpt.insert_short_node_variants_from_proof(proof.proof.to_primitive()); + storage_mpt.insert_short_node_variants_from_proof(proof.proof.compat()); } } } @@ -147,7 +146,7 @@ where /// Fetches the proof data for the given accounts and associated storage keys. async fn fetch_proof_data( accounts_state: HashMap>, - provider: Arc, + provider: &ProviderT, block_number: u64, ) -> anyhow::Result<( Vec<(Address, EIP1186AccountProofResponse)>, @@ -160,30 +159,26 @@ where let account_proofs_fut = accounts_state .clone() .into_iter() - .map(|(address, keys)| { - let provider = provider.clone(); - async move { - let proof = provider - .get_proof(address, keys.into_iter().collect()) - .block_id((block_number - 1).into()) - .await - .context("Failed to get proof for account")?; - anyhow::Result::Ok((address, proof)) - } + .map(|(address, keys)| async move { + let proof = provider + .get_proof(address, keys.into_iter().collect()) + .block_id((block_number - 1).into()) + .await + .context("Failed to get proof for account")?; + anyhow::Result::Ok((address, proof)) }) .collect::>(); - let next_account_proofs_fut = accounts_state.into_iter().map(|(address, keys)| { - let provider = provider.clone(); - async move { + let next_account_proofs_fut = accounts_state + .into_iter() + .map(|(address, keys)| async move { let proof = provider .get_proof(address, keys.into_iter().collect()) .block_id(block_number.into()) .await .context("Failed to get proof for account")?; anyhow::Result::Ok((address, proof)) - } - }); + }); try_join( try_join_all(account_proofs_fut), diff --git a/rpc/src/native/txn.rs b/rpc/src/native/txn.rs index dd9c8284..7c55e1fb 100644 --- a/rpc/src/native/txn.rs +++ b/rpc/src/native/txn.rs @@ -1,8 +1,6 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::collections::{HashMap, HashSet}; +use __compat_primitive_types::{H256, U256}; use alloy::{ primitives::{keccak256, Address, B256}, providers::{ @@ -23,53 +21,42 @@ use alloy::{ }; use anyhow::Context as _; use futures::stream::{FuturesOrdered, TryStreamExt}; -use primitive_types::{H256, U256}; use trace_decoder::trace_protocol::{ContractCodeUsage, TxnInfo, TxnMeta, TxnTrace}; use super::CodeDb; -use crate::compat::ToPrimitive; +use crate::compat::Compat; /// Processes the transactions in the given block and updates the code db. pub(super) async fn process_transactions( block: &Block, - provider: Arc, + provider: &ProviderT, ) -> anyhow::Result<(CodeDb, Vec)> where ProviderT: Provider, TransportT: Transport + Clone, { - let mut futures_ordered = FuturesOrdered::new(); - - for tx in block + block .transactions .as_transactions() .context("No transactions in block")? - { - let provider = provider.clone(); - futures_ordered - .push_back(async move { super::txn::process_transaction(provider, tx).await }); - } - - let result = futures_ordered - .try_collect::>() - .await? - .into_iter() - .fold( + .iter() + .map(|tx| super::txn::process_transaction(provider, tx)) + .collect::>() + .try_fold( (HashMap::new(), Vec::new()), - |(mut code_db, mut txn_infos), (tx_code_db, txn_info)| { + |(mut code_db, mut txn_infos), (tx_code_db, txn_info)| async move { code_db.extend(tx_code_db); txn_infos.push(txn_info); - (code_db, txn_infos) + Ok((code_db, txn_infos)) }, - ); - - Ok(result) + ) + .await } /// Processes the transaction with the given transaction hash and updates the /// accounts state. async fn process_transaction( - provider: Arc, + provider: &ProviderT, tx: &Transaction, ) -> anyhow::Result<(CodeDb, TxnInfo)> where @@ -101,7 +88,7 @@ where meta: tx_meta, traces: tx_traces .into_iter() - .map(|(k, v)| (k.to_primitive(), v)) + .map(|(k, v)| (k.compat(), v)) .collect(), }, )) @@ -109,7 +96,7 @@ where /// Fetches the transaction data for the given transaction hash. async fn fetch_tx_data( - provider: Arc, + provider: &ProviderT, tx_hash: &B256, ) -> anyhow::Result<(::ReceiptResponse, GethTrace, GethTrace), anyhow::Error> where @@ -139,7 +126,7 @@ fn parse_access_list(access_list: Option<&AccessList>) -> HashMap, + access_list: HashSet<__compat_primitive_types::H256>, acct_state: Option<&AccountState>, post_acct: Option<&AccountState>, pre_acct: Option<&AccountState>, @@ -235,7 +222,7 @@ fn process_storage( acct.storage .keys() .copied() - .map(ToPrimitive::to_primitive) + .map(Compat::compat) .collect::>() }) .unwrap_or_default(), @@ -245,7 +232,7 @@ fn process_storage( .map(|x| { x.storage .iter() - .map(|(k, v)| ((*k).to_primitive(), U256::from_big_endian(&v.0))) + .map(|(k, v)| ((*k).compat(), U256::from_big_endian(&v.0))) .collect() }) .unwrap_or_default(); @@ -254,7 +241,7 @@ fn process_storage( if let Some(pre_acct) = pre_acct { for key in pre_acct.storage.keys() { storage_written - .entry((*key).to_primitive()) + .entry((*key).compat()) .or_insert(U256::zero()); } }; @@ -276,12 +263,12 @@ async fn process_code( read_state.and_then(|x| x.code.as_ref()), ) { (Some(post_code), _) => { - let code_hash = keccak256(post_code).to_primitive(); + let code_hash = keccak256(post_code).compat(); code_db.insert(code_hash, post_code.to_vec()); Some(ContractCodeUsage::Write(post_code.to_vec().into())) } (_, Some(read_code)) => { - let code_hash = keccak256(read_code).to_primitive(); + let code_hash = keccak256(read_code).compat(); code_db.insert(code_hash, read_code.to_vec()); Some(ContractCodeUsage::Read(code_hash)) From 07cea87739e9ea8e4f325cfd47cd6f9db6ef703a Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 19:24:15 +0800 Subject: [PATCH 03/10] merge upstream --- Cargo.lock | 16 +-- Cargo.toml | 8 +- leader/src/cli.rs | 25 +++- leader/src/client.rs | 160 +++++++++++++++--------- leader/src/main.rs | 39 +++--- rpc/src/jerigon.rs | 100 ++++++++++----- rpc/src/lib.rs | 276 +++++++++++++++--------------------------- rpc/src/main.rs | 97 ++++----------- rpc/src/native/mod.rs | 17 +-- 9 files changed, 349 insertions(+), 389 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9939ad20..51497afd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1914,8 +1914,8 @@ dependencies = [ [[package]] name = "evm_arithmetization" -version = "0.1.3" -source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" +version = "0.2.0" +source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" dependencies = [ "anyhow", "bytes", @@ -2947,8 +2947,8 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "mpt_trie" -version = "0.2.1" -source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" +version = "0.3.0" +source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" dependencies = [ "bytes", "enum-as-inner", @@ -3690,8 +3690,8 @@ dependencies = [ [[package]] name = "proof_gen" -version = "0.1.3" -source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" +version = "0.2.0" +source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" dependencies = [ "ethereum-types", "evm_arithmetization", @@ -4927,8 +4927,8 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "trace_decoder" -version = "0.3.1" -source = "git+https://github.com/fractal-zkp/zk_evm.git?branch=feat/partial_trie_builder#d29b17148194782e900473460a1ac16315a29448" +version = "0.4.0" +source = "git+https://github.com/0xPolygonZero/zk_evm.git?tag=v0.4.0#46eb449a5a97438ade3f22e2555d7f266b54b290" dependencies = [ "bytes", "ciborium", diff --git a/Cargo.toml b/Cargo.toml index a776f6a1..2dd5caf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,10 @@ alloy = { git = "https://github.com/alloy-rs/alloy", features = [ # zk-evm dependencies plonky2 = "0.2.2" -evm_arithmetization = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } -mpt_trie = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } -trace_decoder = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } -proof_gen = { git = "https://github.com/fractal-zkp/zk_evm.git", branch = "feat/partial_trie_builder" } +evm_arithmetization = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } +mpt_trie = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } +trace_decoder = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } +proof_gen = { git = "https://github.com/0xPolygonZero/zk_evm.git", tag = "v0.4.0" } [workspace.package] edition = "2021" diff --git a/leader/src/cli.rs b/leader/src/cli.rs index bc318fac..48b2e537 100644 --- a/leader/src/cli.rs +++ b/leader/src/cli.rs @@ -19,7 +19,7 @@ pub(crate) struct Cli { pub(crate) prover_state_config: CliProverStateConfig, } -#[derive(Subcommand)] +#[derive(Subcommand, Clone)] pub(crate) enum Command { /// Reads input from stdin and writes output to stdout. Stdio { @@ -35,22 +35,35 @@ pub(crate) enum Command { // The Jerigon RPC URL. #[arg(long, short = 'u', value_hint = ValueHint::Url)] rpc_url: Url, - /// The block number for which to generate a proof. - #[arg(short, long)] - block_number: u64, + /// The block interval for which to generate a proof. + #[arg(long, short = 'i')] + block_interval: String, /// The checkpoint block number. #[arg(short, long, default_value_t = 0)] checkpoint_block_number: u64, /// The previous proof output. #[arg(long, short = 'f', value_hint = ValueHint::FilePath)] previous_proof: Option, - /// If provided, write the generated proof to this file instead of + /// If provided, write the generated proofs to this directory instead of /// stdout. #[arg(long, short = 'o', value_hint = ValueHint::FilePath)] - proof_output_path: Option, + proof_output_dir: Option, /// If true, save the public inputs to disk on error. #[arg(short, long, default_value_t = false)] save_inputs_on_error: bool, + /// Network block time in milliseconds. This value is used + /// to determine the blockchain node polling interval. + #[arg(short, long, env = "ZERO_BIN_BLOCK_TIME", default_value_t = 2000)] + block_time: u64, + /// Keep intermediate proofs. Default action is to + /// delete them after the final proof is generated. + #[arg( + short, + long, + env = "ZERO_BIN_KEEP_INTERMEDIATE_PROOFS", + default_value_t = false + )] + keep_intermediate_proofs: bool, /// Backoff in milliseconds for request retries #[arg(long, default_value_t = 0)] backoff: u64, diff --git a/leader/src/client.rs b/leader/src/client.rs index ff128c48..1941e5db 100644 --- a/leader/src/client.rs +++ b/leader/src/client.rs @@ -1,70 +1,118 @@ -use std::{ - fs::{create_dir_all, File}, - io::Write, - path::PathBuf, - sync::Arc, -}; +use std::io::Write; +use std::path::PathBuf; use alloy::transports::http::reqwest::Url; +use anyhow::Result; +use common::block_interval::BlockInterval; +use common::fs::generate_block_proof_file_name; use paladin::runtime::Runtime; -use proof_gen::types::PlonkyProofIntern; -use rpc::retry::build_http_retry_provider; +use proof_gen::proof_types::GeneratedBlockProof; +use rpc::{retry::build_http_retry_provider, RpcType}; +use tracing::{error, info, warn}; -/// The main function for the jerigon mode. -#[allow(clippy::too_many_arguments)] -pub(crate) async fn rpc_main( - rpc_type: &str, - rpc_url: Url, +#[derive(Debug)] +pub struct RpcParams { + pub rpc_url: Url, + pub rpc_type: RpcType, + pub backoff: u64, + pub max_retries: u32, +} + +#[derive(Debug, Default)] +pub struct ProofParams { + pub checkpoint_block_number: u64, + pub previous_proof: Option, + pub proof_output_dir: Option, + pub save_inputs_on_error: bool, + pub keep_intermediate_proofs: bool, +} + +/// The main function for the client. +pub(crate) async fn client_main( runtime: Runtime, - block_number: u64, - checkpoint_block_number: u64, - previous: Option, - proof_output_path_opt: Option, - save_inputs_on_error: bool, - backoff: u64, - max_retries: u32, -) -> anyhow::Result<()> { - let prover_input = match rpc_type { - "jerigon" => { - rpc::jerigon::prover_input( - build_http_retry_provider(rpc_url, backoff, max_retries), - block_number.into(), - checkpoint_block_number.into(), - ) - .await? - } - "native" => { - rpc::native::prover_input( - Arc::new(build_http_retry_provider(rpc_url, backoff, max_retries)), - block_number.into(), - checkpoint_block_number.into(), - ) - .await? - } - _ => unreachable!(), - }; + rpc_params: RpcParams, + block_interval: BlockInterval, + mut params: ProofParams, +) -> Result<()> { + let prover_input = rpc::prover_input( + build_http_retry_provider( + rpc_params.rpc_url, + rpc_params.backoff, + rpc_params.max_retries, + ), + block_interval, + params.checkpoint_block_number.into(), + rpc_params.rpc_type, + ) + .await?; + + if cfg!(feature = "test_only") { + info!("All proof witnesses have been generated successfully."); + } else { + info!("All proofs have been generated successfully."); + } - let proof = prover_input - .prove(&runtime, previous, save_inputs_on_error) + // If `keep_intermediate_proofs` is not set we only keep the last block + // proof from the interval. It contains all the necessary information to + // verify the whole sequence. + let proved_blocks = prover_input + .prove( + &runtime, + params.previous_proof.take(), + params.save_inputs_on_error, + params.proof_output_dir.clone(), + ) .await; runtime.close().await?; + let proved_blocks = proved_blocks?; - let proof = serde_json::to_vec(&proof?.intern)?; - write_proof(proof, proof_output_path_opt) -} - -fn write_proof(proof: Vec, proof_output_path_opt: Option) -> anyhow::Result<()> { - match proof_output_path_opt { - Some(p) => { - if let Some(parent) = p.parent() { - create_dir_all(parent)?; - } - - let mut f = File::create(p)?; - f.write_all(&proof)?; + if params.keep_intermediate_proofs { + if params.proof_output_dir.is_some() { + // All proof files (including intermediary) are written to disk and kept + warn!("Skipping cleanup, intermediate proof files are kept"); + } else { + // Output all proofs to stdout + std::io::stdout().write_all(&serde_json::to_vec( + &proved_blocks + .into_iter() + .filter_map(|(_, block)| block) + .collect::>(), + )?)?; + } + } else if let Some(proof_output_dir) = params.proof_output_dir.as_ref() { + // Remove intermediary proof files + proved_blocks + .into_iter() + .rev() + .skip(1) + .map(|(block_number, _)| { + generate_block_proof_file_name(&proof_output_dir.to_str(), block_number) + }) + .for_each(|path| { + if let Err(e) = std::fs::remove_file(path) { + error!("Failed to remove intermediate proof file: {e}"); + } + }); + } else { + // Output only last proof to stdout + if let Some(last_block) = proved_blocks + .into_iter() + .filter_map(|(_, block)| block) + .last() + { + std::io::stdout().write_all(&serde_json::to_vec(&last_block)?)?; } - None => std::io::stdout().write_all(&proof)?, } Ok(()) } + +impl From for RpcType { + fn from(command: super::cli::Command) -> Self { + match command { + super::cli::Command::Native { .. } => RpcType::Native, + super::cli::Command::Jerigon { .. } => RpcType::Jerigon, + _ => panic!("Unsupported command type"), + } + } +} diff --git a/leader/src/main.rs b/leader/src/main.rs index af0a9d7d..f9984104 100644 --- a/leader/src/main.rs +++ b/leader/src/main.rs @@ -4,6 +4,7 @@ use std::{fs::File, path::PathBuf}; use anyhow::Result; use clap::Parser; use cli::Command; +use client::RpcParams; use common::block_interval::BlockInterval; use dotenvy::dotenv; use ops::register; @@ -11,7 +12,7 @@ use paladin::runtime::Runtime; use proof_gen::proof_types::GeneratedBlockProof; use tracing::info; -use crate::jerigon::{jerigon_main, ProofParams}; +use crate::client::{client_main, ProofParams}; use crate::utils::get_package_version; mod cli; @@ -68,7 +69,7 @@ async fn main() -> Result<()> { let runtime = Runtime::from_config(&args.paladin, register()).await?; - match args.command { + match args.command.clone() { Command::Stdio { previous_proof, save_inputs_on_error, @@ -103,30 +104,16 @@ async fn main() -> Result<()> { keep_intermediate_proofs, backoff, max_retries, - } => { - let previous_proof = get_previous_proof(previous_proof)?; - - client::rpc_main( - "jerigon", - rpc_url, - runtime, - block_number, - checkpoint_block_number, - previous_proof, - proof_output_path, - save_inputs_on_error, - backoff, - max_retries, - ) - .await?; } - Command::Native { + | Command::Native { rpc_url, - block_number, + block_interval, checkpoint_block_number, previous_proof, - proof_output_path, + proof_output_dir, save_inputs_on_error, + block_time, + keep_intermediate_proofs, backoff, max_retries, } => { @@ -142,8 +129,14 @@ async fn main() -> Result<()> { } info!("Proving interval {block_interval}"); - jerigon_main( + client_main( runtime, + RpcParams { + rpc_url, + rpc_type: args.command.into(), + backoff, + max_retries, + }, block_interval, ProofParams { checkpoint_block_number, @@ -152,8 +145,6 @@ async fn main() -> Result<()> { save_inputs_on_error, keep_intermediate_proofs, }, - backoff, - max_retries, ) .await?; } diff --git a/rpc/src/jerigon.rs b/rpc/src/jerigon.rs index 67998cb2..a2fc380c 100644 --- a/rpc/src/jerigon.rs +++ b/rpc/src/jerigon.rs @@ -1,58 +1,100 @@ -use alloy::{providers::Provider, rpc::types::eth::BlockId, transports::Transport}; +use alloy::{ + primitives::B256, + providers::Provider, + rpc::types::eth::{BlockId, BlockNumberOrTag, BlockTransactionsKind}, + transports::Transport, +}; use anyhow::Context as _; -use itertools::{Either, Itertools as _}; +use common::block_interval::BlockInterval; +use futures::StreamExt as _; +use prover::BlockProverInput; use prover::ProverInput; use serde::Deserialize; use serde_json::json; -use trace_decoder::trace_protocol::{BlockTrace, BlockTraceTriePreImages, TxnInfo}; +use trace_decoder::trace_protocol::{ + BlockTrace, BlockTraceTriePreImages, CombinedPreImages, TrieCompact, TxnInfo, +}; use super::fetch_other_block_data; -#[derive(Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -#[allow(clippy::large_enum_variant)] -enum ZeroTrace { - Result(TxnInfo), - BlockWitness(BlockTraceTriePreImages), +/// Transaction traces retrieved from Erigon zeroTracer. +#[derive(Debug, Deserialize)] +pub struct ZeroTxResult { + #[serde(rename(deserialize = "txHash"))] + pub tx_hash: alloy::primitives::TxHash, + pub result: TxnInfo, } -/// Fetches the prover input for the given BlockId. -pub async fn prover_input( +/// Block witness retrieved from Erigon zeroTracer. +#[derive(Debug, Deserialize)] +pub struct ZeroBlockWitness(TrieCompact); + +pub async fn block_prover_input( provider: ProviderT, target_block_id: BlockId, - checkpoint_block_id: BlockId, -) -> anyhow::Result + checkpoint_state_trie_root: B256, +) -> anyhow::Result where ProviderT: Provider, TransportT: Transport + Clone, { // Grab trace information - ///////////////////////// - let traces = provider - .raw_request::<_, Vec>( + let tx_results = provider + .raw_request::<_, Vec>( "debug_traceBlockByNumber".into(), (target_block_id, json!({"tracer": "zeroTracer"})), ) .await?; - let (txn_info, mut pre_images) = - traces - .into_iter() - .partition_map::, Vec<_>, _, _, _>(|it| match it { - ZeroTrace::Result(it) => Either::Left(it), - ZeroTrace::BlockWitness(it) => Either::Right(it), - }); + // Grab block witness info (packed as combined trie pre-images) + let block_witness = provider + .raw_request::<_, ZeroBlockWitness>("eth_getWitness".into(), vec![target_block_id]) + .await?; - let other_data = fetch_other_block_data(provider, target_block_id, checkpoint_block_id).await?; + let other_data = + fetch_other_block_data(provider, target_block_id, checkpoint_state_trie_root).await?; // Assemble - /////////// - Ok(ProverInput { + Ok(BlockProverInput { block_trace: BlockTrace { - trie_pre_images: pre_images.pop().context("trace had no BlockWitness")?, - code_db: None, - txn_info, + trie_pre_images: BlockTraceTriePreImages::Combined(CombinedPreImages { + compact: block_witness.0, + }), + txn_info: tx_results.into_iter().map(|it| it.result).collect(), + code_db: Default::default(), }, other_data, }) } + +/// Obtain the prover input for a given block interval +pub async fn prover_input( + provider: ProviderT, + block_interval: BlockInterval, + checkpoint_block_id: BlockId, +) -> anyhow::Result +where + ProviderT: Provider, + TransportT: Transport + Clone, +{ + // Grab interval checkpoint block state trie + let checkpoint_state_trie_root = provider + .get_block(checkpoint_block_id, BlockTransactionsKind::Hashes) + .await? + .context("block does not exist")? + .header + .state_root; + + let mut block_proofs = Vec::new(); + let mut block_interval = block_interval.into_bounded_stream()?; + + while let Some(block_num) = block_interval.next().await { + let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); + let block_prover_input = + block_prover_input(&provider, block_id, checkpoint_state_trie_root).await?; + block_proofs.push(block_prover_input); + } + Ok(ProverInput { + blocks: block_proofs, + }) +} diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 0098c149..b67e36bf 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -1,85 +1,83 @@ -use alloy::primitives::B256; -use alloy::rpc::types::eth::BlockNumberOrTag; use alloy::{ + primitives::B256, providers::Provider, - rpc::types::eth::{Block, BlockId, BlockTransactionsKind, Withdrawal}, + rpc::types::eth::{BlockId, BlockNumberOrTag, BlockTransactionsKind, Withdrawal}, transports::Transport, }; use anyhow::Context as _; +use clap::ValueEnum; use common::block_interval::BlockInterval; use evm_arithmetization::proof::{BlockHashes, BlockMetadata}; use futures::{StreamExt as _, TryStreamExt as _}; -use prover::{BlockProverInput, ProverInput}; -use serde::Deserialize; -use serde_json::json; -use trace_decoder::{ - trace_protocol::{ - BlockTrace, BlockTraceTriePreImages, CombinedPreImages, TrieCompact, TxnInfo, - }, - types::{BlockLevelData, OtherBlockData}, -}; +use prover::ProverInput; +use trace_decoder::types::{BlockLevelData, OtherBlockData}; -/// Transaction traces retrieved from Erigon zeroTracer. -#[derive(Debug, Deserialize)] -pub struct ZeroTxResult { - #[serde(rename(deserialize = "txHash"))] - pub tx_hash: alloy::primitives::TxHash, - pub result: TxnInfo, -} +mod compat; +pub mod jerigon; +pub mod native; +pub mod retry; -/// Block witness retrieved from Erigon zeroTracer. -#[derive(Debug, Deserialize)] -pub struct ZeroBlockWitness(TrieCompact); +use compat::Compat; -/// When [fetching a block over RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber), -/// we can choose the transaction format, between: -/// - Full JSON. -/// - Just the hash. -/// -/// We only need the latter. -const BLOCK_WITH_HASHES_ONLY: BlockTransactionsKind = BlockTransactionsKind::Hashes; +/// The RPC type. +#[derive(ValueEnum, Clone, Debug)] +pub enum RpcType { + Jerigon, + Native, +} -/// Retrieve block information from the provider -pub async fn get_block( - provider: &mut ProviderT, - target_block_id: BlockId, - block_transaction_kind: BlockTransactionsKind, -) -> anyhow::Result +/// Obtain the prover input for a given block interval +pub async fn prover_input( + provider: ProviderT, + block_interval: BlockInterval, + checkpoint_block_id: BlockId, + rpc_type: RpcType, +) -> anyhow::Result where ProviderT: Provider, TransportT: Transport + Clone, { - provider - .get_block(target_block_id, block_transaction_kind) + // Grab interval checkpoint block state trie + let checkpoint_state_trie_root = provider + .get_block(checkpoint_block_id, BlockTransactionsKind::Hashes) .await? - .context("block does not exist") + .context("block does not exist")? + .header + .state_root; + + let mut block_proofs = Vec::new(); + let mut block_interval = block_interval.into_bounded_stream()?; + + while let Some(block_num) = block_interval.next().await { + let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); + let block_prover_input = match rpc_type { + RpcType::Jerigon => { + jerigon::block_prover_input(&provider, block_id, checkpoint_state_trie_root).await? + } + RpcType::Native => { + native::block_prover_input(&provider, block_id, checkpoint_state_trie_root).await? + } + }; + + block_proofs.push(block_prover_input); + } + Ok(ProverInput { + blocks: block_proofs, + }) } -pub async fn block_prover_input( +/// Fetches other block data +async fn fetch_other_block_data( provider: ProviderT, target_block_id: BlockId, checkpoint_state_trie_root: B256, -) -> anyhow::Result +) -> anyhow::Result where ProviderT: Provider, TransportT: Transport + Clone, { - // Grab trace information - let tx_results = provider - .raw_request::<_, Vec>( - "debug_traceBlockByNumber".into(), - (target_block_id, json!({"tracer": "zeroTracer"})), - ) - .await?; - - // Grab block witness info (packed as combined trie pre-images) - let block_witness = provider - .raw_request::<_, ZeroBlockWitness>("eth_getWitness".into(), vec![target_block_id]) - .await?; - - // Grab block info let target_block = provider - .get_block(target_block_id, BLOCK_WITH_HASHES_ONLY) + .get_block(target_block_id, BlockTransactionsKind::Hashes) .await? .context("target block does not exist")?; let target_block_number = target_block @@ -101,7 +99,7 @@ where let provider = &provider; async move { let block = provider - .get_block(n.into(), BLOCK_WITH_HASHES_ONLY) + .get_block(n.into(), BlockTransactionsKind::Hashes) .await .context("couldn't get block")? .context("no such block")?; @@ -115,130 +113,48 @@ where .await .context("couldn't fill previous hashes")?; - // Assemble - Ok(BlockProverInput { - block_trace: BlockTrace { - trie_pre_images: BlockTraceTriePreImages::Combined(CombinedPreImages { - compact: block_witness.0, - }), - txn_info: tx_results.into_iter().map(|it| it.result).collect(), - code_db: Default::default(), - }, - other_data: OtherBlockData { - b_data: BlockLevelData { - b_meta: BlockMetadata { - block_beneficiary: target_block.header.miner.compat(), - block_timestamp: target_block.header.timestamp.into(), - block_number: target_block_number.into(), - block_difficulty: target_block.header.difficulty.into(), - block_random: target_block - .header - .mix_hash - .context("target block is missing field `mix_hash`")? - .compat(), - block_gaslimit: target_block.header.gas_limit.into(), - block_chain_id: chain_id.into(), - block_base_fee: target_block - .header - .base_fee_per_gas - .context("target block is missing field `base_fee_per_gas`")? - .into(), - block_gas_used: target_block.header.gas_used.into(), - block_bloom: target_block.header.logs_bloom.compat(), - }, - b_hashes: BlockHashes { - prev_hashes: prev_hashes.map(|it| it.compat()).into(), - cur_hash: target_block - .header - .hash - .context("target block is missing field `hash`")? - .compat(), - }, - withdrawals: target_block - .withdrawals - .into_iter() - .flatten() - .map( - |Withdrawal { - address, amount, .. - }| { (address.compat(), amount.into()) }, - ) - .collect(), + let other_data = OtherBlockData { + b_data: BlockLevelData { + b_meta: BlockMetadata { + block_beneficiary: target_block.header.miner.compat(), + block_timestamp: target_block.header.timestamp.into(), + block_number: target_block_number.into(), + block_difficulty: target_block.header.difficulty.into(), + block_random: target_block + .header + .mix_hash + .context("target block is missing field `mix_hash`")? + .compat(), + block_gaslimit: target_block.header.gas_limit.into(), + block_chain_id: chain_id.into(), + block_base_fee: target_block + .header + .base_fee_per_gas + .context("target block is missing field `base_fee_per_gas`")? + .into(), + block_gas_used: target_block.header.gas_used.into(), + block_bloom: target_block.header.logs_bloom.compat(), + }, + b_hashes: BlockHashes { + prev_hashes: prev_hashes.map(|it| it.compat()).into(), + cur_hash: target_block + .header + .hash + .context("target block is missing field `hash`")? + .compat(), }, - checkpoint_state_trie_root: checkpoint_state_trie_root.compat(), + withdrawals: target_block + .withdrawals + .into_iter() + .flatten() + .map( + |Withdrawal { + address, amount, .. + }| { (address.compat(), amount.into()) }, + ) + .collect(), }, - }) -} - -/// Obtain the prover input for a given block interval -pub async fn prover_input( - mut provider: ProviderT, - block_interval: BlockInterval, - checkpoint_block_id: BlockId, -) -> anyhow::Result -where - ProviderT: Provider, - TransportT: Transport + Clone, -{ - // Grab interval checkpoint block state trie - let checkpoint_state_trie_root = - get_block(&mut provider, checkpoint_block_id, BLOCK_WITH_HASHES_ONLY) - .await? - .header - .state_root; - - let mut block_proofs = Vec::new(); - let mut block_interval = block_interval.into_bounded_stream()?; - - while let Some(block_num) = block_interval.next().await { - let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); - let block_prover_input = - block_prover_input(&provider, block_id, checkpoint_state_trie_root).await?; - block_proofs.push(block_prover_input); - } - Ok(ProverInput { - blocks: block_proofs, - }) -} - -trait Compat { - fn compat(self) -> Out; -} - -impl Compat<__compat_primitive_types::H160> for alloy::primitives::Address { - fn compat(self) -> __compat_primitive_types::H160 { - let alloy::primitives::Address(alloy::primitives::FixedBytes(arr)) = self; - __compat_primitive_types::H160(arr) - } -} - -impl Compat<__compat_primitive_types::H256> for alloy::primitives::B256 { - fn compat(self) -> __compat_primitive_types::H256 { - let alloy::primitives::FixedBytes(arr) = self; - __compat_primitive_types::H256(arr) - } -} - -impl Compat<[__compat_primitive_types::U256; 8]> for alloy::primitives::Bloom { - fn compat(self) -> [__compat_primitive_types::U256; 8] { - let alloy::primitives::Bloom(alloy::primitives::FixedBytes(src)) = self; - // have u8 * 256 - // want U256 * 8 - // (no unsafe, no unstable) - let mut chunks = src.chunks_exact(32); - let dst = core::array::from_fn(|_ix| { - // This is a bit spicy because we're going from an uninterpeted array of bytes - // to wide integers, but we trust this `From` impl to do the right thing - __compat_primitive_types::U256::from( - <[u8; 32]>::try_from(chunks.next().unwrap()).unwrap(), - ) - }); - assert_eq!(chunks.len(), 0); - dst - } -} - -#[test] -fn bloom() { - let _did_not_panic = alloy::primitives::Bloom::ZERO.compat(); + checkpoint_state_trie_root: checkpoint_state_trie_root.compat(), + }; + Ok(other_data) } diff --git a/rpc/src/main.rs b/rpc/src/main.rs index 87aa7d0f..45575df8 100644 --- a/rpc/src/main.rs +++ b/rpc/src/main.rs @@ -1,13 +1,9 @@ -use std::{io, sync::Arc}; +use std::io; -<<<<<<< HEAD -use alloy::{providers::RootProvider, rpc::types::eth::BlockId}; +use alloy::rpc::types::eth::BlockId; use clap::{Parser, ValueHint}; use common::block_interval::BlockInterval; -======= -use clap::{Parser, ValueEnum, ValueHint}; -use rpc::retry::build_http_retry_provider; ->>>>>>> 7cd3d62 (Introduce native tracer support) +use rpc::{retry::build_http_retry_provider, RpcType}; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; @@ -15,7 +11,6 @@ use url::Url; pub enum Cli { /// Fetch and generate prover input from the RPC endpoint Fetch { -<<<<<<< HEAD // Starting block of interval to fetch #[arg(short, long)] start_block: u64, @@ -25,71 +20,49 @@ pub enum Cli { /// The RPC URL. #[arg(short = 'u', long, value_hint = ValueHint::Url)] rpc_url: Url, - /// The checkpoint block number. If not provided, - /// block before the `start_block` is the checkpoint - #[arg(short, long)] - checkpoint_block_number: Option, -======= - /// The RPC URL - #[arg(short = 'u', long, value_hint = ValueHint::Url)] - rpc_url: Url, /// The RPC Tracer Type #[arg(short = 't', long, default_value = "jerigon")] rpc_type: RpcType, - /// The block number + /// The checkpoint block number. If not provided, + /// block before the `start_block` is the checkpoint #[arg(short, long)] - block_number: u64, - /// The checkpoint block number - #[arg(short, long, default_value_t = 0)] - checkpoint_block_number: u64, + checkpoint_block_number: Option, /// Backoff in milliseconds for request retries #[arg(long, default_value_t = 0)] backoff: u64, /// The maximum number of retries #[arg(long, default_value_t = 0)] max_retries: u32, ->>>>>>> 7cd3d62 (Introduce native tracer support) }, } -/// The RPC type. -#[derive(ValueEnum, Clone)] -pub enum RpcType { - Jerigon, - Native, -} - impl Cli { /// Execute the cli command. pub async fn execute(self) -> anyhow::Result<()> { match self { Self::Fetch { + start_block, + end_block, rpc_url, rpc_type, - block_number, checkpoint_block_number, backoff, max_retries, } => { - let prover_input = match rpc_type { - RpcType::Jerigon => { - rpc::jerigon::prover_input( - build_http_retry_provider(rpc_url, backoff, max_retries), - block_number.into(), - checkpoint_block_number.into(), - ) - .await? - } - RpcType::Native => { - rpc::native::prover_input( - Arc::new(build_http_retry_provider(rpc_url, backoff, max_retries)), - block_number.into(), - checkpoint_block_number.into(), - ) - .await? - } - }; - serde_json::to_writer_pretty(io::stdout(), &prover_input)?; + let checkpoint_block_number = + checkpoint_block_number.unwrap_or((start_block - 1).into()); + let block_interval = BlockInterval::Range(start_block..end_block + 1); + + // Retrieve prover input from the Erigon node + let prover_input = rpc::prover_input( + build_http_retry_provider(rpc_url, backoff, max_retries), + block_interval, + checkpoint_block_number, + rpc_type, + ) + .await?; + + serde_json::to_writer_pretty(io::stdout(), &prover_input.blocks)?; } } Ok(()) @@ -107,29 +80,5 @@ async fn main() -> anyhow::Result<()> { ) .init(); -<<<<<<< HEAD - let Args::Fetch { - start_block, - end_block, - rpc_url, - checkpoint_block_number, - } = Args::parse(); - - let checkpoint_block_number = checkpoint_block_number.unwrap_or((start_block - 1).into()); - let block_interval = BlockInterval::Range(start_block..end_block + 1); - - // Retrieve prover input from the Erigon node - let prover_input = rpc::prover_input( - RootProvider::new_http(rpc_url), - block_interval, - checkpoint_block_number, - ) - .await?; - - serde_json::to_writer_pretty(io::stdout(), &prover_input.blocks)?; - -======= - Cli::parse().execute().await?; ->>>>>>> 7cd3d62 (Introduce native tracer support) - Ok(()) + Cli::parse().execute().await } diff --git a/rpc/src/native/mod.rs b/rpc/src/native/mod.rs index 2dbd2107..75de3d5d 100644 --- a/rpc/src/native/mod.rs +++ b/rpc/src/native/mod.rs @@ -1,13 +1,14 @@ -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use alloy::{ + primitives::B256, providers::Provider, rpc::types::eth::{BlockId, BlockTransactionsKind}, transports::Transport, }; use anyhow::Context as _; use futures::try_join; -use prover::ProverInput; +use prover::BlockProverInput; use trace_decoder::trace_protocol::BlockTrace; mod state; @@ -16,21 +17,21 @@ mod txn; type CodeDb = HashMap<__compat_primitive_types::H256, Vec>; /// Fetches the prover input for the given BlockId. -pub async fn prover_input( - provider: Arc, +pub async fn block_prover_input( + provider: &ProviderT, block_number: BlockId, - checkpoint_block_number: BlockId, -) -> anyhow::Result + checkpoint_state_trie_root: B256, +) -> anyhow::Result where ProviderT: Provider, TransportT: Transport + Clone, { let (block_trace, other_data) = try_join!( process_block_trace(&provider, block_number), - crate::fetch_other_block_data(&provider, block_number, checkpoint_block_number,) + crate::fetch_other_block_data(&provider, block_number, checkpoint_state_trie_root,) )?; - Ok(ProverInput { + Ok(BlockProverInput { block_trace, other_data, }) From ab4aff61a3dc9a48816bc5b51d589da978e4ac00 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 19:41:22 +0800 Subject: [PATCH 04/10] update README --- README.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 771a9f71..f7f68c64 100644 --- a/README.md +++ b/README.md @@ -206,21 +206,25 @@ cargo r --release --bin leader jerigon --help Reads input from a Jerigon node and writes output to stdout -Usage: leader jerigon [OPTIONS] --rpc-url --block-number +Usage: leader jerigon [OPTIONS] --rpc-url --block-interval Options: -u, --rpc-url - -b, --block-number - The block number for which to generate a proof + -i, --block-interval + The block interval for which to generate a proof -c, --checkpoint-block-number The checkpoint block number [default: 0] -f, --previous-proof The previous proof output - -o, --proof-output-path - If provided, write the generated proof to this file instead of stdout + -o, --proof-output-dir + If provided, write the generated proofs to this directory instead of stdout -s, --save-inputs-on-error If true, save the public inputs to disk on error + -b, --block-time + Network block time in milliseconds. This value is used to determine the blockchain node polling interval [env: ZERO_BIN_BLOCK_TIME=] [default: 2000] + -k, --keep-intermediate-proofs + Keep intermediate proofs. Default action is to delete them after the final proof is generated [env: ZERO_BIN_KEEP_INTERMEDIATE_PROOFS=] --backoff Backoff in milliseconds for request retries [default: 0] --max-retries @@ -244,21 +248,25 @@ cargo r --release --bin leader native --help Reads input from a native node and writes output to stdout -Usage: leader native [OPTIONS] --rpc-url --block-number +Usage: leader native [OPTIONS] --rpc-url --block-interval Options: -u, --rpc-url - -b, --block-number - The block number for which to generate a proof + -i, --block-interval + The block interval for which to generate a proof -c, --checkpoint-block-number The checkpoint block number [default: 0] -f, --previous-proof The previous proof output - -o, --proof-output-path - If provided, write the generated proof to this file instead of stdout + -o, --proof-output-dir + If provided, write the generated proofs to this directory instead of stdout -s, --save-inputs-on-error If true, save the public inputs to disk on error + -b, --block-time + Network block time in milliseconds. This value is used to determine the blockchain node polling interval [env: ZERO_BIN_BLOCK_TIME=] [default: 2000] + -k, --keep-intermediate-proofs + Keep intermediate proofs. Default action is to delete them after the final proof is generated [env: ZERO_BIN_KEEP_INTERMEDIATE_PROOFS=] --backoff Backoff in milliseconds for request retries [default: 0] --max-retries @@ -386,7 +394,7 @@ Options: Example: ```bash -cargo r --release --bin rpc fetch --rpc-url --block-number 16 > ./output/block-16.json +cargo r --release --bin rpc fetch --start-block --end-block --rpc-url --block-number 16 > ./output/block-16.json ``` ## Docker From 41bcbbdda4088ea91b17fb3278a014b5f67699cb Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 19:46:32 +0800 Subject: [PATCH 05/10] remove unecessary tools --- tools/debug_block.sh | 42 ---------------------------- tools/debug_blocks.sh | 31 --------------------- tools/prove_blocks.sh | 65 ------------------------------------------- 3 files changed, 138 deletions(-) delete mode 100755 tools/debug_block.sh delete mode 100755 tools/debug_blocks.sh delete mode 100755 tools/prove_blocks.sh diff --git a/tools/debug_block.sh b/tools/debug_block.sh deleted file mode 100755 index 61230dec..00000000 --- a/tools/debug_block.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Args: -# 1 --> Block idx -# 2 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) -# 3 --> Rpc type (eg. jerigon / native) -# 4 --> Backoff time (in milliseconds) for the RPC requests -# 5 --> Number of retries for the RPC requests - -export RUST_BACKTRACE=1 -export RUST_MIN_STACK=8388608 -export RUST_LOG=mpt_trie=info,trace_decoder=info,plonky2=info,evm_arithmetization=trace,leader=info -export RUSTFLAGS='-Ctarget-cpu=native' - -# Speciying smallest ranges, as we won't need them anyway. -export ARITHMETIC_CIRCUIT_SIZE="16..17" -export BYTE_PACKING_CIRCUIT_SIZE="9..10" -export CPU_CIRCUIT_SIZE="12..13" -export KECCAK_CIRCUIT_SIZE="14..15" -export KECCAK_SPONGE_CIRCUIT_SIZE="9..10" -export LOGIC_CIRCUIT_SIZE="12..13" -export MEMORY_CIRCUIT_SIZE="17..18" - -OUTPUT_DIR="debug" -OUT_DUMMY_PROOF_PATH="${OUTPUT_DIR}/b${1}.zkproof" -OUT_LOG_PATH="${OUTPUT_DIR}/b${1}.log" - -echo "Testing block ${1}..." -mkdir -p $OUTPUT_DIR - -cargo r --release --features test_only --bin leader -- -n 1 --runtime in-memory "$3" --rpc-url "$2" --block-number "$1" --checkpoint-block-number "$(($1-1))" --proof-output-path $OUT_DUMMY_PROOF_PATH --backoff "${4:-0}" --max-retries "${5:-0}" > $OUT_LOG_PATH 2>&1 -retVal=$? -if [ $retVal -ne 0 ]; then - # Some error occured. - echo "Witness generation for block ${1} errored. See ${OUT_LOG_PATH} for more details." -else - echo "Witness generation for block ${1} succeeded." - # Remove the log / dummy proof on success. - rm $OUT_DUMMY_PROOF_PATH - rm $OUT_LOG_PATH -fi - diff --git a/tools/debug_blocks.sh b/tools/debug_blocks.sh deleted file mode 100755 index 767f9ad3..00000000 --- a/tools/debug_blocks.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# Check if the correct number of arguments was provided -if [ "$#" -lt 4 ]; then - echo "Usage: $0 INITIAL_BLOCK NUM_BLOCKS RPC_ENDPOINT RPC_TYPE BACKOFF RETRIES" - exit 1 -fi - -# Read the arguments -# 1 --> initial block number -# 2 --> number of blocks to debug -# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) -# 4 --> Rpc type (eg. jerigon / native) -# 5 --> Backoff time (in milliseconds) for the RPC requests -# 6 --> Number of retries for the RPC requests -INITIAL_BLOCK=$1 -NUM_BLOCKS=$2 -RPC_ENDPOINT=$3 -RPC_TYPE=$4 -BACKOFF=${5:-0} -RETRIES=${6:-0} - -# Get the directory of the current script -script_dir=$(dirname "$0") - -# Loop and call the existing script -for (( i=0; i Start block idx -# 2 --> End block index (inclusive) -# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) -# 4 --> Rpc type (eg. jerigon / native) -# 5 --> Ignore previous proofs (boolean) - -export RUST_BACKTRACE=1 -export RUST_LOG=mpt_trie=info,trace_decoder=info,plonky2=info,evm_arithmetization=trace,leader=info -export RUSTFLAGS='-Ctarget-cpu=native' - -export ARITHMETIC_CIRCUIT_SIZE="16..23" -export BYTE_PACKING_CIRCUIT_SIZE="9..21" -export CPU_CIRCUIT_SIZE="12..25" -export KECCAK_CIRCUIT_SIZE="14..20" -export KECCAK_SPONGE_CIRCUIT_SIZE="9..15" -export LOGIC_CIRCUIT_SIZE="12..18" -export MEMORY_CIRCUIT_SIZE="17..28" - -PROOF_OUTPUT_DIR="proofs" -ALWAYS_WRITE_LOGS=0 # Change this to `1` if you always want logs to be written. - -TOT_BLOCKS=$(($2-$1+1)) -RPC_TYPE=$4 -IGNORE_PREVIOUS_PROOFS=$5 - -echo "Proving blocks ${1}..=${2}... (Total: ${TOT_BLOCKS})" -mkdir -p $PROOF_OUTPUT_DIR - -for ((i=$1; i<=$2; i++)) -do - echo "Proving block ${i}..." - - OUT_PROOF_PATH="${PROOF_OUTPUT_DIR}/b${i}.zkproof" - OUT_LOG_PATH="${PROOF_OUTPUT_DIR}/b${i}.log" - - if [ $IGNORE_PREVIOUS_PROOFS ]; then - # Set checkpoint height to previous block number - prev_proof_num=$((i-1)) - PREV_PROOF_EXTRA_ARG="--checkpoint-block-number ${prev_proof_num}" - else - if [ $i -gt 1 ]; then - prev_proof_num=$((i-1)) - PREV_PROOF_EXTRA_ARG="-f ${PROOF_OUTPUT_DIR}/b${prev_proof_num}.zkproof" - fi - fi - - cargo r --release --bin leader -- --runtime in-memory "$RPC_TYPE" --rpc-url "$3" --block-number $i --proof-output-path $OUT_PROOF_PATH $PREV_PROOF_EXTRA_ARG > $OUT_LOG_PATH 2>&1 - - retVal=$? - if [ $retVal -ne 0 ]; then - # Some error occured. - echo "Block ${i} errored. See ${OUT_LOG_PATH} for more details." - exit $retVal - else - # Remove the log on success if we don't want to keep it. - if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then - rm $OUT_LOG_PATH - fi - fi -done - -echo "Successfully generated ${TOT_BLOCKS} proofs!" \ No newline at end of file From 371ca61bd082aa09a6a1e672b2a3b58fd71d627f Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 20:02:16 +0800 Subject: [PATCH 06/10] use reference for provider --- leader/src/client.rs | 2 +- rpc/src/lib.rs | 2 +- rpc/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/leader/src/client.rs b/leader/src/client.rs index 1941e5db..5dd3fba3 100644 --- a/leader/src/client.rs +++ b/leader/src/client.rs @@ -35,7 +35,7 @@ pub(crate) async fn client_main( mut params: ProofParams, ) -> Result<()> { let prover_input = rpc::prover_input( - build_http_retry_provider( + &build_http_retry_provider( rpc_params.rpc_url, rpc_params.backoff, rpc_params.max_retries, diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index b67e36bf..22ac0ec6 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -28,7 +28,7 @@ pub enum RpcType { /// Obtain the prover input for a given block interval pub async fn prover_input( - provider: ProviderT, + provider: &ProviderT, block_interval: BlockInterval, checkpoint_block_id: BlockId, rpc_type: RpcType, diff --git a/rpc/src/main.rs b/rpc/src/main.rs index 45575df8..ccab182a 100644 --- a/rpc/src/main.rs +++ b/rpc/src/main.rs @@ -55,7 +55,7 @@ impl Cli { // Retrieve prover input from the Erigon node let prover_input = rpc::prover_input( - build_http_retry_provider(rpc_url, backoff, max_retries), + &build_http_retry_provider(rpc_url, backoff, max_retries), block_interval, checkpoint_block_number, rpc_type, From 0c2dd1a193d38d45fc06068bdbca2fbf3ac29616 Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 20:36:51 +0800 Subject: [PATCH 07/10] refactor prove_rpc tool --- tools/prove_rpc.sh | 124 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100755 tools/prove_rpc.sh diff --git a/tools/prove_rpc.sh b/tools/prove_rpc.sh new file mode 100755 index 00000000..9c511600 --- /dev/null +++ b/tools/prove_rpc.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +# Args: +# 1 --> Start block idx +# 2 --> End block index (inclusive) +# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) +# 4 --> Rpc type (eg. jerigon / native) +# 5 --> Ignore previous proofs (boolean) +# 6 --> Backoff in milliseconds (optional [default: 0]) +# 7 --> Number of retries (optional [default: 0]) +# 8 --> Test run only flag `test_only` (optional) + +export RUST_MIN_STACK=33554432 +export RUST_BACKTRACE=1 +export RUST_LOG=info +# Disable the lld linker for now, as it's causing issues with the linkme package. +# https://github.com/rust-lang/rust/pull/124129 +# https://github.com/dtolnay/linkme/pull/88 +export RUSTFLAGS='-C target-cpu=native -Zlinker-features=-lld' + +if [[ $8 == "test_only" ]]; then + # Circuit sizes don't matter in test_only mode, so we keep them minimal. + export ARITHMETIC_CIRCUIT_SIZE="16..17" + export BYTE_PACKING_CIRCUIT_SIZE="9..10" + export CPU_CIRCUIT_SIZE="12..13" + export KECCAK_CIRCUIT_SIZE="14..15" + export KECCAK_SPONGE_CIRCUIT_SIZE="9..10" + export LOGIC_CIRCUIT_SIZE="12..13" + export MEMORY_CIRCUIT_SIZE="17..18" +else + export ARITHMETIC_CIRCUIT_SIZE="16..23" + export BYTE_PACKING_CIRCUIT_SIZE="9..21" + export CPU_CIRCUIT_SIZE="12..25" + export KECCAK_CIRCUIT_SIZE="14..20" + export KECCAK_SPONGE_CIRCUIT_SIZE="9..15" + export LOGIC_CIRCUIT_SIZE="12..18" + export MEMORY_CIRCUIT_SIZE="17..28" +fi + +PROOF_OUTPUT_DIR="proofs" +OUT_LOG_PATH="${PROOF_OUTPUT_DIR}/b${i}.log" +ALWAYS_WRITE_LOGS=0 # Change this to `1` if you always want logs to be written. +TOT_BLOCKS=$(($2-$1+1)) + +START_BLOCK=$1 +END_BLOCK=$2 +NODE_RPC_URL=$3 +NODE_RPC_TYPE=$4 +IGNORE_PREVIOUS_PROOFS=$5 +BACKOFF=${6:-0} +RETRIES=${7:-0} + + +mkdir -p $PROOF_OUTPUT_DIR + + +if [ $IGNORE_PREVIOUS_PROOFS ]; then + # Set checkpoint height to previous block number for the first block in range + prev_proof_num=$(($1-1)) + PREV_PROOF_EXTRA_ARG="--checkpoint-block-number ${prev_proof_num}" +else + if [ $1 -gt 1 ]; then + prev_proof_num=$(($1-1)) + PREV_PROOF_EXTRA_ARG="-f ${PROOF_OUTPUT_DIR}/b${prev_proof_num}.zkproof" + fi +fi + +# Convert hex to decimal parameters +if [[ $START_BLOCK == 0x* ]]; then + START_BLOCK=$((16#${START_BLOCK#"0x"})) +fi +if [[ $END_BLOCK == 0x* ]]; then + END_BLOCK=$((16#${END_BLOCK#"0x"})) +fi + +# Define block interval +if [ $START_BLOCK == $END_BLOCK ]; then + BLOCK_INTERVAL=$((16#${START_BLOCK#"0x"})) +else + BLOCK_INTERVAL=$START_BLOCK..=$END_BLOCK +fi + + +# If we set test_only flag, we'll generate a dummy +# proof. This is useful for quickly testing decoding and all of the +# other non-proving code. +if [[ $8 == "test_only" ]]; then + # test only run + echo "Proving blocks ${BLOCK_INTERVAL} in a test_only mode now... (Total: ${TOT_BLOCKS})" + cargo r --release --features test_only --bin leader -- --runtime in-memory --load-strategy on-demand "$NODE_RPC_TYPE" --rpc-url "$NODE_RPC_URL" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" > $OUT_LOG_PATH 2>&1 + if grep -q 'All proof witnesses have been generated successfully.' $OUT_LOG_PATH; then + echo -e "Success - Note this was just a test, not a proof" + # Remove the log on success if we don't want to keep it. + if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then + rm $OUT_LOG_PATH + fi + exit + else + echo "Failed to create proof witnesses. See ${OUT_LOG_PATH} for more details." + exit 1 + fi +else + # normal run + echo "Proving blocks ${BLOCK_INTERVAL} now... (Total: ${TOT_BLOCKS})" + cargo r --release --bin leader -- --runtime in-memory --load-strategy on-demand "$NODE_RPC_TYPE" --rpc-url "$3" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG --backoff "$BACKOFF" --max-retries "$RETRIES" > $OUT_LOG_PATH 2>&1 + + retVal=$? + if [ $retVal -ne 0 ]; then + # Some error occured. + echo "Block ${i} errored. See ${OUT_LOG_PATH} for more details." + exit $retVal + else + # Remove the log on success if we don't want to keep it. + if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then + rm $OUT_LOG_PATH + fi + fi + + echo "Successfully generated ${TOT_BLOCKS} proofs!" +fi + + + + From ec1a99de3dadf6c27b712294ed708f50ea25055a Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 20:37:24 +0800 Subject: [PATCH 08/10] remove prove_jerigon tool --- tools/prove_jerigon.sh | 118 ----------------------------------------- 1 file changed, 118 deletions(-) delete mode 100755 tools/prove_jerigon.sh diff --git a/tools/prove_jerigon.sh b/tools/prove_jerigon.sh deleted file mode 100755 index 409cbf33..00000000 --- a/tools/prove_jerigon.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/bash - -# Args: -# 1 --> Start block idx -# 2 --> End block index (inclusive) -# 3 --> Rpc endpoint:port (eg. http://35.246.1.96:8545) -# 4 --> Ignore previous proofs (boolean) -# 5 --> Test run only flag `test_only` (optional) - -export RUST_MIN_STACK=33554432 -export RUST_BACKTRACE=1 -export RUST_LOG=info -# Disable the lld linker for now, as it's causing issues with the linkme package. -# https://github.com/rust-lang/rust/pull/124129 -# https://github.com/dtolnay/linkme/pull/88 -export RUSTFLAGS='-C target-cpu=native -Zlinker-features=-lld' - -if [[ $5 == "test_only" ]]; then - # Circuit sizes don't matter in test_only mode, so we keep them minimal. - export ARITHMETIC_CIRCUIT_SIZE="16..17" - export BYTE_PACKING_CIRCUIT_SIZE="9..10" - export CPU_CIRCUIT_SIZE="12..13" - export KECCAK_CIRCUIT_SIZE="14..15" - export KECCAK_SPONGE_CIRCUIT_SIZE="9..10" - export LOGIC_CIRCUIT_SIZE="12..13" - export MEMORY_CIRCUIT_SIZE="17..18" -else - export ARITHMETIC_CIRCUIT_SIZE="16..23" - export BYTE_PACKING_CIRCUIT_SIZE="9..21" - export CPU_CIRCUIT_SIZE="12..25" - export KECCAK_CIRCUIT_SIZE="14..20" - export KECCAK_SPONGE_CIRCUIT_SIZE="9..15" - export LOGIC_CIRCUIT_SIZE="12..18" - export MEMORY_CIRCUIT_SIZE="17..28" -fi - -PROOF_OUTPUT_DIR="proofs" -OUT_LOG_PATH="${PROOF_OUTPUT_DIR}/b${i}.log" -ALWAYS_WRITE_LOGS=0 # Change this to `1` if you always want logs to be written. -TOT_BLOCKS=$(($2-$1+1)) - -START_BLOCK=$1 -END_BLOCK=$2 -NODE_RPC_URL=$3 -IGNORE_PREVIOUS_PROOFS=$4 - - -mkdir -p $PROOF_OUTPUT_DIR - - -if [ $IGNORE_PREVIOUS_PROOFS ]; then - # Set checkpoint height to previous block number for the first block in range - prev_proof_num=$(($1-1)) - PREV_PROOF_EXTRA_ARG="--checkpoint-block-number ${prev_proof_num}" -else - if [ $1 -gt 1 ]; then - prev_proof_num=$(($1-1)) - PREV_PROOF_EXTRA_ARG="-f ${PROOF_OUTPUT_DIR}/b${prev_proof_num}.zkproof" - fi -fi - -# Convert hex to decimal parameters -if [[ $START_BLOCK == 0x* ]]; then - START_BLOCK=$((16#${START_BLOCK#"0x"})) -fi -if [[ $END_BLOCK == 0x* ]]; then - END_BLOCK=$((16#${END_BLOCK#"0x"})) -fi - -# Define block interval -if [ $START_BLOCK == $END_BLOCK ]; then - BLOCK_INTERVAL=$((16#${START_BLOCK#"0x"})) -else - BLOCK_INTERVAL=$START_BLOCK..=$END_BLOCK -fi - - -# If we set test_only flag, we'll generate a dummy -# proof. This is useful for quickly testing decoding and all of the -# other non-proving code. -if [[ $5 == "test_only" ]]; then - # test only run - echo "Proving blocks ${BLOCK_INTERVAL} in a test_only mode now... (Total: ${TOT_BLOCKS})" - cargo r --release --features test_only --bin leader -- --runtime in-memory --load-strategy on-demand jerigon --rpc-url "$NODE_RPC_URL" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG > $OUT_LOG_PATH 2>&1 - if grep -q 'All proof witnesses have been generated successfully.' $OUT_LOG_PATH; then - echo -e "Success - Note this was just a test, not a proof" - # Remove the log on success if we don't want to keep it. - if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then - rm $OUT_LOG_PATH - fi - exit - else - echo "Failed to create proof witnesses. See ${OUT_LOG_PATH} for more details." - exit 1 - fi -else - # normal run - echo "Proving blocks ${BLOCK_INTERVAL} now... (Total: ${TOT_BLOCKS})" - cargo r --release --bin leader -- --runtime in-memory --load-strategy on-demand jerigon --rpc-url "$3" --block-interval $BLOCK_INTERVAL --proof-output-dir $PROOF_OUTPUT_DIR $PREV_PROOF_EXTRA_ARG > $OUT_LOG_PATH 2>&1 - - retVal=$? - if [ $retVal -ne 0 ]; then - # Some error occured. - echo "Block ${i} errored. See ${OUT_LOG_PATH} for more details." - exit $retVal - else - # Remove the log on success if we don't want to keep it. - if [ $ALWAYS_WRITE_LOGS -ne 1 ]; then - rm $OUT_LOG_PATH - fi - fi - - echo "Successfully generated ${TOT_BLOCKS} proofs!" -fi - - - - From 0fd74c067ba5b149ad5d467c73531e5fef32ec7d Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 20:50:12 +0800 Subject: [PATCH 09/10] update README for prove_rpc.sh script changes --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f7f68c64..aecba5de 100644 --- a/README.md +++ b/README.md @@ -415,16 +415,16 @@ For testing proof generation for blocks, the `testing` branch should be used. ### Proving Blocks -If you want to generate a full block proof, you can use `tools/prove_jerigon.sh`: +If you want to generate a full block proof, you can use `tools/prove_rpc.sh`: ```sh -./prove_jerigon.sh +./prove_rpc.sh ``` Which may look like this: ```sh -./prove_jerigon.sh 17 18 http://127.0.0.1:8545 false +./prove_rpc.sh 17 18 http://127.0.0.1:8545 jerigon false ``` Which will attempt to generate proofs for blocks `17` & `18` consecutively and incorporate the previous block proof during generation. @@ -436,16 +436,16 @@ A few other notes: ### Generating Witnesses Only -If you want to test a block without the high CPU & memory requirements that come with creating a full proof, you can instead generate only the witness using `tools/prove_jerigon.sh` in the `test_only` mode: +If you want to test a block without the high CPU & memory requirements that come with creating a full proof, you can instead generate only the witness using `tools/prove_rpc.sh` in the `test_only` mode: ```sh -./prove_jerigon.sh test_only +./prove_rpc.sh test_only ``` Filled in: ```sh -./prove_jerigon.sh 18299898 18299899 http://34.89.57.138:8545 true test_only +./prove_rpc.sh 18299898 18299899 http://34.89.57.138:8545 jerigon true 0 0 test_only ``` Finally, note that both of these testing scripts force proof generation to be sequential by allowing only one worker. Because of this, this is not a realistic representation of performance but makes the debugging logs much easier to follow. From 5f4cdb46a42e4707ad6d801119bc5c6b336a3d7a Mon Sep 17 00:00:00 2001 From: frisitano Date: Mon, 17 Jun 2024 21:15:43 +0800 Subject: [PATCH 10/10] remove dead code --- rpc/src/jerigon.rs | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/rpc/src/jerigon.rs b/rpc/src/jerigon.rs index a2fc380c..d1d29ed0 100644 --- a/rpc/src/jerigon.rs +++ b/rpc/src/jerigon.rs @@ -1,14 +1,7 @@ use alloy::{ - primitives::B256, - providers::Provider, - rpc::types::eth::{BlockId, BlockNumberOrTag, BlockTransactionsKind}, - transports::Transport, + primitives::B256, providers::Provider, rpc::types::eth::BlockId, transports::Transport, }; -use anyhow::Context as _; -use common::block_interval::BlockInterval; -use futures::StreamExt as _; use prover::BlockProverInput; -use prover::ProverInput; use serde::Deserialize; use serde_json::json; use trace_decoder::trace_protocol::{ @@ -66,35 +59,3 @@ where other_data, }) } - -/// Obtain the prover input for a given block interval -pub async fn prover_input( - provider: ProviderT, - block_interval: BlockInterval, - checkpoint_block_id: BlockId, -) -> anyhow::Result -where - ProviderT: Provider, - TransportT: Transport + Clone, -{ - // Grab interval checkpoint block state trie - let checkpoint_state_trie_root = provider - .get_block(checkpoint_block_id, BlockTransactionsKind::Hashes) - .await? - .context("block does not exist")? - .header - .state_root; - - let mut block_proofs = Vec::new(); - let mut block_interval = block_interval.into_bounded_stream()?; - - while let Some(block_num) = block_interval.next().await { - let block_id = BlockId::Number(BlockNumberOrTag::Number(block_num)); - let block_prover_input = - block_prover_input(&provider, block_id, checkpoint_state_trie_root).await?; - block_proofs.push(block_prover_input); - } - Ok(ProverInput { - blocks: block_proofs, - }) -}