From a1d6e7dac747288d478cdb4c390a7711cdf7c9b5 Mon Sep 17 00:00:00 2001 From: Preston Evans <32944016+preston-evans98@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:49:40 -0700 Subject: [PATCH] Enable end-to-end integrations tests including prover->verifier workflow (#842) * Switch ordering of witness * Feature gate native in stf runner * WIP: Fix chain-state signature. Done except for borsh * Fix all interfaces * lint; clippy * Move curr_hash to MockBlockHeader * lint * fix benches * add missing borrow * remove commented code * fix feature gating. Fix demo-prover * fmt demo prover * lint * implement stv * Got to a failing test * Tests pass * lint * lint * fix tests/features * fix prover * lint --- Cargo.lock | 1 - adapters/celestia/src/da_service.rs | 4 +- adapters/celestia/src/verifier/mod.rs | 2 +- adapters/risc0/src/guest.rs | 81 +++++++++++++++++-- adapters/risc0/src/host.rs | 56 +++++++------ .../demo-prover/host/benches/prover_bench.rs | 12 +-- examples/demo-prover/host/src/main.rs | 12 +-- .../methods/guest/src/bin/rollup.rs | 7 +- examples/demo-rollup/Cargo.toml | 1 - examples/demo-rollup/benches/rng_xfers.rs | 24 +++++- examples/demo-rollup/src/main.rs | 6 +- examples/demo-rollup/src/rollup.rs | 70 ++++++++++++---- examples/demo-rollup/tests/all_tests.rs | 3 + examples/demo-rollup/tests/bank/mod.rs | 15 +++- examples/demo-rollup/tests/evm/mod.rs | 4 +- examples/demo-rollup/tests/test_helpers.rs | 9 ++- examples/demo-stf/src/app.rs | 7 +- full-node/sov-stf-runner/src/lib.rs | 30 +++++++ full-node/sov-stf-runner/src/runner.rs | 53 ++++++++++-- full-node/sov-stf-runner/src/verifier.rs | 56 +++++++++++++ .../examples/sov-value-setter/src/tests.rs | 4 +- .../src/nested_modules/tests.rs | 2 +- .../module-template/tests/value_setter.rs | 2 +- .../tests/dispatch/derive_dispatch.rs | 2 +- .../tests/dispatch/derive_genesis.rs | 2 +- .../tests/rpc/derive_rpc.rs | 2 +- .../tests/rpc/derive_rpc_with_where.rs | 2 +- .../expose_rpc_associated_type_not_static.rs | 2 +- .../tests/rpc/expose_rpc_associated_types.rs | 2 +- .../rpc/expose_rpc_associated_types_nested.rs | 2 +- .../expose_rpc_first_generic_not_context.rs | 2 +- module-system/sov-state/src/prover_storage.rs | 7 +- module-system/sov-state/src/witness.rs | 3 +- module-system/sov-state/src/zk_storage.rs | 17 ++-- module-system/sov-state/tests/state_tests.rs | 4 +- rollup-interface/src/node/services/da.rs | 5 +- rollup-interface/src/state_machine/da.rs | 5 +- .../src/state_machine/mocks/da.rs | 31 ++++++- .../src/state_machine/mocks/mod.rs | 2 +- rollup-interface/src/state_machine/stf.rs | 4 +- rollup-interface/src/state_machine/zk/mod.rs | 7 +- 41 files changed, 440 insertions(+), 122 deletions(-) create mode 100644 full-node/sov-stf-runner/src/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index caa76b9ed..e94df23bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6698,7 +6698,6 @@ dependencies = [ "sov-celestia-adapter", "sov-cli", "sov-db", - "sov-demo-rollup", "sov-ethereum", "sov-evm", "sov-modules-api", diff --git a/adapters/celestia/src/da_service.rs b/adapters/celestia/src/da_service.rs index e319d2646..e1eeb9e28 100644 --- a/adapters/celestia/src/da_service.rs +++ b/adapters/celestia/src/da_service.rs @@ -18,7 +18,7 @@ use crate::types::{ExtendedDataSquare, FilteredCelestiaBlock, Row, RpcNamespaced use crate::utils::BoxError; use crate::verifier::address::CelestiaAddress; use crate::verifier::proofs::{CompletenessProof, CorrectnessProof}; -use crate::verifier::{CelestiaSpec, RollupParams, PFB_NAMESPACE}; +use crate::verifier::{CelestiaSpec, CelestiaVerifier, RollupParams, PFB_NAMESPACE}; use crate::{ parse_pfb_namespace, BlobWithSender, CelestiaHeader, CelestiaHeaderResponse, DataAvailabilityHeader, @@ -143,6 +143,8 @@ impl CelestiaService { impl DaService for CelestiaService { type Spec = CelestiaSpec; + type Verifier = CelestiaVerifier; + type FilteredBlock = FilteredCelestiaBlock; type Error = BoxError; diff --git a/adapters/celestia/src/verifier/mod.rs b/adapters/celestia/src/verifier/mod.rs index 388a38388..1e3c9ea7f 100644 --- a/adapters/celestia/src/verifier/mod.rs +++ b/adapters/celestia/src/verifier/mod.rs @@ -166,7 +166,7 @@ impl da::DaVerifier for CelestiaVerifier { } #[cfg_attr(all(target_os = "zkvm", feature = "bench"), cycle_tracker)] - fn verify_relevant_tx_list( + fn verify_relevant_tx_list( &self, block_header: &::BlockHeader, txs: &[::BlobTransaction], diff --git a/adapters/risc0/src/guest.rs b/adapters/risc0/src/guest.rs index a68cf1714..2198c578f 100644 --- a/adapters/risc0/src/guest.rs +++ b/adapters/risc0/src/guest.rs @@ -1,11 +1,14 @@ +#[cfg(not(target_os = "zkvm"))] +use std::ops::DerefMut; + #[cfg(target_os = "zkvm")] use risc0_zkvm::guest::env; +#[cfg(not(target_os = "zkvm"))] +use risc0_zkvm::serde::{Deserializer, WordRead}; use sov_rollup_interface::zk::{Zkvm, ZkvmGuest}; use crate::Risc0MethodId; -pub struct Risc0Guest; - #[cfg(target_os = "zkvm")] impl ZkvmGuest for Risc0Guest { fn read_from_host(&self) -> T { @@ -17,14 +20,82 @@ impl ZkvmGuest for Risc0Guest { } } +#[cfg(not(target_os = "zkvm"))] +#[derive(Default)] +struct Hints { + values: Vec, + position: usize, +} + +#[cfg(not(target_os = "zkvm"))] +impl Hints { + pub fn with_hints(hints: Vec) -> Self { + Hints { + values: hints, + position: 0, + } + } + pub fn remaining(&self) -> usize { + self.values.len() - self.position + } +} + +#[cfg(not(target_os = "zkvm"))] +impl WordRead for Hints { + fn read_words(&mut self, words: &mut [u32]) -> risc0_zkvm::serde::Result<()> { + if self.remaining() < words.len() { + return Err(risc0_zkvm::serde::Error::DeserializeUnexpectedEnd); + } + words.copy_from_slice(&self.values[self.position..self.position + words.len()]); + self.position += words.len(); + Ok(()) + } + + fn read_padded_bytes(&mut self, bytes: &mut [u8]) -> risc0_zkvm::serde::Result<()> { + let remaining_bytes: &[u8] = bytemuck::cast_slice(&self.values[self.position..]); + if bytes.len() > remaining_bytes.len() { + return Err(risc0_zkvm::serde::Error::DeserializeUnexpectedEnd); + } + bytes.copy_from_slice(&remaining_bytes[..bytes.len()]); + self.position += bytes.len() / std::mem::size_of::(); + Ok(()) + } +} + +#[derive(Default)] +pub struct Risc0Guest { + #[cfg(not(target_os = "zkvm"))] + hints: std::sync::Mutex, + #[cfg(not(target_os = "zkvm"))] + commits: std::sync::Mutex>, +} + +impl Risc0Guest { + pub fn new() -> Self { + Self::default() + } + + #[cfg(not(target_os = "zkvm"))] + pub fn with_hints(hints: Vec) -> Self { + Self { + hints: std::sync::Mutex::new(Hints::with_hints(hints)), + commits: Default::default(), + } + } +} + #[cfg(not(target_os = "zkvm"))] impl ZkvmGuest for Risc0Guest { fn read_from_host(&self) -> T { - unimplemented!("This method should only be called in zkvm mode") + let mut hints = self.hints.lock().unwrap(); + let mut hints = hints.deref_mut(); + T::deserialize(&mut Deserializer::new(&mut hints)).unwrap() } - fn commit(&self, _item: &T) { - unimplemented!("This method should only be called in zkvm mode") + fn commit(&self, item: &T) { + self.commits.lock().unwrap().extend_from_slice( + &risc0_zkvm::serde::to_vec(item).expect("Serialization to vec is infallible"), + ); } } diff --git a/adapters/risc0/src/host.rs b/adapters/risc0/src/host.rs index f208fa88b..58a8a881c 100644 --- a/adapters/risc0/src/host.rs +++ b/adapters/risc0/src/host.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::sync::Mutex; use risc0_zkvm::receipt::Receipt; use risc0_zkvm::serde::to_vec; @@ -9,38 +9,36 @@ use sov_rollup_interface::zk::{Zkvm, ZkvmHost}; #[cfg(feature = "bench")] use sov_zk_cycle_utils::{cycle_count_callback, get_syscall_name, get_syscall_name_cycles}; +use crate::guest::Risc0Guest; #[cfg(feature = "bench")] use crate::metrics::metrics_callback; use crate::Risc0MethodId; pub struct Risc0Host<'a> { - env: RefCell>, + env: Mutex>, elf: &'a [u8], } -impl<'a> Risc0Host<'a> { - #[cfg(not(feature = "bench"))] - pub fn new(elf: &'a [u8]) -> Self { - let default_env = ExecutorEnvBuilder::default(); - - Self { - env: RefCell::new(default_env), - elf, - } - } +#[cfg(not(feature = "bench"))] +fn add_benchmarking_callbacks(env: ExecutorEnvBuilder<'_>) -> ExecutorEnvBuilder<'_> { + env +} - #[cfg(feature = "bench")] - pub fn new(elf: &'a [u8]) -> Self { - let mut default_env = ExecutorEnvBuilder::default(); +#[cfg(feature = "bench")] +fn add_benchmarking_callbacks(mut env: ExecutorEnvBuilder<'_>) -> ExecutorEnvBuilder<'_> { + let metrics_syscall_name = get_syscall_name(); + env.io_callback(metrics_syscall_name, metrics_callback); - let metrics_syscall_name = get_syscall_name(); - default_env.io_callback(metrics_syscall_name, metrics_callback); + let cycles_syscall_name = get_syscall_name_cycles(); + env.io_callback(cycles_syscall_name, cycle_count_callback); - let cycles_syscall_name = get_syscall_name_cycles(); - default_env.io_callback(cycles_syscall_name, cycle_count_callback); + env +} +impl<'a> Risc0Host<'a> { + pub fn new(elf: &'a [u8]) -> Self { Self { - env: RefCell::new(default_env), + env: Default::default(), elf, } } @@ -48,11 +46,13 @@ impl<'a> Risc0Host<'a> { /// Run a computation in the zkvm without generating a receipt. /// This creates the "Session" trace without invoking the heavy cryptographic machinery. pub fn run_without_proving(&mut self) -> anyhow::Result { - let env = self.env.borrow_mut().build()?; + let env = add_benchmarking_callbacks(ExecutorEnvBuilder::default()) + .add_input(&self.env.lock().unwrap()) + .build() + .unwrap(); let mut executor = LocalExecutor::from_elf(env, self.elf)?; executor.run() } - /// Run a computation in the zkvm and generate a receipt. pub fn run(&mut self) -> anyhow::Result { let session = self.run_without_proving()?; @@ -61,13 +61,19 @@ impl<'a> Risc0Host<'a> { } impl<'a> ZkvmHost for Risc0Host<'a> { - fn write_to_guest(&self, item: T) { + fn add_hint(&self, item: T) { let serialized = to_vec(&item).expect("Serialization to vec is infallible"); - self.env.borrow_mut().add_input(&serialized); + self.env.lock().unwrap().extend_from_slice(&serialized[..]); + } + + type Guest = Risc0Guest; + + fn simulate_with_hints(&mut self) -> Self::Guest { + Risc0Guest::with_hints(std::mem::take(&mut self.env.lock().unwrap())) } } -impl<'prover> Zkvm for Risc0Host<'prover> { +impl<'host> Zkvm for Risc0Host<'host> { type CodeCommitment = Risc0MethodId; type Error = anyhow::Error; diff --git a/examples/demo-prover/host/benches/prover_bench.rs b/examples/demo-prover/host/benches/prover_bench.rs index d25ed1efb..ffb388d4a 100644 --- a/examples/demo-prover/host/benches/prover_bench.rs +++ b/examples/demo-prover/host/benches/prover_bench.rs @@ -201,7 +201,7 @@ async fn main() -> Result<(), anyhow::Error> { for height in 2..(bincoded_blocks.len() as u64) { num_blocks += 1; let mut host = Risc0Host::new(ROLLUP_ELF); - host.write_to_guest(prev_state_root); + host.add_hint(prev_state_root); println!( "Requesting data for height {} and prev_state_root 0x{}", height, @@ -209,14 +209,14 @@ async fn main() -> Result<(), anyhow::Error> { ); let filtered_block = &bincoded_blocks[height as usize]; let _header_hash = hex::encode(filtered_block.header.header.hash()); - host.write_to_guest(&filtered_block.header); + host.add_hint(&filtered_block.header); let (mut blob_txs, inclusion_proof, completeness_proof) = da_service .extract_relevant_txs_with_proof(&filtered_block) .await; - host.write_to_guest(&inclusion_proof); - host.write_to_guest(&completeness_proof); - host.write_to_guest(&blob_txs); + host.add_hint(&inclusion_proof); + host.add_hint(&completeness_proof); + host.add_hint(&blob_txs); if !blob_txs.is_empty() { num_blobs += blob_txs.len(); @@ -232,7 +232,7 @@ async fn main() -> Result<(), anyhow::Error> { } // println!("{:?}",result.batch_receipts); - host.write_to_guest(&result.witness); + host.add_hint(&result.witness); println!("Skipping prover at block {height} to capture cycle counts\n"); let _receipt = host diff --git a/examples/demo-prover/host/src/main.rs b/examples/demo-prover/host/src/main.rs index d2885e068..8ef7bb9fd 100644 --- a/examples/demo-prover/host/src/main.rs +++ b/examples/demo-prover/host/src/main.rs @@ -92,7 +92,7 @@ async fn main() -> Result<(), anyhow::Error> { // prev_state_root is the root after applying the block at height-1 // This is necessary since we're proving that the current state root for the current height is // result of applying the block against state with root prev_state_root - host.write_to_guest(prev_state_root); + host.add_hint(prev_state_root); info!( "Requesting data for height {} and prev_state_root 0x{}", height, @@ -100,7 +100,7 @@ async fn main() -> Result<(), anyhow::Error> { ); let filtered_block = da_service.get_finalized_at(height).await?; let header_hash = hex::encode(filtered_block.header.header.hash()); - host.write_to_guest(&filtered_block.header); + host.add_hint(&filtered_block.header); // When we get a block from DA, we also need to provide proofs of completeness and correctness // https://github.com/Sovereign-Labs/sovereign-sdk/blob/nightly/rollup-interface/specs/interfaces/da.md#type-inclusionmultiproof let (mut blobs, inclusion_proof, completeness_proof) = da_service @@ -115,8 +115,8 @@ async fn main() -> Result<(), anyhow::Error> { ); // The above proofs of correctness and completeness need to passed to the prover - host.write_to_guest(&inclusion_proof); - host.write_to_guest(&completeness_proof); + host.add_hint(&inclusion_proof); + host.add_hint(&completeness_proof); let result = app.stf.apply_slot( Default::default(), @@ -127,11 +127,11 @@ async fn main() -> Result<(), anyhow::Error> { // The extracted blobs need to be passed to the prover after execution. // (Without executing, the host couldn't prune any data that turned out to be irrelevant to the guest) - host.write_to_guest(&blobs); + host.add_hint(&blobs); // Witness contains the merkle paths to the state root so that the code inside the VM // can access state values (Witness can also contain other hints and proofs) - host.write_to_guest(&result.witness); + host.add_hint(&result.witness); // Run the actual prover to generate a receipt that can then be verified if !skip_prover { diff --git a/examples/demo-prover/methods/guest/src/bin/rollup.rs b/examples/demo-prover/methods/guest/src/bin/rollup.rs index ec2244630..b15351f3c 100644 --- a/examples/demo-prover/methods/guest/src/bin/rollup.rs +++ b/examples/demo-prover/methods/guest/src/bin/rollup.rs @@ -13,7 +13,6 @@ use sov_celestia_adapter::verifier::address::CelestiaAddress; use sov_celestia_adapter::verifier::{CelestiaSpec, CelestiaVerifier}; use sov_celestia_adapter::{BlobWithSender, CelestiaHeader}; use sov_risc0_adapter::guest::Risc0Guest; -use sov_rollup_interface::crypto::NoOpHasher; use sov_rollup_interface::da::{BlockHeaderTrait, DaSpec, DaVerifier}; use sov_rollup_interface::stf::StateTransitionFunction; use sov_rollup_interface::zk::{StateTransition, ZkvmGuest}; @@ -32,7 +31,7 @@ risc0_zkvm::guest::entry!(main); // 6. Output (Da hash, start_root, end_root, event_root) pub fn main() { env::write(&"Start guest\n"); - let guest = Risc0Guest; + let guest = Risc0Guest{}; #[cfg(feature = "bench")] let start_cycles = env::get_cycle_count(); @@ -54,12 +53,12 @@ pub fn main() { }); let validity_condition = verifier - .verify_relevant_tx_list::(&header, &blobs, inclusion_proof, completeness_proof) + .verify_relevant_tx_list(&header, &blobs, inclusion_proof, completeness_proof) .expect("Transaction list must be correct"); env::write(&"Relevant txs verified\n"); // Step 3: Apply blobs - let mut app = create_zk_app_template::(prev_state_root_hash); + let mut app = create_zk_app_template::(); let witness: ArrayWitness = guest.read_from_host(); env::write(&"Witness have been read\n"); diff --git a/examples/demo-rollup/Cargo.toml b/examples/demo-rollup/Cargo.toml index 17551bc77..92f5d3f7c 100644 --- a/examples/demo-rollup/Cargo.toml +++ b/examples/demo-rollup/Cargo.toml @@ -67,7 +67,6 @@ ethers-signers = { workspace = true } ethers = { workspace = true } revm = { workspace = true } -sov-demo-rollup = { path = ".", features = ["experimental"] } [features] default = [] diff --git a/examples/demo-rollup/benches/rng_xfers.rs b/examples/demo-rollup/benches/rng_xfers.rs index 39ae23cf9..72c3e4c4b 100644 --- a/examples/demo-rollup/benches/rng_xfers.rs +++ b/examples/demo-rollup/benches/rng_xfers.rs @@ -8,7 +8,7 @@ use sov_modules_api::default_context::DefaultContext; use sov_modules_api::default_signature::private_key::DefaultPrivateKey; use sov_modules_api::transaction::Transaction; use sov_modules_api::{Address, AddressBech32, EncodeCall, PrivateKey, PublicKey, Spec}; -use sov_rollup_interface::da::DaSpec; +use sov_rollup_interface::da::{DaSpec, DaVerifier}; use sov_rollup_interface::mocks::{ MockAddress, MockBlob, MockBlock, MockBlockHeader, MockHash, MockValidityCond, }; @@ -109,6 +109,7 @@ impl DaSpec for RngDaSpec { #[async_trait] impl DaService for RngDaService { type Spec = RngDaSpec; + type Verifier = RngDaVerifier; type FilteredBlock = MockBlock; type Error = anyhow::Error; @@ -174,3 +175,24 @@ impl DaService for RngDaService { unimplemented!() } } + +pub struct RngDaVerifier; +impl DaVerifier for RngDaVerifier { + type Spec = RngDaSpec; + + type Error = anyhow::Error; + + fn new(_params: ::ChainParams) -> Self { + Self + } + + fn verify_relevant_tx_list( + &self, + _block_header: &::BlockHeader, + _txs: &[::BlobTransaction], + _inclusion_proof: ::InclusionMultiProof, + _completeness_proof: ::CompletenessProof, + ) -> Result<::ValidityCondition, Self::Error> { + Ok(MockValidityCond { is_valid: true }) + } +} diff --git a/examples/demo-rollup/src/main.rs b/examples/demo-rollup/src/main.rs index 44d33497f..cfccceff3 100644 --- a/examples/demo-rollup/src/main.rs +++ b/examples/demo-rollup/src/main.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use clap::Parser; use sov_demo_rollup::{new_rollup_with_celestia_da, new_rollup_with_mock_da}; +use sov_risc0_adapter::host::Risc0Host; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; @@ -37,11 +38,12 @@ async fn main() -> Result<(), anyhow::Error> { match args.da_layer.as_str() { "mock" => { - let rollup = new_rollup_with_mock_da(rollup_config_path)?; + let rollup = new_rollup_with_mock_da::>(rollup_config_path, None)?; rollup.run().await } "celestia" => { - let rollup = new_rollup_with_celestia_da(rollup_config_path).await?; + let rollup = + new_rollup_with_celestia_da::>(rollup_config_path, None).await?; rollup.run().await } da => panic!("DA Layer not supported: {}", da), diff --git a/examples/demo-rollup/src/rollup.rs b/examples/demo-rollup/src/rollup.rs index 029a11f46..79280982a 100644 --- a/examples/demo-rollup/src/rollup.rs +++ b/examples/demo-rollup/src/rollup.rs @@ -5,23 +5,26 @@ use anyhow::Context; use const_rollup_config::SEQUENCER_DA_ADDRESS; #[cfg(feature = "experimental")] use demo_stf::app::DefaultPrivateKey; -use demo_stf::app::{App, DefaultContext}; -use demo_stf::runtime::{get_rpc_methods, GenesisConfig}; +use demo_stf::app::{create_zk_app_template, App, DefaultContext}; +use demo_stf::runtime::{get_rpc_methods, GenesisConfig, Runtime}; #[cfg(feature = "experimental")] use secp256k1::SecretKey; use sov_celestia_adapter::verifier::address::CelestiaAddress; -use sov_celestia_adapter::verifier::RollupParams; +use sov_celestia_adapter::verifier::{CelestiaVerifier, RollupParams}; use sov_celestia_adapter::CelestiaService; #[cfg(feature = "experimental")] use sov_cli::wallet_state::PrivateKeyAndAddress; use sov_db::ledger_db::LedgerDB; #[cfg(feature = "experimental")] use sov_ethereum::experimental::EthRpcConfig; -use sov_risc0_adapter::host::Risc0Verifier; +use sov_modules_api::default_context::ZkDefaultContext; +use sov_modules_stf_template::AppTemplate; +use sov_rollup_interface::da::DaVerifier; use sov_rollup_interface::mocks::{MockAddress, MockDaConfig, MockDaService}; use sov_rollup_interface::services::da::DaService; -use sov_rollup_interface::zk::Zkvm; +use sov_rollup_interface::zk::ZkvmHost; use sov_state::storage::Storage; +use sov_stf_runner::verifier::StateTransitionVerifier; use sov_stf_runner::{from_toml_path, RollupConfig, RunnerConfig, StateTransitionRunner}; use tokio::sync::oneshot; use tracing::debug; @@ -35,7 +38,7 @@ use crate::{get_genesis_config, initialize_ledger, ROLLUP_NAMESPACE}; const TX_SIGNER_PRIV_KEY_PATH: &str = "../test-data/keys/tx_signer_private_key.json"; /// Dependencies needed to run the rollup. -pub struct Rollup { +pub struct Rollup { /// Implementation of the STF. pub app: App, /// Data availability service. @@ -49,12 +52,28 @@ pub struct Rollup { #[cfg(feature = "experimental")] /// Configuration for the Ethereum RPC. pub eth_rpc_config: EthRpcConfig, + /// Verifier for the rollup. + #[allow(clippy::type_complexity)] + pub verifier: Option<(Vm, AppVerifier)>, } +/// A verifier for the demo rollup +pub type AppVerifier = StateTransitionVerifier< + AppTemplate< + ZkDefaultContext, + ::Spec, + Zk, + Runtime::Spec>, + >, + DA, + Zk, +>; + /// Creates celestia based rollup. -pub async fn new_rollup_with_celestia_da( +pub async fn new_rollup_with_celestia_da( rollup_config_path: &str, -) -> Result, anyhow::Error> { + prover: Option, +) -> Result, anyhow::Error> { debug!( "Starting demo celestia rollup with config {}", rollup_config_path @@ -82,6 +101,17 @@ pub async fn new_rollup_with_celestia_da( #[cfg(feature = "experimental")] eth_signer.signers(), ); + let verifier = prover.map(|p| { + ( + p, + AppVerifier::new( + create_zk_app_template(), + CelestiaVerifier { + rollup_namespace: ROLLUP_NAMESPACE, + }, + ), + ) + }); Ok(Rollup { app, @@ -95,25 +125,28 @@ pub async fn new_rollup_with_celestia_da( sov_tx_signer_priv_key: read_sov_tx_signer_priv_key()?, eth_signer, }, + verifier, }) } /// Creates MockDa based rollup. -pub fn new_rollup_with_mock_da( +pub fn new_rollup_with_mock_da( rollup_config_path: &str, -) -> Result, anyhow::Error> { + prover: Option, +) -> Result, anyhow::Error> { debug!("Starting mock rollup with config {}", rollup_config_path); let rollup_config: RollupConfig = from_toml_path(rollup_config_path).context("Failed to read rollup configuration")?; - new_rollup_with_mock_da_from_config(rollup_config) + new_rollup_with_mock_da_from_config(rollup_config, prover) } /// Creates MockDa based rollup. -pub fn new_rollup_with_mock_da_from_config( +pub fn new_rollup_with_mock_da_from_config( rollup_config: RollupConfig, -) -> Result, anyhow::Error> { + prover: Option, +) -> Result, anyhow::Error> { let ledger_db = initialize_ledger(&rollup_config.storage.path); let sequencer_da_address = MockAddress::from([0u8; 32]); let da_service = MockDaService::new(sequencer_da_address); @@ -127,6 +160,12 @@ pub fn new_rollup_with_mock_da_from_config( eth_signer.signers(), ); + let verifier = prover.map(|p| { + ( + p, + AppVerifier::new(create_zk_app_template(), Default::default()), + ) + }); Ok(Rollup { app, da_service, @@ -139,6 +178,8 @@ pub fn new_rollup_with_mock_da_from_config( sov_tx_signer_priv_key: read_sov_tx_signer_priv_key()?, eth_signer, }, + // TODO: add verifier + verifier, }) } @@ -163,7 +204,7 @@ fn read_eth_tx_signers() -> sov_ethereum::DevSigner { .unwrap()]) } -impl + Clone> Rollup { +impl + Clone> Rollup { /// Runs the rollup. pub async fn run(self) -> Result<(), anyhow::Error> { self.run_and_report_rpc_port(None).await @@ -194,6 +235,7 @@ impl + Clone> Rollup { self.app.stf, storage.is_empty(), self.genesis_config, + self.verifier, )?; runner.start_rpc_server(methods, channel).await; diff --git a/examples/demo-rollup/tests/all_tests.rs b/examples/demo-rollup/tests/all_tests.rs index ab4829fa8..fb5916cf5 100644 --- a/examples/demo-rollup/tests/all_tests.rs +++ b/examples/demo-rollup/tests/all_tests.rs @@ -1,3 +1,6 @@ +// For now, exclude the evm from the bank tests +#[cfg(not(feature = "experimental"))] mod bank; +#[cfg(feature = "experimental")] mod evm; mod test_helpers; diff --git a/examples/demo-rollup/tests/bank/mod.rs b/examples/demo-rollup/tests/bank/mod.rs index c2c5cb42a..a60fed7f5 100644 --- a/examples/demo-rollup/tests/bank/mod.rs +++ b/examples/demo-rollup/tests/bank/mod.rs @@ -8,6 +8,7 @@ use jsonrpsee::rpc_params; use sov_modules_api::default_context::DefaultContext; use sov_modules_api::transaction::Transaction; use sov_modules_api::{PrivateKey, Spec}; +use sov_risc0_adapter::host::Risc0Host; use sov_rollup_interface::mocks::MockDaSpec; use sov_sequencer::utils::SimpleClient; @@ -67,14 +68,22 @@ async fn send_test_create_token_tx(rpc_address: SocketAddr) -> Result<(), anyhow async fn bank_tx_tests() -> Result<(), anyhow::Error> { let (port_tx, port_rx) = tokio::sync::oneshot::channel(); + // Use a dummy `elf` file, since the prover doesn't currently use it in native execution + let prover = Risc0Host::new(&[]); + let rollup_task = tokio::spawn(async { - start_rollup(port_tx).await; + start_rollup(port_tx, Some(prover)).await; }); // Wait for rollup task to start: let port = port_rx.await.unwrap(); - send_test_create_token_tx(port).await?; - rollup_task.abort(); + // If the rollup throws an error, stop trying to send the transaction and return it + tokio::select! { + err = rollup_task => { + err?; + }, + res = send_test_create_token_tx(port) =>{res?}, + }; Ok(()) } diff --git a/examples/demo-rollup/tests/evm/mod.rs b/examples/demo-rollup/tests/evm/mod.rs index 7b1c18755..d9c740084 100644 --- a/examples/demo-rollup/tests/evm/mod.rs +++ b/examples/demo-rollup/tests/evm/mod.rs @@ -13,6 +13,7 @@ use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; use jsonrpsee::rpc_params; use sov_evm::smart_contracts::SimpleStorageContract; +use sov_risc0_adapter::host::Risc0Host; use super::test_helpers::start_rollup; @@ -196,7 +197,8 @@ async fn evm_tx_tests() -> Result<(), anyhow::Error> { let (port_tx, port_rx) = tokio::sync::oneshot::channel(); let rollup_task = tokio::spawn(async { - start_rollup(port_tx).await; + // Don't provide a prover since the EVM is not currently provable + start_rollup::>(port_tx, None).await; }); // Wait for rollup task to start: diff --git a/examples/demo-rollup/tests/test_helpers.rs b/examples/demo-rollup/tests/test_helpers.rs index 121363fef..d340efd77 100644 --- a/examples/demo-rollup/tests/test_helpers.rs +++ b/examples/demo-rollup/tests/test_helpers.rs @@ -1,12 +1,15 @@ use std::net::SocketAddr; use sov_demo_rollup::new_rollup_with_mock_da_from_config; -#[cfg(feature = "experimental")] use sov_rollup_interface::mocks::MockDaConfig; +use sov_rollup_interface::zk::ZkvmHost; use sov_stf_runner::{RollupConfig, RpcConfig, RunnerConfig, StorageConfig}; use tokio::sync::oneshot; -pub async fn start_rollup(rpc_reporting_channel: oneshot::Sender) { +pub async fn start_rollup( + rpc_reporting_channel: oneshot::Sender, + prover: Option, +) { let temp_dir = tempfile::tempdir().unwrap(); let temp_path = temp_dir.path(); @@ -24,7 +27,7 @@ pub async fn start_rollup(rpc_reporting_channel: oneshot::Sender) { da: MockDaConfig {}, }; let rollup = - new_rollup_with_mock_da_from_config(rollup_config).expect("Rollup config is valid"); + new_rollup_with_mock_da_from_config(rollup_config, prover).expect("Rollup config is valid"); rollup .run_and_report_rpc_port(Some(rpc_reporting_channel)) .await diff --git a/examples/demo-stf/src/app.rs b/examples/demo-stf/src/app.rs index c326f2a9f..27970c9ae 100644 --- a/examples/demo-stf/src/app.rs +++ b/examples/demo-stf/src/app.rs @@ -9,9 +9,9 @@ use sov_modules_stf_template::AppTemplate; pub use sov_modules_stf_template::Batch; use sov_rollup_interface::da::DaSpec; use sov_rollup_interface::zk::Zkvm; +use sov_state::ZkStorage; #[cfg(feature = "native")] -use sov_state::ProverStorage; -use sov_state::{Storage, ZkStorage}; +use sov_state::{ProverStorage, Storage}; #[cfg(feature = "native")] use sov_stf_runner::FiFoStrictBatchBuilder; #[cfg(feature = "native")] @@ -50,8 +50,7 @@ impl App { } pub fn create_zk_app_template( - runtime_config: [u8; 32], ) -> AppTemplate> { - let storage = ZkStorage::with_config(runtime_config).expect("Failed to open zk storage"); + let storage = ZkStorage::new(); AppTemplate::new(storage, Runtime::default()) } diff --git a/full-node/sov-stf-runner/src/lib.rs b/full-node/sov-stf-runner/src/lib.rs index 2db8ae86d..defc63c28 100644 --- a/full-node/sov-stf-runner/src/lib.rs +++ b/full-node/sov-stf-runner/src/lib.rs @@ -6,6 +6,7 @@ mod batch_builder; #[cfg(feature = "native")] mod config; +use borsh::{BorshDeserialize, BorshSerialize}; #[cfg(feature = "native")] pub use config::RpcConfig; #[cfg(feature = "native")] @@ -20,3 +21,32 @@ pub use config::{from_toml_path, RollupConfig, RunnerConfig, StorageConfig}; pub use ledger_rpc::get_ledger_rpc; #[cfg(feature = "native")] pub use runner::*; +use serde::{Deserialize, Serialize}; +use sov_modules_api::{DaSpec, Zkvm}; +use sov_rollup_interface::stf::StateTransitionFunction; + +/// Implements the `StateTransitionVerifier` type for checking the validity of a state transition +pub mod verifier; + +#[derive(Serialize, BorshDeserialize, BorshSerialize, Deserialize)] +// Prevent serde from generating spurious trait bounds. The correct serde bounds are already enforced by the +// StateTransitionFunction, DA, and Zkvm traits. +#[serde(bound = "")] +/// Data required to verify a state transition. +pub struct StateTransitionData, DA: DaSpec, Zk> +where + Zk: Zkvm, +{ + /// The state root before the state transition + pub pre_state_root: ST::StateRoot, + /// The header of the da block that is being processed + pub da_block_header: DA::BlockHeader, + /// The proof of inclusion for all blobs + pub inclusion_proof: DA::InclusionMultiProof, + /// The proof that the provided set of blobs is complete + pub completeness_proof: DA::CompletenessProof, + /// The blobs that are being processed + pub blobs: Vec<::BlobTransaction>, + /// The witness for the state transition + pub state_transition_witness: ST::Witness, +} diff --git a/full-node/sov-stf-runner/src/runner.rs b/full-node/sov-stf-runner/src/runner.rs index 178ed5c6c..c722bd5e3 100644 --- a/full-node/sov-stf-runner/src/runner.rs +++ b/full-node/sov-stf-runner/src/runner.rs @@ -6,22 +6,24 @@ use sov_modules_api::SlotData; use sov_rollup_interface::da::{BlobReaderTrait, DaSpec}; use sov_rollup_interface::services::da::DaService; use sov_rollup_interface::stf::StateTransitionFunction; -use sov_rollup_interface::zk::Zkvm; +use sov_rollup_interface::zk::ZkvmHost; use tokio::sync::oneshot; use tracing::{debug, info}; -use crate::RunnerConfig; +use crate::verifier::StateTransitionVerifier; +use crate::{RunnerConfig, StateTransitionData}; type StateRoot = >::StateRoot; type InitialState = >::InitialState; /// Combines `DaService` with `StateTransitionFunction` and "runs" the rollup. -pub struct StateTransitionRunner +pub struct StateTransitionRunner where Da: DaService, - Vm: Zkvm, + Vm: ZkvmHost, ST: StateTransitionFunction::ValidityCondition>, + V: StateTransitionFunction, { start_height: u64, da_service: Da, @@ -29,15 +31,27 @@ where ledger_db: LedgerDB, state_root: StateRoot, listen_address: SocketAddr, + #[allow(clippy::type_complexity)] + verifier: Option<(Vm, StateTransitionVerifier)>, } -impl StateTransitionRunner +impl StateTransitionRunner where Da: DaService + Clone + Send + Sync + 'static, - Vm: Zkvm, - ST: StateTransitionFunction::ValidityCondition>, + Vm: ZkvmHost, + V: StateTransitionFunction, + ST: StateTransitionFunction< + Vm, + Da::Spec, + StateRoot = Root, + Condition = ::ValidityCondition, + Witness = Witness, + >, + Witness: Default, + Root: Clone, { /// Creates a new `StateTransitionRunner` runner. + #[allow(clippy::type_complexity)] pub fn new( runner_config: RunnerConfig, da_service: Da, @@ -45,6 +59,7 @@ where mut app: ST, should_init_chain: bool, genesis_config: InitialState, + verifier: Option<(Vm, StateTransitionVerifier)>, ) -> Result { let rpc_config = runner_config.rpc_config; @@ -75,6 +90,7 @@ where ledger_db, state_root: prev_state_root, listen_address, + verifier, }) } @@ -135,6 +151,29 @@ where for receipt in slot_result.batch_receipts { data_to_commit.add_batch(receipt); } + if let Some((host, verifier)) = self.verifier.as_mut() { + let (inclusion_proof, completeness_proof) = self + .da_service + .get_extraction_proof(&filtered_block, &blobs) + .await; + + let transition_data: StateTransitionData = + StateTransitionData { + pre_state_root: self.state_root.clone(), + da_block_header: filtered_block.header().clone(), + inclusion_proof, + completeness_proof, + blobs, + state_transition_witness: slot_result.witness, + }; + host.add_hint(transition_data); + + verifier + .run_block(host.simulate_with_hints()) + .map_err(|e| { + anyhow::anyhow!("Guest execution must succeed but failed with {:?}", e) + })?; + } let next_state_root = slot_result.state_root; self.ledger_db.commit_slot(data_to_commit)?; diff --git a/full-node/sov-stf-runner/src/verifier.rs b/full-node/sov-stf-runner/src/verifier.rs new file mode 100644 index 000000000..899474bd4 --- /dev/null +++ b/full-node/sov-stf-runner/src/verifier.rs @@ -0,0 +1,56 @@ +use std::marker::PhantomData; + +use sov_modules_api::Zkvm; +use sov_rollup_interface::da::DaVerifier; +use sov_rollup_interface::stf::StateTransitionFunction; +use sov_rollup_interface::zk::ZkvmGuest; + +use crate::StateTransitionData; + +/// Verifies a state transition +pub struct StateTransitionVerifier +where + Da: DaVerifier, + Zk: Zkvm, + ST: StateTransitionFunction, +{ + app: ST, + da_verifier: Da, + phantom: PhantomData, +} +impl StateTransitionVerifier +where + Da: DaVerifier, + Zk: ZkvmGuest, + ST: StateTransitionFunction, +{ + /// Create a [`StateTransitionVerifier`] + pub fn new(app: ST, da_verifier: Da) -> Self { + Self { + app, + da_verifier, + phantom: Default::default(), + } + } + + /// Verify the next block + pub fn run_block(&mut self, zkvm: Zk) -> Result { + let mut data: StateTransitionData = zkvm.read_from_host(); + let validity_condition = self.da_verifier.verify_relevant_tx_list( + &data.da_block_header, + &data.blobs, + data.inclusion_proof, + data.completeness_proof, + )?; + + let result = self.app.apply_slot( + data.state_transition_witness, + &data.da_block_header, + &validity_condition, + &mut data.blobs, + ); + + zkvm.commit(&result.state_root); + Ok(result.state_root) + } +} diff --git a/module-system/module-implementations/examples/sov-value-setter/src/tests.rs b/module-system/module-implementations/examples/sov-value-setter/src/tests.rs index 5c3747479..03cbafcfb 100644 --- a/module-system/module-implementations/examples/sov-value-setter/src/tests.rs +++ b/module-system/module-implementations/examples/sov-value-setter/src/tests.rs @@ -24,7 +24,7 @@ fn test_value_setter() { { let config = ValueSetterConfig { admin }; let zk_context = ZkDefaultContext::new(admin); - let mut zk_working_set = WorkingSet::with_witness(ZkStorage::new([0u8; 32]), witness); + let mut zk_working_set = WorkingSet::with_witness(ZkStorage::new(), witness); test_value_setter_helper(zk_context, &config, &mut zk_working_set); } } @@ -85,7 +85,7 @@ fn test_err_on_sender_is_not_admin() { let config = ValueSetterConfig { admin: sender_not_admin, }; - let zk_backing_store = ZkStorage::new([0u8; 32]); + let zk_backing_store = ZkStorage::new(); let zk_context = ZkDefaultContext::new(sender); let zk_working_set = &mut WorkingSet::with_witness(zk_backing_store, witness); test_err_on_sender_is_not_admin_helper(zk_context, &config, zk_working_set); diff --git a/module-system/module-implementations/integration-tests/src/nested_modules/tests.rs b/module-system/module-implementations/integration-tests/src/nested_modules/tests.rs index aac0cfd7a..294afb047 100644 --- a/module-system/module-implementations/integration-tests/src/nested_modules/tests.rs +++ b/module-system/module-implementations/integration-tests/src/nested_modules/tests.rs @@ -33,7 +33,7 @@ fn nested_module_call_test() { // Test the `zk` execution. { - let zk_storage = ZkStorage::new([0u8; 32]); + let zk_storage = ZkStorage::new(); let working_set = &mut WorkingSet::with_witness(zk_storage, witness); execute_module_logic::(working_set); test_state_update::(working_set); diff --git a/module-system/module-implementations/module-template/tests/value_setter.rs b/module-system/module-implementations/module-template/tests/value_setter.rs index b8ad67bb3..e000747ba 100644 --- a/module-system/module-implementations/module-template/tests/value_setter.rs +++ b/module-system/module-implementations/module-template/tests/value_setter.rs @@ -28,7 +28,7 @@ fn test_value_setter() { { let config = ExampleModuleConfig {}; let zk_context = ZkDefaultContext::new(admin); - let mut zk_working_set = WorkingSet::with_witness(ZkStorage::new([0u8; 32]), witness); + let mut zk_working_set = WorkingSet::with_witness(ZkStorage::new(), witness); test_value_setter_helper(zk_context, &config, &mut zk_working_set); } } diff --git a/module-system/sov-modules-macros/tests/dispatch/derive_dispatch.rs b/module-system/sov-modules-macros/tests/dispatch/derive_dispatch.rs index 38952914d..77d94d268 100644 --- a/module-system/sov-modules-macros/tests/dispatch/derive_dispatch.rs +++ b/module-system/sov-modules-macros/tests/dispatch/derive_dispatch.rs @@ -24,7 +24,7 @@ fn main() { type RT = Runtime; let runtime = &mut RT::default(); - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let mut working_set = &mut sov_state::WorkingSet::new(storage); let config = GenesisConfig::new((), (), ()); runtime.genesis(&config, working_set).unwrap(); diff --git a/module-system/sov-modules-macros/tests/dispatch/derive_genesis.rs b/module-system/sov-modules-macros/tests/dispatch/derive_genesis.rs index 580a564c9..809d9d2fd 100644 --- a/module-system/sov-modules-macros/tests/dispatch/derive_genesis.rs +++ b/module-system/sov-modules-macros/tests/dispatch/derive_genesis.rs @@ -22,7 +22,7 @@ where fn main() { type C = ZkDefaultContext; - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let mut working_set = &mut sov_state::WorkingSet::new(storage); let runtime = &mut Runtime::::default(); let config = GenesisConfig::new((), (), ()); diff --git a/module-system/sov-modules-macros/tests/rpc/derive_rpc.rs b/module-system/sov-modules-macros/tests/rpc/derive_rpc.rs index 0177fc500..7e4a6c4e1 100644 --- a/module-system/sov-modules-macros/tests/rpc/derive_rpc.rs +++ b/module-system/sov-modules-macros/tests/rpc/derive_rpc.rs @@ -60,7 +60,7 @@ impl TestStructRpcImpl for RpcStorage { } fn main() { - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let r: RpcStorage = RpcStorage { storage: storage.clone(), }; diff --git a/module-system/sov-modules-macros/tests/rpc/derive_rpc_with_where.rs b/module-system/sov-modules-macros/tests/rpc/derive_rpc_with_where.rs index 716d8f0e6..8e944f8be 100644 --- a/module-system/sov-modules-macros/tests/rpc/derive_rpc_with_where.rs +++ b/module-system/sov-modules-macros/tests/rpc/derive_rpc_with_where.rs @@ -73,7 +73,7 @@ impl TestStructRpcImpl for RpcStorage { } fn main() { - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let r: RpcStorage = RpcStorage { storage: storage.clone(), }; diff --git a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_type_not_static.rs b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_type_not_static.rs index b71e8a18e..e92f7c0c3 100644 --- a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_type_not_static.rs +++ b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_type_not_static.rs @@ -111,7 +111,7 @@ impl TestSpec for ActualSpec { fn main() { type C = ZkDefaultContext; type RT = Runtime; - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let working_set = &mut WorkingSet::new(storage); let runtime = &mut Runtime::::default(); let config = GenesisConfig::new(22); diff --git a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types.rs b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types.rs index 352553613..4316511c5 100644 --- a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types.rs +++ b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types.rs @@ -111,7 +111,7 @@ impl TestSpec for ActualSpec { fn main() { type C = ZkDefaultContext; type RT = Runtime; - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let working_set = &mut WorkingSet::new(storage); let runtime = &mut Runtime::::default(); let config = GenesisConfig::new(22); diff --git a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types_nested.rs b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types_nested.rs index 87242a9ea..b056cd885 100644 --- a/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types_nested.rs +++ b/module-system/sov-modules-macros/tests/rpc/expose_rpc_associated_types_nested.rs @@ -122,7 +122,7 @@ impl TestSpec for ActualSpec { fn main() { type C = ZkDefaultContext; type RT = Runtime; - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let working_set = &mut WorkingSet::new(storage); let runtime = &mut Runtime::::default(); let config = GenesisConfig::new(22); diff --git a/module-system/sov-modules-macros/tests/rpc/expose_rpc_first_generic_not_context.rs b/module-system/sov-modules-macros/tests/rpc/expose_rpc_first_generic_not_context.rs index 70af8d1aa..71c1a9384 100644 --- a/module-system/sov-modules-macros/tests/rpc/expose_rpc_first_generic_not_context.rs +++ b/module-system/sov-modules-macros/tests/rpc/expose_rpc_first_generic_not_context.rs @@ -111,7 +111,7 @@ impl TestSpec for ActualSpec { fn main() { type C = ZkDefaultContext; type RT = Runtime; - let storage = ZkStorage::new([1u8; 32]); + let storage = ZkStorage::new(); let working_set = &mut WorkingSet::new(storage); let runtime = &mut Runtime::::default(); let config = GenesisConfig::new(22); diff --git a/module-system/sov-state/src/prover_storage.rs b/module-system/sov-state/src/prover_storage.rs index 70061749c..bed8157cc 100644 --- a/module-system/sov-state/src/prover_storage.rs +++ b/module-system/sov-state/src/prover_storage.rs @@ -96,8 +96,6 @@ impl Storage for ProverStorage { witness: &Self::Witness, ) -> Result<([u8; 32], Self::StateUpdate), anyhow::Error> { let latest_version = self.db.get_next_version() - 1; - witness.add_hint(latest_version); - let read_logger = TreeReadLogger::with_db_and_witness(self.db.clone(), witness); let untracked_jmt = JellyfishMerkleTree::<_, S::Hasher>::new(&self.db); @@ -116,6 +114,11 @@ impl Storage for ProverStorage { .write_node_batch(&tree_update.node_batch) .expect("db write must succeed"); } + let prev_root = untracked_jmt + .get_root_hash(latest_version) + .expect("Previous root hash was just populated"); + witness.add_hint(prev_root.0); + witness.add_hint(latest_version); // For each value that's been read from the tree, read it from the logged JMT to populate hints for (key, read_value) in state_accesses.ordered_reads { diff --git a/module-system/sov-state/src/witness.rs b/module-system/sov-state/src/witness.rs index eaa202cda..4c8bf1492 100644 --- a/module-system/sov-state/src/witness.rs +++ b/module-system/sov-state/src/witness.rs @@ -3,11 +3,12 @@ use std::sync::Mutex; use borsh::{BorshDeserialize, BorshSerialize}; use jmt::storage::TreeReader; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; // TODO: Refactor witness trait so it only require Serialize / Deserialize // https://github.com/Sovereign-Labs/sovereign-sdk/issues/263 -pub trait Witness: Default + Serialize { +pub trait Witness: Default + Serialize + DeserializeOwned { fn add_hint(&self, hint: T); fn get_hint(&self) -> T; fn merge(&self, rhs: &Self); diff --git a/module-system/sov-state/src/zk_storage.rs b/module-system/sov-state/src/zk_storage.rs index d8e6ce64b..abc0aeaa6 100644 --- a/module-system/sov-state/src/zk_storage.rs +++ b/module-system/sov-state/src/zk_storage.rs @@ -14,24 +14,22 @@ use crate::{MerkleProofSpec, Storage}; #[cfg(all(target_os = "zkvm", feature = "bench"))] extern crate risc0_zkvm; +#[derive(Default)] pub struct ZkStorage { - prev_state_root: [u8; 32], _phantom_hasher: PhantomData, } impl Clone for ZkStorage { fn clone(&self) -> Self { Self { - prev_state_root: self.prev_state_root, _phantom_hasher: Default::default(), } } } impl ZkStorage { - pub fn new(prev_state_root: [u8; 32]) -> Self { + pub fn new() -> Self { Self { - prev_state_root, _phantom_hasher: Default::default(), } } @@ -39,12 +37,12 @@ impl ZkStorage { impl Storage for ZkStorage { type Witness = S::Witness; - type RuntimeConfig = [u8; 32]; + type RuntimeConfig = (); type Proof = jmt::proof::SparseMerkleProof; type StateUpdate = NodeBatch; - fn with_config(config: Self::RuntimeConfig) -> Result { - Ok(Self::new(config)) + fn with_config(_config: Self::RuntimeConfig) -> Result { + Ok(Self::new()) } fn get(&self, _key: &StorageKey, witness: &Self::Witness) -> Option { @@ -61,6 +59,7 @@ impl Storage for ZkStorage { state_accesses: OrderedReadsAndWrites, witness: &Self::Witness, ) -> Result<([u8; 32], Self::StateUpdate), anyhow::Error> { + let prev_state_root = witness.get_hint(); let latest_version: Version = witness.get_hint(); let reader = TreeWitnessReader::new(witness); @@ -71,11 +70,11 @@ impl Storage for ZkStorage { let proof: jmt::proof::SparseMerkleProof = witness.get_hint(); match read_value { Some(val) => proof.verify_existence( - jmt::RootHash(self.prev_state_root), + jmt::RootHash(prev_state_root), key_hash, val.value.as_ref(), )?, - None => proof.verify_nonexistence(jmt::RootHash(self.prev_state_root), key_hash)?, + None => proof.verify_nonexistence(jmt::RootHash(prev_state_root), key_hash)?, } } diff --git a/module-system/sov-state/tests/state_tests.rs b/module-system/sov-state/tests/state_tests.rs index d4692fd2f..63de3ef99 100644 --- a/module-system/sov-state/tests/state_tests.rs +++ b/module-system/sov-state/tests/state_tests.rs @@ -8,8 +8,6 @@ enum Operation { Finalize, } -const EMPTY_ROOT: [u8; 32] = *b"SPARSE_MERKLE_PLACEHOLDER_HASH__"; - impl Operation { fn execute(&self, working_set: WorkingSet) -> StateCheckpoint { match self { @@ -193,7 +191,7 @@ fn test_witness_roundtrip() { }; { - let storage = ZkStorage::::new(EMPTY_ROOT); + let storage = ZkStorage::::new(); let mut working_set = WorkingSet::with_witness(storage.clone(), witness); state_value.set(&11, &mut working_set); let _ = state_value.get(&mut working_set); diff --git a/rollup-interface/src/node/services/da.rs b/rollup-interface/src/node/services/da.rs index 267d1411f..1b530c182 100644 --- a/rollup-interface/src/node/services/da.rs +++ b/rollup-interface/src/node/services/da.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use serde::de::DeserializeOwned; use serde::Serialize; -use crate::da::{BlockHeaderTrait, DaSpec}; +use crate::da::{BlockHeaderTrait, DaSpec, DaVerifier}; use crate::zk::ValidityCondition; /// A DaService is the local side of an RPC connection talking to a node of the DA layer @@ -18,6 +18,9 @@ pub trait DaService: Send + Sync + 'static { /// A handle to the types used by the DA layer. type Spec: DaSpec; + /// The verifier for this DA layer. + type Verifier: DaVerifier; + /// A DA layer block, possibly excluding some irrelevant information. type FilteredBlock: SlotData< BlockHeader = ::BlockHeader, diff --git a/rollup-interface/src/state_machine/da.rs b/rollup-interface/src/state_machine/da.rs index 94b638fda..1b4a8cbbd 100644 --- a/rollup-interface/src/state_machine/da.rs +++ b/rollup-interface/src/state_machine/da.rs @@ -5,7 +5,6 @@ use std::cmp::min; use borsh::{BorshDeserialize, BorshSerialize}; use bytes::Buf; -use digest::Digest; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -61,7 +60,7 @@ pub trait DaVerifier { fn new(params: ::ChainParams) -> Self; /// Verify a claimed set of transactions against a block header. - fn verify_relevant_tx_list( + fn verify_relevant_tx_list( &self, block_header: &::BlockHeader, txs: &[::BlobTransaction], @@ -185,7 +184,7 @@ pub trait BlockHashTrait: } /// A block header, typically used in the context of an underlying DA blockchain. -pub trait BlockHeaderTrait: PartialEq + Debug + Clone { +pub trait BlockHeaderTrait: PartialEq + Debug + Clone + Serialize + DeserializeOwned { /// Each block header must have a unique canonical hash. type Hash: Clone; /// Each block header must contain the hash of the previous block. diff --git a/rollup-interface/src/state_machine/mocks/da.rs b/rollup-interface/src/state_machine/mocks/da.rs index a37041cf6..09569085f 100644 --- a/rollup-interface/src/state_machine/mocks/da.rs +++ b/rollup-interface/src/state_machine/mocks/da.rs @@ -6,7 +6,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use bytes::Bytes; use serde::{Deserialize, Serialize}; -use crate::da::{BlobReaderTrait, BlockHashTrait, BlockHeaderTrait, CountedBufReader, DaSpec}; +use crate::da::{ + BlobReaderTrait, BlockHashTrait, BlockHeaderTrait, CountedBufReader, DaSpec, DaVerifier, +}; use crate::mocks::MockValidityCond; use crate::services::da::{DaService, SlotData}; use crate::{BasicAddress, RollupAddress}; @@ -281,6 +283,7 @@ impl MockDaService { #[async_trait] impl DaService for MockDaService { + type Verifier = MockDaVerifier; type Spec = MockDaSpec; type FilteredBlock = MockBlock; type Error = anyhow::Error; @@ -317,7 +320,7 @@ impl DaService for MockDaService { ::InclusionMultiProof, ::CompletenessProof, ) { - todo!() + ([0u8; 32], ()) } async fn send_transaction(&self, blob: &[u8]) -> Result<(), Self::Error> { @@ -329,3 +332,27 @@ impl DaService for MockDaService { /// The configuration for mock da #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] pub struct MockDaConfig {} + +#[derive(Clone, Default)] +/// DaVerifier used in tests. +pub struct MockDaVerifier {} + +impl DaVerifier for MockDaVerifier { + type Spec = MockDaSpec; + + type Error = anyhow::Error; + + fn new(_params: ::ChainParams) -> Self { + Self {} + } + + fn verify_relevant_tx_list( + &self, + _block_header: &::BlockHeader, + _txs: &[::BlobTransaction], + _inclusion_proof: ::InclusionMultiProof, + _completeness_proof: ::CompletenessProof, + ) -> Result<::ValidityCondition, Self::Error> { + Ok(MockValidityCond { is_valid: true }) + } +} diff --git a/rollup-interface/src/state_machine/mocks/mod.rs b/rollup-interface/src/state_machine/mocks/mod.rs index 8f07dd832..232a5626f 100644 --- a/rollup-interface/src/state_machine/mocks/mod.rs +++ b/rollup-interface/src/state_machine/mocks/mod.rs @@ -6,7 +6,7 @@ mod validity_condition; mod zk_vm; pub use da::{ MockAddress, MockBlob, MockBlock, MockBlockHeader, MockDaConfig, MockDaService, MockDaSpec, - MockHash, + MockDaVerifier, MockHash, }; pub use validity_condition::{MockValidityCond, MockValidityCondChecker}; pub use zk_vm::{MockCodeCommitment, MockProof, MockZkvm}; diff --git a/rollup-interface/src/state_machine/stf.rs b/rollup-interface/src/state_machine/stf.rs index 57855467a..e3d6ad776 100644 --- a/rollup-interface/src/state_machine/stf.rs +++ b/rollup-interface/src/state_machine/stf.rs @@ -95,7 +95,7 @@ pub struct SlotResult { /// - blob: Non serialised batch or anything else that can be posted on DA layer, like attestation or proof. pub trait StateTransitionFunction { /// Root hash of state merkle tree - type StateRoot; + type StateRoot: Serialize + DeserializeOwned + Clone; /// The initial state of the rollup. type InitialState; @@ -107,7 +107,7 @@ pub trait StateTransitionFunction { /// Witness is a data that is produced during actual batch execution /// or validated together with proof during verification - type Witness: Default + Serialize; + type Witness: Default + Serialize + DeserializeOwned; /// The validity condition that must be verified outside of the Vm type Condition: ValidityCondition; diff --git a/rollup-interface/src/state_machine/zk/mod.rs b/rollup-interface/src/state_machine/zk/mod.rs index 41483dba2..fa9289210 100644 --- a/rollup-interface/src/state_machine/zk/mod.rs +++ b/rollup-interface/src/state_machine/zk/mod.rs @@ -18,8 +18,13 @@ use crate::RollupAddress; /// A trait implemented by the prover ("host") of a zkVM program. pub trait ZkvmHost: Zkvm { + /// The associated guest type + type Guest: ZkvmGuest; /// Give the guest a piece of advice non-deterministically - fn write_to_guest(&self, item: T); + fn add_hint(&self, item: T); + + /// Simulate running the guest using the provided hints + fn simulate_with_hints(&mut self) -> Self::Guest; } /// A Zk proof system capable of proving and verifying arbitrary Rust code