diff --git a/.gitignore b/.gitignore index 174aa94..f931161 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,11 @@ pgo-data.profdata **/proof-with-io.json # Env -.env \ No newline at end of file +.env + +# EF test data +script/mainnet/ + +# Logs and Summaries +**/logs/** +**/summaries/** \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ca6a515..e815802 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,4 +34,5 @@ "editor.formatOnSave": true, "editor.hover.enabled": true }, + "rust-analyzer.trace.server": "verbose", } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9e67d7c..380dc98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ "alloy-trie", "auto_impl", "c-kzg", - "derive_more", + "derive_more 1.0.0", "serde", ] @@ -83,7 +83,7 @@ dependencies = [ "alloy-rlp", "alloy-trie", "auto_impl", - "derive_more", + "derive_more 1.0.0", ] [[package]] @@ -131,7 +131,7 @@ checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ "alloy-primitives", "alloy-rlp", - "derive_more", + "derive_more 1.0.0", "serde", ] @@ -143,7 +143,7 @@ checksum = "cabf647eb4650c91a9d38cb6f972bb320009e7e9d61765fb688a86f1563b33e8" dependencies = [ "alloy-primitives", "alloy-rlp", - "derive_more", + "derive_more 1.0.0", ] [[package]] @@ -158,7 +158,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "c-kzg", - "derive_more", + "derive_more 1.0.0", "once_cell", "serde", "sha2", @@ -176,7 +176,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "auto_impl", - "derive_more", + "derive_more 1.0.0", "sha2", ] @@ -254,7 +254,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more", + "derive_more 1.0.0", "foldhash", "hashbrown 0.15.2", "indexmap 2.7.1", @@ -318,7 +318,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "derive_more", + "derive_more 1.0.0", "itertools 0.13.0", "serde", "serde_json", @@ -444,7 +444,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more", + "derive_more 1.0.0", "nybbles", "serde", "smallvec", @@ -847,7 +847,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1196,6 +1196,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -1458,7 +1467,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1473,6 +1491,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.98", + "unicode-xid", +] + [[package]] name = "digest" version = "0.9.0" @@ -1644,7 +1675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2631,7 +2662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3582,7 +3613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.98", @@ -3643,7 +3674,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3777,17 +3808,20 @@ name = "ream-lib" version = "0.1.0" dependencies = [ "ethereum_ssz", + "ream-consensus", + "serde", "snap", "tracing", ] [[package]] -name = "ream-program" +name = "ream-operations" version = "0.1.0" dependencies = [ "ethereum_ssz", "ream-consensus", "ream-lib", + "sp1-derive", "sp1-zkvm", ] @@ -3796,6 +3830,7 @@ name = "ream-script" version = "0.1.0" dependencies = [ "clap", + "derive_more 2.0.1", "dotenv", "ethereum_ssz", "hex", @@ -4062,7 +4097,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4152,7 +4187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", ] @@ -5125,7 +5160,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5619,6 +5654,12 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.0" diff --git a/README.md b/README.md index 9e6eceb..c67adc3 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,35 @@ To run the program without generating a proof: ```sh cd script -cargo run --release -- --execute +make download +cargo run --release -- --execute --operation_name +``` + +``` +possible values for OPERATION_NAME: attestation, attester_slashing, block_header, bls_to_execution_change, deposit, execution_payload, proposer_slashing, sync_aggregate, voluntary_exit, withdrawals ``` This will execute the program and display the output. +### Generate benchmarks for execution + +```sh +cd script +make download +make run- +``` + +```sh +OPERATIONS = attestation attester_slashing block_header bls_to_execution_change deposit execution_payload proposer_slashing sync_aggregate voluntary_exit withdrawals +``` + +This will execute the program and generate benchmarks (especially for cycles) in `./script/summaries` directory. + ### Generate a Core Proof To generate a core proof for your program: ```sh cd script -cargo run --release -- --prove +cargo run --release -- --prove --operation_name ``` diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9306e61..69f4e67 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -7,3 +7,7 @@ edition = "2021" ethereum_ssz = { workspace = true } snap = "1.1.1" tracing = { workspace = true } +serde = { version = "1.0.200", default-features = false, features = ["derive"] } + +# Ream dependencies +ream-consensus = { workspace = true } diff --git a/lib/src/file.rs b/lib/src/file.rs new file mode 100644 index 0000000..2330d82 --- /dev/null +++ b/lib/src/file.rs @@ -0,0 +1,32 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::snappy::decode_snappy; +use crate::ssz::to_ssz; + +pub fn read_file(path: &Path) -> T { + let raw_bytes = + std::fs::read(path).unwrap_or_else(|e| panic!("Could not read file: {:?}: {}", path, e)); + let ssz_bytes = decode_snappy(&raw_bytes).unwrap_or_else(|e| { + panic!("Could not decode snappy {:?}: {}", path, e); + }); + to_ssz(&ssz_bytes).unwrap_or_else(|| { + panic!("Could not decode ssz {:?}", path); + }) +} + +pub fn get_test_cases(base_dir: &PathBuf) -> Vec { + let mut test_cases = Vec::new(); + + if let Ok(entries) = fs::read_dir(base_dir) { + for entry in entries.flatten() { + if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { + if let Some(folder_name) = entry.file_name().to_str() { + test_cases.push(folder_name.to_string()); + } + } + } + } + + test_cases +} diff --git a/lib/src/input.rs b/lib/src/input.rs new file mode 100644 index 0000000..d8e99ac --- /dev/null +++ b/lib/src/input.rs @@ -0,0 +1,28 @@ +use ream_consensus::{ + attestation::Attestation, + attester_slashing::AttesterSlashing, + bls_to_execution_change::SignedBLSToExecutionChange, + deneb::{ + beacon_block::BeaconBlock, beacon_block_body::BeaconBlockBody, + execution_payload::ExecutionPayload, + }, + deposit::Deposit, + proposer_slashing::ProposerSlashing, + sync_aggregate::SyncAggregate, + voluntary_exit::SignedVoluntaryExit, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub enum OperationInput { + Attestation(Attestation), + AttesterSlashing(AttesterSlashing), + BeaconBlock(BeaconBlock), + SignedBLSToExecutionChange(SignedBLSToExecutionChange), + Deposit(Deposit), + BeaconBlockBody(BeaconBlockBody), + ProposerSlashing(ProposerSlashing), + SyncAggregate(SyncAggregate), + SignedVoluntaryExit(SignedVoluntaryExit), + ExecutionPayload(ExecutionPayload), +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b319429..3f71878 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1 +1,4 @@ +pub mod file; +pub mod input; pub mod snappy; +pub mod ssz; diff --git a/lib/src/snappy.rs b/lib/src/snappy.rs index 35c563f..91db1ae 100644 --- a/lib/src/snappy.rs +++ b/lib/src/snappy.rs @@ -1,10 +1,6 @@ -use std::path::Path; - use snap::raw::Decoder; -pub fn read_ssz_snappy(path: &Path) -> Option { - let ssz_snappy = std::fs::read(path).ok()?; +pub fn decode_snappy(raw_bytes: &[u8]) -> Result, snap::Error> { let mut decoder = Decoder::new(); - let ssz = decoder.decompress_vec(&ssz_snappy).unwrap(); - T::from_ssz_bytes(&ssz).ok() + decoder.decompress_vec(raw_bytes) } diff --git a/lib/src/ssz.rs b/lib/src/ssz.rs new file mode 100644 index 0000000..36d9f6a --- /dev/null +++ b/lib/src/ssz.rs @@ -0,0 +1,3 @@ +pub fn to_ssz(ssz_bytes: &[u8]) -> Option { + T::from_ssz_bytes(ssz_bytes).ok() +} diff --git a/program/Cargo.toml b/program/Cargo.toml index 7eb1d27..a02386d 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -1,11 +1,12 @@ [package] version = "0.1.0" -name = "ream-program" +name = "ream-operations" edition = "2021" [dependencies] # SP1 dependencies sp1-zkvm = "4.0.0" +sp1-derive = "4.0.0" # Ethereum dependencies ethereum_ssz = { workspace = true } diff --git a/program/src/main.rs b/program/src/main.rs index 22d006b..2aa055e 100644 --- a/program/src/main.rs +++ b/program/src/main.rs @@ -5,40 +5,74 @@ #![no_main] sp1_zkvm::entrypoint!(main); -use ream_consensus::deneb::{beacon_block::BeaconBlock, beacon_state::BeaconState}; +use ream_consensus::deneb::beacon_state::BeaconState; +use ream_lib::input::OperationInput; use ssz::Encode; +#[sp1_derive::cycle_tracker] pub fn main() { // Read an input to the program. // // Behind the scenes, this compiles down to a custom system call which handles reading inputs // from the prover. - // NOTE: BeaconState/BeaconBlock should implement Serialize & Deserialize trait. + // NOTE: BeaconState/OperationInput should implement Serialize & Deserialize trait. - println!("cycle-tracker-start: read-pre-state"); + println!("cycle-tracker-report-start: read-pre-state"); let mut pre_state = sp1_zkvm::io::read::(); - println!("cycle-tracker-end: read-pre-state"); + println!("cycle-tracker-report-end: read-pre-state"); - println!("cycle-tracker-start: read-block"); - let block = sp1_zkvm::io::read::(); - println!("cycle-tracker-end: read-block"); + println!("cycle-tracker-report-start: read-operation-input"); + let input = sp1_zkvm::io::read::(); + println!("cycle-tracker-report-end: read-operation-input"); // Main logic of the program. // State transition of the beacon state. - println!("cycle-tracker-start: process-block-header"); - let _ = pre_state.process_block_header(&block); - println!("cycle-tracker-end: process-block-header"); + println!("cycle-tracker-report-start: process-operation"); + match input { + OperationInput::Attestation(attestation) => { + let _ = pre_state.process_attestation(&attestation); + } + OperationInput::AttesterSlashing(attester_slashing) => { + let _ = pre_state.process_attester_slashing(&attester_slashing); + } + OperationInput::BeaconBlock(block) => { + let _ = pre_state.process_block_header(&block); + } + OperationInput::SignedBLSToExecutionChange(bls_change) => { + let _ = pre_state.process_bls_to_execution_change(&bls_change); + } + OperationInput::Deposit(deposit) => { + let _ = pre_state.process_deposit(&deposit); + } + OperationInput::BeaconBlockBody(_block_body) => { + panic!("Not implemented"); + // let _ = pre_state.process_execution_payload(&block_body); + } + OperationInput::ProposerSlashing(proposer_slashing) => { + let _ = pre_state.process_proposer_slashing(&proposer_slashing); + } + OperationInput::SyncAggregate(sync_aggregate) => { + let _ = pre_state.process_sync_aggregate(&sync_aggregate); + } + OperationInput::SignedVoluntaryExit(voluntary_exit) => { + let _ = pre_state.process_voluntary_exit(&voluntary_exit); + } + OperationInput::ExecutionPayload(execution_payload) => { + let _ = pre_state.process_withdrawals(&execution_payload); + } + } + println!("cycle-tracker-report-end: process-operation"); // Commit to the public values of the program. The final proof will have a commitment to all the // bytes that were committed to. // NOTE: BeaconState should implement Serialize & Deserialize trait. - println!("cycle-tracker-start: convert-to-ssz-bytes"); + println!("cycle-tracker-report-start: convert-to-ssz-bytes"); let pre_state_bytes = pre_state.as_ssz_bytes(); - println!("cycle-tracker-end: convert-to-ssz-bytes"); + println!("cycle-tracker-report-end: convert-to-ssz-bytes"); - println!("cycle-tracker-start: commit"); + println!("cycle-tracker-report-start: commit"); sp1_zkvm::io::commit_slice(&pre_state_bytes); - println!("cycle-tracker-end: commit"); + println!("cycle-tracker-report-end: commit"); } diff --git a/script/Cargo.toml b/script/Cargo.toml index fd95726..1f1fa82 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -12,6 +12,7 @@ path = "src/bin/main.rs" sp1-sdk = "4.0.0" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } serde = { version = "1.0.200", default-features = false, features = ["derive"] } +derive_more = { version = "2.0.1", features = ["full"] } clap = { version = "4.0", features = ["derive", "env"] } tracing = { workspace = true } hex = "0.4.3" diff --git a/script/Makefile b/script/Makefile new file mode 100644 index 0000000..ede6959 --- /dev/null +++ b/script/Makefile @@ -0,0 +1,42 @@ +TARGET = mainnet.tar.gz +EXTRACT_DIR = mainnet +LOGS_DIR = logs +SUMMARIES_DIR = summaries + +DOWNLOAD_SCRIPT = ./subscripts/download_ef_data.sh +PARSE_SCRIPT = ./subscripts/parse_log_to_table.sh +SORT_SCRIPT = ./subscripts/sort_table.sh + +OPERATIONS = attestation attester_slashing block_header bls_to_execution_change deposit execution_payload proposer_slashing sync_aggregate voluntary_exit withdrawals + +.PHONY: all download run clean $(addprefix run-, $(OPERATIONS)) + +all: download run-attestation + +download: + @echo "Running download script..." + @chmod +x $(DOWNLOAD_SCRIPT) + @$(DOWNLOAD_SCRIPT) + +run: + @echo "Specify an operation: $(OPERATIONS)" + @exit 1 + +$(addprefix run-, $(OPERATIONS)): run-%: $(EXTRACT_DIR) + @mkdir -p $(LOGS_DIR) + @mkdir -p $(SUMMARIES_DIR) + @echo "Running benchmarks for $*..." + @NO_COLOR=1 cargo run --release -- --execute -o $* \ + --excluded-cases multi_proposer_index_iterations \ # attestation + --excluded-cases random_with_exits_with_duplicates \ # sync_aggregate + &> $(LOGS_DIR)/execution_$*.log + @echo "Execution complete for $*." + @$(PARSE_SCRIPT) $* + @$(SORT_SCRIPT) $(SUMMARIES_DIR)/summary_$*.md + +clean: + @echo "Cleaning up downloaded/execution files..." + @rm -f $(TARGET) + @rm -rf $(EXTRACT_DIR) + @rm -rf $(LOGS_DIR) + @echo "Clean up complete." \ No newline at end of file diff --git a/script/src/bin/cli/fork.rs b/script/src/bin/cli/fork.rs new file mode 100644 index 0000000..47a62be --- /dev/null +++ b/script/src/bin/cli/fork.rs @@ -0,0 +1,19 @@ +use clap::{Parser, ValueEnum}; +use derive_more::Display; + +#[derive(Debug, Clone, Parser)] +pub struct ForkArgs { + #[clap(long, short, default_value_t = Fork::Deneb)] + pub fork: Fork, +} + +#[derive(ValueEnum, Debug, Clone, Default, Display)] +#[clap(rename_all = "lowercase")] +pub enum Fork { + #[default] + #[display("deneb")] + Deneb, + + #[display("electra")] + Electra, +} diff --git a/script/src/bin/cli/mod.rs b/script/src/bin/cli/mod.rs new file mode 100644 index 0000000..45bcd56 --- /dev/null +++ b/script/src/bin/cli/mod.rs @@ -0,0 +1,2 @@ +pub mod fork; +pub mod operation; diff --git a/script/src/bin/cli/operation.rs b/script/src/bin/cli/operation.rs new file mode 100644 index 0000000..5dfeb62 --- /dev/null +++ b/script/src/bin/cli/operation.rs @@ -0,0 +1,50 @@ +use clap::{Parser, ValueEnum}; +use derive_more::Display; + +#[derive(Debug, Clone, Parser)] +pub struct OperationArgs { + #[clap(long, short)] + pub operation_name: OperationName, +} + +#[derive(ValueEnum, Debug, Clone, Display)] +#[clap(rename_all = "snake_case")] +pub enum OperationName { + #[display("attestation")] + Attestation, + #[display("attester_slashing")] + AttesterSlashing, + #[display("block_header")] + BlockHeader, + #[display("bls_to_execution_change")] + BLSToExecutionChange, + #[display("deposit")] + Deposit, + #[display("execution_payload")] + ExecutionPayload, + #[display("proposer_slashing")] + ProposerSlashing, + #[display("sync_aggregate")] + SyncAggregate, + #[display("voluntary_exit")] + VoluntaryExit, + #[display("withdrawals")] + Withdrawals, +} + +impl OperationName { + pub fn to_input_name(&self) -> String { + match self { + OperationName::Attestation => "attestation".to_string(), + OperationName::AttesterSlashing => "attester_slashing".to_string(), + OperationName::BlockHeader => "block".to_string(), + OperationName::BLSToExecutionChange => "address_change".to_string(), + OperationName::Deposit => "deposit".to_string(), + OperationName::ExecutionPayload => "body".to_string(), + OperationName::ProposerSlashing => "proposer_slashing".to_string(), + OperationName::SyncAggregate => "sync_aggregate".to_string(), + OperationName::VoluntaryExit => "voluntary_exit".to_string(), + OperationName::Withdrawals => "execution_payload".to_string(), + } + } +} diff --git a/script/src/bin/main.rs b/script/src/bin/main.rs index 5f49d56..5b6a1f2 100644 --- a/script/src/bin/main.rs +++ b/script/src/bin/main.rs @@ -1,34 +1,50 @@ -//! An end-to-end example of using the SP1 SDK to generate a proof of a program that can be executed -//! or have a core proof generated. -//! -//! You can run this script using the following command: -//! ```shell -//! RUST_LOG=info cargo run --release -- --execute -//! ``` -//! or -//! ```shell -//! RUST_LOG=info cargo run --release -- --prove -//! ``` - use clap::Parser; -use ream_consensus::deneb::{beacon_block::BeaconBlock, beacon_state::BeaconState}; use sp1_sdk::{include_elf, ProverClient, SP1Stdin}; +use tracing::{error, info}; + +use ream_consensus::deneb::beacon_state::BeaconState; +use ream_lib::{file::read_file, input::OperationInput}; + +mod cli; +use cli::operation::OperationName; /// The ELF (executable and linkable format) file for the Succinct RISC-V zkVM. -pub const REAM_ELF: &[u8] = include_elf!("ream-program"); +pub const REAM_ELF: &[u8] = include_elf!("ream-operations"); /// The arguments for the command. #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { + /// Argument for zkVMs + #[clap(long)] execute: bool, #[clap(long)] prove: bool, + + /// Argument for STFs + + #[clap(flatten)] + fork: cli::fork::ForkArgs, + + #[clap(flatten)] + operation: cli::operation::OperationArgs, + + #[clap(long)] + excluded_cases: Vec, } fn main() { + let test_case_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("mainnet") + .join("tests") + .join("mainnet"); + if !std::path::Path::new(&test_case_dir).exists() { + eprintln!("Error: You must first download test data via `make download`"); + std::process::exit(1); + } + if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "info"); } @@ -41,62 +57,119 @@ fn main() { let args = Args::parse(); if args.execute == args.prove { - eprintln!("Error: You must specify either --execute or --prove"); + error!("Error: You must specify either --execute or --prove"); std::process::exit(1); } + let fork = args.fork.fork; + let operation_name = args.operation.operation_name; + let excluded_cases = args.excluded_cases; + // Load the test assets. - // This asset is from consensus-specs repo. - // Path: tests/mainnet/deneb/operations/block_header/pyspec_tests/basic_block_header - let base_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("src") - .join("data"); - - let pre_state: BeaconState = - ream_lib::snappy::read_ssz_snappy(&base_dir.join("pre.ssz_snappy")) - .expect("cannot find test asset(pre.ssz_snappy) or decode it"); - let block: BeaconBlock = ream_lib::snappy::read_ssz_snappy(&base_dir.join("block.ssz_snappy")) - .expect("cannot find test asset(block.ssz_snappy) or decode it"); - let post_state: BeaconState = - ream_lib::snappy::read_ssz_snappy(&base_dir.join("post.ssz_snappy")) - .expect("cannot find test asset(post.ssz_snappy) or decode it"); - - // Setup the prover client. - let client = ProverClient::from_env(); - - // Setup the inputs. - let mut stdin = SP1Stdin::new(); - stdin.write(&pre_state); - stdin.write(&block); - - if args.execute { - // Execute the program - let (output, report) = client.execute(REAM_ELF, &stdin).run().unwrap(); - println!("Program executed successfully."); - - // Decode the output - let result: BeaconState = ssz::Decode::from_ssz_bytes(output.as_slice()).unwrap(); - - // Compare the output with the expected post state. - assert_eq!(result, post_state); - println!("Execution is correct!"); - - // Record the number of cycles executed. - println!("Number of cycles: {}", report.total_instruction_count()); - } else { - // Setup the program for proving. - let (pk, vk) = client.setup(REAM_ELF); - - // Generate the proof - let proof = client - .prove(&pk, &stdin) - .run() - .expect("failed to generate proof"); - - println!("Successfully generated proof!"); - - // Verify the proof. - client.verify(&proof, &vk).expect("failed to verify proof"); - println!("Successfully verified proof!"); + // These assets are from consensus-specs repo. + let base_dir = test_case_dir + .join(format!("{}", fork)) + .join("operations") + .join(format!("{}", operation_name)) + .join("pyspec_tests"); + + let test_cases = ream_lib::file::get_test_cases(&base_dir); + for test_case in test_cases { + if excluded_cases.contains(&test_case) { + info!("Skipping test case: {}", test_case); + continue; + } + + info!("{}", "-".repeat(50)); + info!("[{}] Test case: {}", operation_name, test_case); + + let case_dir = &base_dir.join(&test_case); + let input_path = &case_dir.join(format!("{}.ssz_snappy", operation_name.to_input_name())); + + let pre_state: BeaconState = read_file(&case_dir.join("pre.ssz_snappy")); + let input = match operation_name { + OperationName::Attestation => OperationInput::Attestation(read_file(input_path)), + OperationName::AttesterSlashing => { + OperationInput::AttesterSlashing(read_file(input_path)) + } + OperationName::BlockHeader => OperationInput::BeaconBlock(read_file(input_path)), + OperationName::BLSToExecutionChange => { + OperationInput::SignedBLSToExecutionChange(read_file(input_path)) + } + OperationName::Deposit => OperationInput::Deposit(read_file(input_path)), + OperationName::ExecutionPayload => { + OperationInput::BeaconBlockBody(read_file(input_path)) + } + OperationName::ProposerSlashing => { + OperationInput::ProposerSlashing(read_file(input_path)) + } + OperationName::SyncAggregate => OperationInput::SyncAggregate(read_file(input_path)), + OperationName::VoluntaryExit => { + OperationInput::SignedVoluntaryExit(read_file(input_path)) + } + OperationName::Withdrawals => OperationInput::ExecutionPayload(read_file(input_path)), + }; + let post_state_opt: Option = { + if case_dir.join("post.ssz_snappy").exists() { + Some(read_file(&case_dir.join("post.ssz_snappy"))) + } else { + None + } + }; + + // Setup the prover client. + let client = ProverClient::from_env(); + + // Setup the inputs. + let mut stdin = SP1Stdin::new(); + stdin.write(&pre_state); + stdin.write(&input); + + if args.execute { + // Execute the program + let (output, report) = client.execute(REAM_ELF, &stdin).run().unwrap(); + info!("Program executed successfully."); + + // Decode the output + let result: BeaconState = ssz::Decode::from_ssz_bytes(output.as_slice()).unwrap(); + + // Match `post_state_opt`: some test cases should not mutate beacon state. + match post_state_opt { + Some(post_state) => { + assert_eq!(result, post_state); + info!("Execution is correct!: State mutated"); + } + None => { + assert_eq!(result, pre_state); + info!("Execution is correct!: State should not be mutated"); + } + } + + // Record the number of cycles executed. + info!("----- Cycle Tracker -----"); + info!("[{}] Test case: {}", operation_name, test_case); + info!("Number of cycles: {}", report.total_instruction_count()); + info!("Number of syscall count: {}", report.total_syscall_count()); + for (key, value) in report.cycle_tracker.iter() { + info!("{}: {}", key, value); + } + info!("----- Cycle Tracker End -----"); + } else { + // Setup the program for proving. + let (pk, vk) = client.setup(REAM_ELF); + + // Generate the proof + let proof = client + .prove(&pk, &stdin) + .run() + .expect("failed to generate proof"); + + info!("Successfully generated proof!"); + + // Verify the proof. + client.verify(&proof, &vk).expect("failed to verify proof"); + info!("Successfully verified proof!"); + } + info!("{}", "-".repeat(50)); } } diff --git a/script/src/data/block.ssz_snappy b/script/src/data/block.ssz_snappy deleted file mode 100644 index 5e620e6..0000000 Binary files a/script/src/data/block.ssz_snappy and /dev/null differ diff --git a/script/src/data/post.ssz_snappy b/script/src/data/post.ssz_snappy deleted file mode 100644 index 9c58e8c..0000000 Binary files a/script/src/data/post.ssz_snappy and /dev/null differ diff --git a/script/src/data/pre.ssz_snappy b/script/src/data/pre.ssz_snappy deleted file mode 100644 index 00f5607..0000000 Binary files a/script/src/data/pre.ssz_snappy and /dev/null differ diff --git a/script/subscripts/download_ef_data.sh b/script/subscripts/download_ef_data.sh new file mode 100755 index 0000000..abb6aa7 --- /dev/null +++ b/script/subscripts/download_ef_data.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +TARGET="mainnet.tar.gz" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PARENT_DIR="$(dirname "$SCRIPT_DIR")" +EXTRACT_DIR="$PARENT_DIR/mainnet" +LATEST_RELEASE_URL="https://api.github.com/repos/ethereum/consensus-spec-tests/releases/latest" + +download_and_extract() { + if [ -d "$EXTRACT_DIR" ]; then + echo "$EXTRACT_DIR already exists. Skipping extraction." + return 0 + fi + + echo "Fetching the latest release URL for $TARGET..." + DOWNLOAD_URL=$(curl -s "$LATEST_RELEASE_URL" | jq -r ".assets[] | select(.name == \"$TARGET\") | .browser_download_url") + + if [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" == "null" ]; then + echo "Failed to fetch download URL for $TARGET." + exit 1 + fi + + echo "Downloading $TARGET..." + echo "URL: $DOWNLOAD_URL" + curl -L "$DOWNLOAD_URL" -o "$SCRIPT_DIR/$TARGET" + + if [ ! -f "$SCRIPT_DIR/$TARGET" ]; then + echo "Download failed. Exiting." + exit 1 + fi + + echo "Extracting $TARGET into $EXTRACT_DIR..." + mkdir -p "$EXTRACT_DIR" + tar -xzf "$SCRIPT_DIR/$TARGET" -C "$EXTRACT_DIR" + rm -f "$SCRIPT_DIR/$TARGET" + echo "Extraction complete." +} + +download_and_extract \ No newline at end of file diff --git a/script/subscripts/parse_log_to_table.sh b/script/subscripts/parse_log_to_table.sh new file mode 100755 index 0000000..fda5c51 --- /dev/null +++ b/script/subscripts/parse_log_to_table.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +OPERATION=$1 + +LOG_FILE="logs/execution_$OPERATION.log" +OUTPUT_FILE="summaries/summary_$OPERATION.md" + +# Table Header +echo '| Operation | Test Case | Total Cycles | Syscall Count | Read Pre-State | Read Operation | Process | Convert SSZ | Commit |' > $OUTPUT_FILE +echo '|-----------|-----------|--------------|--------------|----------------|----------------|---------|-------------|--------|' >> $OUTPUT_FILE + +awk ' +BEGIN { + op = ""; + test_case = ""; + total_cycles = 0; + syscall_count = 0; + read_pre_state = 0; + read_operation = 0; + process_op = 0; + convert_ssz = 0; + commit = 0; +} + +/INFO \[.*\] Test case:/ { + op = $3; + gsub(/[\[\]]/, "", op) + test_case = $NF; +} + +/INFO Number of cycles:/ { + total_cycles = $NF; +} + +/INFO Number of syscall count:/ { + syscall_count = $NF; +} + +/INFO read-pre-state:/ { + read_pre_state = $NF; +} + +/INFO read-operation-input:/ { + read_operation = $NF; +} + +/INFO process-operation:/ { + process_op = $NF; +} + +/INFO convert-to-ssz-bytes:/ { + convert_ssz = $NF; +} + +/INFO commit:/ { + commit = $NF; +} + +/INFO ----- Cycle Tracker End -----/ { + printf "%s | %s | %d | %d | %d | %d | %d | %d | %d\n", op, test_case, total_cycles, syscall_count, read_pre_state, read_operation, process_op, convert_ssz, commit >> "'$OUTPUT_FILE'" + + # Initialize + op = ""; + test_case = ""; + total_cycles = 0; + syscall_count = 0; + read_pre_state = 0; + read_operation = 0; + process_op = 0; + convert_ssz = 0; + commit = 0; +} +' $LOG_FILE diff --git a/script/subscripts/sort_table.sh b/script/subscripts/sort_table.sh new file mode 100755 index 0000000..c4111a1 --- /dev/null +++ b/script/subscripts/sort_table.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +INPUT_FILE=$1 +OUTPUT_FILE=$(echo $INPUT_FILE | awk -F/ '{print $1 "/" "sorted_" $2}') + +HEADER=$(head -n 2 "$INPUT_FILE") + +echo "$HEADER" > "$OUTPUT_FILE" +tail -n +3 "$INPUT_FILE" | sort -t '|' -k1,1 -k2,2 >> "$OUTPUT_FILE" + +rm $INPUT_FILE