From 79a1bb5df6c5790a8aa542216eac9f77efa574fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20Yaz=C4=B1c=C4=B1?= <75089142+yaziciahmet@users.noreply.github.com> Date: Tue, 12 Nov 2024 01:15:44 +0700 Subject: [PATCH] Remove final state root from batch proof input (#1449) --- crates/batch-prover/src/proving.rs | 70 +++++++------------ crates/batch-prover/src/runner.rs | 2 +- crates/batch-prover/tests/prover_tests.rs | 8 +-- crates/citrea-stf/src/verifier.rs | 15 ++-- crates/evm/src/evm/conversions.rs | 25 ++++--- crates/evm/src/tests/utils.rs | 4 +- crates/fullnode/src/da_block_handler.rs | 5 +- crates/fullnode/src/runner.rs | 2 +- crates/fullnode/tests/hash_stf.rs | 11 ++- crates/light-client-prover/src/circuit.rs | 5 +- .../src/tests/test_utils.rs | 5 +- crates/sequencer/src/runner.rs | 7 +- .../full-node/sov-stf-runner/src/mock/mod.rs | 1 - .../module-system/sov-modules-api/src/lib.rs | 2 +- .../sov-modules-core/src/storage/mod.rs | 10 +-- .../sov-modules-stf-blueprint/src/lib.rs | 31 +++++--- .../sov-state/src/prover_storage.rs | 20 +++++- .../module-system/sov-state/src/zk_storage.rs | 20 +++++- .../src/state_machine/optimistic.rs | 4 +- .../rollup-interface/src/state_machine/stf.rs | 13 +++- .../src/state_machine/zk/mod.rs | 63 ++++++++++++++++- 21 files changed, 205 insertions(+), 118 deletions(-) diff --git a/crates/batch-prover/src/proving.rs b/crates/batch-prover/src/proving.rs index 09b0d4b9b..0cc0470af 100644 --- a/crates/batch-prover/src/proving.rs +++ b/crates/batch-prover/src/proving.rs @@ -12,12 +12,12 @@ use serde::de::DeserializeOwned; use serde::Serialize; use sov_db::ledger_db::BatchProverLedgerOps; use sov_db::schema::types::{BatchNumber, StoredBatchProof, StoredBatchProofOutput}; -use sov_modules_api::{BlobReaderTrait, SlotData, SpecId, Zkvm}; +use sov_modules_api::{BatchProofCircuitOutputV2, BlobReaderTrait, SlotData, SpecId, Zkvm}; use sov_rollup_interface::da::{BlockHeaderTrait, DaNamespace, DaSpec, SequencerCommitment}; use sov_rollup_interface::fork::fork_from_block_number; use sov_rollup_interface::rpc::SoftConfirmationStatus; use sov_rollup_interface::services::da::DaService; -use sov_rollup_interface::zk::{BatchProofCircuitInput, BatchProofCircuitOutput, Proof, ZkvmHost}; +use sov_rollup_interface::zk::{BatchProofCircuitInputV2, Proof, ZkvmHost}; use sov_stf_runner::ProverService; use tokio::sync::Mutex; use tracing::{debug, info}; @@ -38,7 +38,7 @@ pub(crate) async fn data_to_prove( ) -> Result< ( Vec, - Vec>, + Vec>, ), L1ProcessingError, > @@ -119,8 +119,6 @@ where for sequencer_commitments_range in ranges { let first_l2_height_of_l1 = sequencer_commitments[*sequencer_commitments_range.start()].l2_start_block_number; - let last_l2_height_of_l1 = - sequencer_commitments[*sequencer_commitments_range.end()].l2_end_block_number; let ( state_transition_witnesses, soft_confirmations, @@ -144,43 +142,25 @@ where L1ProcessingError::Other(format!("Error getting initial state root: {:?}", e)) })? .expect("There should be a state root"); - let initial_batch_hash = ledger - .get_soft_confirmation_by_number(&BatchNumber(first_l2_height_of_l1)) - .map_err(|e| { - L1ProcessingError::Other(format!("Error getting initial batch hash: {:?}", e)) - })? - .ok_or(L1ProcessingError::Other(format!( - "Could not find soft batch at height {}", - first_l2_height_of_l1 - )))? - .prev_hash; - - let final_state_root = ledger - .get_l2_state_root::(last_l2_height_of_l1) - .map_err(|e| { - L1ProcessingError::Other(format!("Error getting final state root: {:?}", e)) - })? - .expect("There should be a state root"); - let input: BatchProofCircuitInput = BatchProofCircuitInput { - initial_state_root, - final_state_root, - prev_soft_confirmation_hash: initial_batch_hash, - da_data: da_data.clone(), - da_block_header_of_commitments: da_block_header_of_commitments.clone(), - inclusion_proof: inclusion_proof.clone(), - completeness_proof: completeness_proof.clone(), - soft_confirmations, - state_transition_witnesses, - da_block_headers_of_soft_confirmations, - preproven_commitments: preproven_commitments.to_vec(), - sequencer_commitments_range: ( - *sequencer_commitments_range.start() as u32, - *sequencer_commitments_range.end() as u32, - ), - sequencer_public_key: sequencer_pub_key.clone(), - sequencer_da_public_key: sequencer_da_pub_key.clone(), - }; + let input: BatchProofCircuitInputV2 = + BatchProofCircuitInputV2 { + initial_state_root, + da_data: da_data.clone(), + da_block_header_of_commitments: da_block_header_of_commitments.clone(), + inclusion_proof: inclusion_proof.clone(), + completeness_proof: completeness_proof.clone(), + soft_confirmations, + state_transition_witnesses, + da_block_headers_of_soft_confirmations, + preproven_commitments: preproven_commitments.to_vec(), + sequencer_commitments_range: ( + *sequencer_commitments_range.start() as u32, + *sequencer_commitments_range.end() as u32, + ), + sequencer_public_key: sequencer_pub_key.clone(), + sequencer_da_public_key: sequencer_da_pub_key.clone(), + }; batch_proof_circuit_inputs.push(input); } @@ -194,7 +174,7 @@ pub(crate) async fn prove_l1( code_commitments_by_spec: HashMap, l1_block: Da::FilteredBlock, sequencer_commitments: Vec, - inputs: Vec>, + inputs: Vec>, ) -> anyhow::Result<()> where Da: DaService, @@ -247,7 +227,7 @@ where } pub(crate) fn state_transition_already_proven( - input: &BatchProofCircuitInput, + input: &BatchProofCircuitInputV2, proofs: &Vec, ) -> bool where @@ -263,7 +243,6 @@ where { for proof in proofs { if proof.proof_output.initial_state_root == input.initial_state_root.as_ref() - && proof.proof_output.final_state_root == input.final_state_root.as_ref() && proof.proof_output.sequencer_commitments_range == input.sequencer_commitments_range { return true; @@ -294,9 +273,10 @@ where // l1_height => (tx_id, proof, circuit_output) // save proof along with tx id to db, should be queryable by slot number or slot hash + // TODO: select output version based on spec let circuit_output = Vm::extract_output::< ::Spec, - BatchProofCircuitOutput<::Spec, StateRoot>, + BatchProofCircuitOutputV2<::Spec, StateRoot>, >(&proof) .expect("Proof should be deserializable"); diff --git a/crates/batch-prover/src/runner.rs b/crates/batch-prover/src/runner.rs index 8dc5fae6e..cbee15f9c 100644 --- a/crates/batch-prover/src/runner.rs +++ b/crates/batch-prover/src/runner.rs @@ -401,7 +401,7 @@ where let receipt = soft_confirmation_result.soft_confirmation_receipt; - let next_state_root = soft_confirmation_result.state_root; + let next_state_root = soft_confirmation_result.state_root_transition.final_root; // Check if post state root is the same as the one in the soft confirmation if next_state_root.as_ref().to_vec() != soft_confirmation.state_root { bail!("Post state root mismatch at height: {}", l2_height) diff --git a/crates/batch-prover/tests/prover_tests.rs b/crates/batch-prover/tests/prover_tests.rs index 7733a56af..5d9716184 100644 --- a/crates/batch-prover/tests/prover_tests.rs +++ b/crates/batch-prover/tests/prover_tests.rs @@ -7,7 +7,7 @@ use sov_db::rocks_db_config::RocksdbConfig; use sov_mock_da::{MockAddress, MockBlockHeader, MockDaService, MockDaSpec, MockHash}; use sov_mock_zkvm::MockZkvm; use sov_rollup_interface::da::Time; -use sov_rollup_interface::zk::BatchProofCircuitInput; +use sov_rollup_interface::zk::BatchProofCircuitInputV2; use sov_stf_runner::mock::MockStf; use sov_stf_runner::ProverService; @@ -99,11 +99,9 @@ fn make_new_prover(thread_pool_size: usize, da_service: Arc) -> T fn make_transition_data( header_hash: MockHash, -) -> BatchProofCircuitInput<'static, [u8; 0], Vec, MockDaSpec> { - BatchProofCircuitInput { +) -> BatchProofCircuitInputV2<'static, [u8; 0], Vec, MockDaSpec> { + BatchProofCircuitInputV2 { initial_state_root: [], - final_state_root: [], - prev_soft_confirmation_hash: [0; 32], inclusion_proof: [0; 32], completeness_proof: (), da_data: vec![], diff --git a/crates/citrea-stf/src/verifier.rs b/crates/citrea-stf/src/verifier.rs index 4eb4365f3..bdebfeb2d 100644 --- a/crates/citrea-stf/src/verifier.rs +++ b/crates/citrea-stf/src/verifier.rs @@ -3,7 +3,9 @@ use std::marker::PhantomData; use citrea_primitives::forks::FORKS; use sov_rollup_interface::da::{BlockHeaderTrait, DaNamespace, DaVerifier}; use sov_rollup_interface::stf::{ApplySequencerCommitmentsOutput, StateTransitionFunction}; -use sov_rollup_interface::zk::{BatchProofCircuitInput, BatchProofCircuitOutput, Zkvm, ZkvmGuest}; +use sov_rollup_interface::zk::{ + BatchProofCircuitInputV2, BatchProofCircuitOutputV2, Zkvm, ZkvmGuest, +}; /// Verifies a state transition pub struct StateTransitionVerifier @@ -39,7 +41,7 @@ where pre_state: Stf::PreState, ) -> Result<(), Da::Error> { println!("Running sequencer commitments in DA slot"); - let data: BatchProofCircuitInput = zkvm.read_from_host(); + let data: BatchProofCircuitInputV2 = zkvm.read_from_host(); if !data.da_block_header_of_commitments.verify_hash() { panic!("Invalid hash of DA block header of commitments"); @@ -77,7 +79,6 @@ where data.sequencer_public_key.as_ref(), data.sequencer_da_public_key.as_ref(), &data.initial_state_root, - data.prev_soft_confirmation_hash, pre_state, data.da_data, data.sequencer_commitments_range, @@ -89,16 +90,10 @@ where ); println!("out of apply_soft_confirmations_from_sequencer_commitments"); - assert_eq!( - final_state_root.as_ref(), - data.final_state_root.as_ref(), - "Invalid final state root" - ); - let out: BatchProofCircuitOutput = BatchProofCircuitOutput { + let out: BatchProofCircuitOutputV2 = BatchProofCircuitOutputV2 { initial_state_root: data.initial_state_root, final_state_root, - prev_soft_confirmation_hash: data.prev_soft_confirmation_hash, final_soft_confirmation_hash, state_diff, da_slot_hash: data.da_block_header_of_commitments.hash(), diff --git a/crates/evm/src/evm/conversions.rs b/crates/evm/src/evm/conversions.rs index cbdd76ecb..def01eda9 100644 --- a/crates/evm/src/evm/conversions.rs +++ b/crates/evm/src/evm/conversions.rs @@ -1,17 +1,11 @@ -use citrea_primitives::forks::FORKS; use reth_primitives::{ - Bytes as RethBytes, SealedHeader, TransactionSigned, TransactionSignedEcRecovered, - TransactionSignedNoHash, KECCAK_EMPTY, + Bytes as RethBytes, TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, + KECCAK_EMPTY, }; -use revm::primitives::{ - AccountInfo as ReVmAccountInfo, BlobExcessGasAndPrice, BlockEnv, SpecId, TransactTo, TxEnv, - U256, -}; -use sov_modules_api::fork::fork_from_block_number; +use revm::primitives::{AccountInfo as ReVmAccountInfo, SpecId, TransactTo, TxEnv, U256}; use super::primitive_types::{RlpEvmTransaction, TransactionSignedAndRecovered}; use super::AccountInfo; -use crate::citrea_spec_id_to_evm_spec_id; impl From for ReVmAccountInfo { fn from(info: AccountInfo) -> Self { @@ -132,8 +126,17 @@ impl From for TransactionSignedEcRecovered { } } -pub(crate) fn sealed_block_to_block_env(sealed_header: &SealedHeader) -> BlockEnv { - BlockEnv { +#[cfg(feature = "native")] +pub(crate) fn sealed_block_to_block_env( + sealed_header: &reth_primitives::SealedHeader, +) -> revm::primitives::BlockEnv { + use citrea_primitives::forks::FORKS; + use revm::primitives::BlobExcessGasAndPrice; + use sov_modules_api::fork::fork_from_block_number; + + use crate::citrea_spec_id_to_evm_spec_id; + + revm::primitives::BlockEnv { number: U256::from(sealed_header.number), coinbase: sealed_header.beneficiary, timestamp: U256::from(sealed_header.timestamp), diff --git a/crates/evm/src/tests/utils.rs b/crates/evm/src/tests/utils.rs index a97745cae..033ccb54f 100644 --- a/crates/evm/src/tests/utils.rs +++ b/crates/evm/src/tests/utils.rs @@ -100,7 +100,7 @@ pub(crate) fn commit( let (cache_log, mut witness) = checkpoint.freeze(); - let (root, authenticated_node_batch, _) = storage + let (state_root_transition, authenticated_node_batch, _) = storage .compute_state_update(cache_log, &mut witness) .expect("jellyfish merkle tree update must succeed"); @@ -111,7 +111,7 @@ pub(crate) fn commit( storage.commit(&authenticated_node_batch, &accessory_log, &offchain_log); - root.0 + state_root_transition.final_root.0 } /// Loads the genesis configuration from the given path and pushes the accounts to the evm config diff --git a/crates/fullnode/src/da_block_handler.rs b/crates/fullnode/src/da_block_handler.rs index fbd8bd973..26514ab0e 100644 --- a/crates/fullnode/src/da_block_handler.rs +++ b/crates/fullnode/src/da_block_handler.rs @@ -24,7 +24,7 @@ use sov_rollup_interface::fork::fork_from_block_number; use sov_rollup_interface::rpc::SoftConfirmationStatus; use sov_rollup_interface::services::da::{DaService, SlotData}; use sov_rollup_interface::spec::SpecId; -use sov_rollup_interface::zk::{BatchProofCircuitOutput, Proof, ZkvmHost}; +use sov_rollup_interface::zk::{BatchProofCircuitOutputV2, Proof, ZkvmHost}; use tokio::select; use tokio::sync::{mpsc, Mutex}; use tokio::time::{sleep, Duration}; @@ -298,9 +298,10 @@ where ); tracing::debug!("ZK proof: {:?}", proof); + // TODO: select output version based on spec let batch_proof_output = Vm::extract_output::< ::Spec, - BatchProofCircuitOutput<::Spec, StateRoot>, + BatchProofCircuitOutputV2<::Spec, StateRoot>, >(&proof) .expect("Proof should be deserializable"); if batch_proof_output.sequencer_da_public_key != self.sequencer_da_pub_key diff --git a/crates/fullnode/src/runner.rs b/crates/fullnode/src/runner.rs index 5eb3cdb24..8e9b0ffec 100644 --- a/crates/fullnode/src/runner.rs +++ b/crates/fullnode/src/runner.rs @@ -260,7 +260,7 @@ where let receipt = soft_confirmation_result.soft_confirmation_receipt; - let next_state_root = soft_confirmation_result.state_root; + let next_state_root = soft_confirmation_result.state_root_transition.final_root; // Check if post state root is the same as the one in the soft confirmation if next_state_root.as_ref().to_vec() != soft_confirmation.state_root { bail!("Post state root mismatch at height: {}", l2_height) diff --git a/crates/fullnode/tests/hash_stf.rs b/crates/fullnode/tests/hash_stf.rs index ebf815e57..75fbc0c3b 100644 --- a/crates/fullnode/tests/hash_stf.rs +++ b/crates/fullnode/tests/hash_stf.rs @@ -43,7 +43,7 @@ impl HashStf { ordered_writes: vec![(hash_key.to_cache_key(), Some(hash_value.into_cache_value()))], }; - let (jmt_root_hash, state_update, _) = storage + let (state_root_transition, state_update, _) = storage .compute_state_update(ordered_reads_writes, witness) .unwrap(); @@ -55,7 +55,13 @@ impl HashStf { let mut root_hash = [0u8; 32]; - for (i, &byte) in jmt_root_hash.as_ref().iter().enumerate().take(32) { + for (i, &byte) in state_root_transition + .final_root + .as_ref() + .iter() + .enumerate() + .take(32) + { root_hash[i] = byte; } @@ -227,7 +233,6 @@ impl StateTransitionFunction for HashStf { _sequencer_public_key: &[u8], _sequencer_da_public_key: &[u8], _initial_state_root: &Self::StateRoot, - _initial_batch_hash: [u8; 32], _pre_state: Self::PreState, _da_data: Vec<::BlobTransaction>, _sequencer_commitments_range: (u32, u32), diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 9c63aa80d..230d21e05 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -2,7 +2,7 @@ use borsh::BorshDeserialize; use sov_modules_api::BlobReaderTrait; use sov_rollup_interface::da::{DaDataLightClient, DaNamespace, DaVerifier}; use sov_rollup_interface::zk::{ - BatchProofCircuitOutput, BatchProofInfo, LightClientCircuitInput, LightClientCircuitOutput, + BatchProofCircuitOutputV2, BatchProofInfo, LightClientCircuitInput, LightClientCircuitOutput, ZkvmGuest, }; @@ -101,7 +101,8 @@ pub fn run_circuit( DaDataLightClient::Complete(proof) => { let journal = G::extract_raw_output(&proof).expect("DaData proofs must be valid"); - let batch_proof_output: BatchProofCircuitOutput = + // TODO: select output version based on the spec + let batch_proof_output: BatchProofCircuitOutputV2 = match G::verify_and_extract_output( &journal, &batch_proof_method_id.into(), diff --git a/crates/light-client-prover/src/tests/test_utils.rs b/crates/light-client-prover/src/tests/test_utils.rs index 64a5767f9..9e92286ff 100644 --- a/crates/light-client-prover/src/tests/test_utils.rs +++ b/crates/light-client-prover/src/tests/test_utils.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use sov_mock_da::{MockAddress, MockBlob, MockDaSpec, MockHash}; use sov_mock_zkvm::{MockCodeCommitment, MockJournal, MockProof}; use sov_rollup_interface::da::{BlobReaderTrait, DaDataLightClient}; -use sov_rollup_interface::zk::{BatchProofCircuitOutput, LightClientCircuitOutput}; +use sov_rollup_interface::zk::{BatchProofCircuitOutputV2, LightClientCircuitOutput}; pub(crate) fn create_mock_blob( initial_state_root: [u8; 32], @@ -13,10 +13,9 @@ pub(crate) fn create_mock_blob( ) -> MockBlob { let batch_proof_method_id = MockCodeCommitment([2u8; 32]); - let bp = BatchProofCircuitOutput:: { + let bp = BatchProofCircuitOutputV2:: { initial_state_root, final_state_root, - prev_soft_confirmation_hash: [3; 32], final_soft_confirmation_hash: [4; 32], state_diff: BTreeMap::new(), da_slot_hash: MockHash([5; 32]), diff --git a/crates/sequencer/src/runner.rs b/crates/sequencer/src/runner.rs index e4b6bbcda..e78655261 100644 --- a/crates/sequencer/src/runner.rs +++ b/crates/sequencer/src/runner.rs @@ -492,19 +492,20 @@ where prestate, &mut signed_soft_confirmation, ); + let state_root_transition = soft_confirmation_result.state_root_transition; let receipt = soft_confirmation_result.soft_confirmation_receipt; - if soft_confirmation_result.state_root.as_ref() == self.state_root.as_ref() { + if state_root_transition.final_root.as_ref() == self.state_root.as_ref() { bail!("Max L2 blocks per L1 is reached for the current L1 block. State root is the same as before, skipping"); } trace!( "State root after applying slot: {:?}", - soft_confirmation_result.state_root + state_root_transition.final_root, ); - let next_state_root = soft_confirmation_result.state_root; + let next_state_root = state_root_transition.final_root; self.storage_manager .save_change_set_l2(l2_height, soft_confirmation_result.change_set)?; diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/mock/mod.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/mock/mod.rs index b38a5ea61..0f11ba9ec 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/mock/mod.rs +++ b/crates/sovereign-sdk/full-node/sov-stf-runner/src/mock/mod.rs @@ -91,7 +91,6 @@ impl StateTransitionFunction for MockStf { _sequencer_public_key: &[u8], _sequencer_da_public_key: &[u8], _initial_state_root: &Self::StateRoot, - _initial_batch_hash: [u8; 32], _pre_state: Self::PreState, _da_data: Vec<::BlobTransaction>, _sequencer_commitments_range: (u32, u32), diff --git a/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs b/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs index 91dfa3d46..e6b555b4a 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-api/src/lib.rs @@ -196,7 +196,7 @@ pub use sov_rollup_interface::soft_confirmation::{ SignedSoftConfirmation, UnsignedSoftConfirmation, }; pub use sov_rollup_interface::stf::{Event, StateDiff}; -pub use sov_rollup_interface::zk::{BatchProofCircuitOutput, Zkvm}; +pub use sov_rollup_interface::zk::{BatchProofCircuitOutputV2, Zkvm}; pub use sov_rollup_interface::{digest, BasicAddress, RollupAddress}; pub mod prelude { diff --git a/crates/sovereign-sdk/module-system/sov-modules-core/src/storage/mod.rs b/crates/sovereign-sdk/module-system/sov-modules-core/src/storage/mod.rs index 0c52c7bee..97d94ba4b 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-core/src/storage/mod.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-core/src/storage/mod.rs @@ -6,7 +6,7 @@ use core::fmt; use borsh::{BorshDeserialize, BorshSerialize}; use serde::de::DeserializeOwned; use serde::Serialize; -use sov_rollup_interface::stf::StateDiff; +use sov_rollup_interface::stf::{StateDiff, StateRootTransition}; use sov_rollup_interface::RefCount; use crate::common::{AlignedVec, Prefix, Version, Witness}; @@ -239,13 +239,14 @@ pub trait Storage: Clone { } /// Calculates new state root but does not commit any changes to the database. + #[allow(clippy::type_complexity)] fn compute_state_update( &self, state_accesses: OrderedReadsAndWrites, witness: &mut Self::Witness, ) -> Result< ( - Self::Root, + StateRootTransition, Self::StateUpdate, StateDiff, // computed in Zk mode ), @@ -268,10 +269,11 @@ pub trait Storage: Clone { accessory_update: &OrderedReadsAndWrites, offchain_update: &OrderedReadsAndWrites, ) -> Result { - let (root_hash, node_batch, _) = self.compute_state_update(state_accesses, witness)?; + let (state_root_transition, node_batch, _) = + self.compute_state_update(state_accesses, witness)?; self.commit(&node_batch, accessory_update, offchain_update); - Ok(root_hash) + Ok(state_root_transition.final_root) } /// Validate all of the storage accesses in a particular cache log, diff --git a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs index c24f139c0..485e36dca 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-stf-blueprint/src/lib.rs @@ -345,21 +345,23 @@ where ); } - let (state_root, witness, offchain_witness, storage, state_diff) = { + let (state_root_transition, witness, offchain_witness, storage, state_diff) = { let working_set = checkpoint.to_revertable(); // Save checkpoint let mut checkpoint = working_set.checkpoint(); let (cache_log, mut witness) = checkpoint.freeze(); - let (root_hash, state_update, state_diff) = pre_state + let (state_root_transition, state_update, state_diff) = pre_state .compute_state_update(cache_log, &mut witness) .expect("jellyfish merkle tree update must succeed"); let mut working_set = checkpoint.to_revertable(); - self.runtime - .finalize_hook(&root_hash, &mut working_set.accessory_state()); + self.runtime.finalize_hook( + &state_root_transition.final_root, + &mut working_set.accessory_state(), + ); let mut checkpoint = working_set.checkpoint(); let accessory_log = checkpoint.freeze_non_provable(); @@ -367,11 +369,17 @@ where pre_state.commit(&state_update, &accessory_log, &offchain_log); - (root_hash, witness, offchain_witness, pre_state, state_diff) + ( + state_root_transition, + witness, + offchain_witness, + pre_state, + state_diff, + ) }; SoftConfirmationResult { - state_root, + state_root_transition, change_set: storage, witness, offchain_witness, @@ -414,9 +422,10 @@ where let mut checkpoint = working_set.checkpoint(); let (log, mut witness) = checkpoint.freeze(); - let (genesis_hash, state_update, _) = pre_state + let (state_root_transition, state_update, _) = pre_state .compute_state_update(log, &mut witness) .expect("Storage update must succeed"); + let genesis_hash = state_root_transition.final_root; let mut working_set = checkpoint.to_revertable(); @@ -538,7 +547,6 @@ where sequencer_public_key: &[u8], sequencer_da_public_key: &[u8], initial_state_root: &Self::StateRoot, - initial_batch_hash: [u8; 32], pre_state: Self::PreState, da_data: Vec<::BlobTransaction>, sequencer_commitments_range: (u32, u32), @@ -614,7 +622,7 @@ where // Then verify these soft confirmations. let mut current_state_root = initial_state_root.clone(); - let mut previous_batch_hash = initial_batch_hash; + let mut previous_batch_hash = soft_confirmations[0][0].prev_hash(); let mut last_commitment_end_height: Option = None; let mut fork_manager = ForkManager::new(forks, sequencer_commitments_range.0 as u64); @@ -651,6 +659,7 @@ where previous_batch_hash, "Soft confirmation previous hash must match the hash of the block before" ); + previous_batch_hash = soft_confirmations[index_soft_confirmation].hash(); assert_eq!( soft_confirmations[index_soft_confirmation].da_slot_hash(), @@ -664,7 +673,6 @@ where "Soft confirmation DA slot height must match DA block header height" ); - previous_batch_hash = soft_confirmations[index_soft_confirmation].hash(); index_soft_confirmation += 1; while index_soft_confirmation < soft_confirmations.len() { @@ -801,7 +809,8 @@ where // for now we don't allow "broken" seq. com.s .expect("Soft confirmation must succeed"); - current_state_root = result.state_root; + assert_eq!(current_state_root, result.state_root_transition.init_root); + current_state_root = result.state_root_transition.final_root; state_diff.extend(result.state_diff); // Notify fork manager about the block so that the next spec / fork diff --git a/crates/sovereign-sdk/module-system/sov-state/src/prover_storage.rs b/crates/sovereign-sdk/module-system/sov-state/src/prover_storage.rs index 779bb2a1e..19434db71 100644 --- a/crates/sovereign-sdk/module-system/sov-state/src/prover_storage.rs +++ b/crates/sovereign-sdk/module-system/sov-state/src/prover_storage.rs @@ -9,7 +9,7 @@ use sov_modules_core::{ CacheKey, NativeStorage, OrderedReadsAndWrites, Storage, StorageKey, StorageProof, StorageValue, Witness, }; -use sov_rollup_interface::stf::StateDiff; +use sov_rollup_interface::stf::{StateDiff, StateRootTransition}; use crate::config::Config; use crate::{DefaultHasher, DefaultWitness}; @@ -119,7 +119,14 @@ where &self, state_accesses: OrderedReadsAndWrites, witness: &mut Self::Witness, - ) -> Result<(Self::Root, Self::StateUpdate, StateDiff), anyhow::Error> { + ) -> Result< + ( + StateRootTransition, + Self::StateUpdate, + StateDiff, + ), + anyhow::Error, + > { let latest_version = self.db.get_next_version() - 1; let jmt = JellyfishMerkleTree::<_, DefaultHasher>::new(&self.db); @@ -191,7 +198,14 @@ where // We need the state diff to be calculated only inside zk context. // The diff then can be used by special nodes to construct the state of the rollup by verifying the zk proof. // And constructing the tree from the diff. - Ok((new_root, state_update, diff)) + Ok(( + StateRootTransition { + init_root: prev_root, + final_root: new_root, + }, + state_update, + diff, + )) } fn commit( diff --git a/crates/sovereign-sdk/module-system/sov-state/src/zk_storage.rs b/crates/sovereign-sdk/module-system/sov-state/src/zk_storage.rs index 628adeb97..e327fb41e 100644 --- a/crates/sovereign-sdk/module-system/sov-state/src/zk_storage.rs +++ b/crates/sovereign-sdk/module-system/sov-state/src/zk_storage.rs @@ -6,7 +6,7 @@ use sha2::Digest; use sov_modules_core::{ OrderedReadsAndWrites, Storage, StorageKey, StorageProof, StorageValue, Witness, }; -use sov_rollup_interface::stf::StateDiff; +use sov_rollup_interface::stf::{StateDiff, StateRootTransition}; /// A [`Storage`] implementation designed to be used inside the zkVM. #[derive(Default)] @@ -76,7 +76,14 @@ where &self, state_accesses: OrderedReadsAndWrites, witness: &mut Self::Witness, - ) -> Result<(Self::Root, Self::StateUpdate, StateDiff), anyhow::Error> { + ) -> Result< + ( + StateRootTransition, + Self::StateUpdate, + StateDiff, + ), + anyhow::Error, + > { let prev_state_root = witness.get_hint(); // For each value that's been read from the tree, verify the provided smt proof @@ -123,7 +130,14 @@ where ) .expect("Updates must be valid"); - Ok((jmt::RootHash(new_root), (), diff)) + Ok(( + StateRootTransition { + init_root: jmt::RootHash(prev_state_root), + final_root: jmt::RootHash(new_root), + }, + (), + diff, + )) } fn commit( diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/optimistic.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/optimistic.rs index ece7f648a..7f762d6bd 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/optimistic.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/optimistic.rs @@ -3,7 +3,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::da::DaSpec; -use crate::zk::BatchProofCircuitOutput; +use crate::zk::BatchProofCircuitOutputV2; /// A proof that the attester was bonded at the transition num `transition_num`. /// For rollups using the `jmt`, this will be a `jmt::SparseMerkleProof` @@ -39,7 +39,7 @@ pub struct ChallengeContents { /// The rollup address of the originator of this challenge pub challenger_address: Address, /// The state transition that was proven - pub state_transition: BatchProofCircuitOutput, + pub state_transition: BatchProofCircuitOutputV2, } #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, Serialize, Deserialize)] diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs index cc04532ad..a5bfe90f5 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/stf.rs @@ -143,6 +143,14 @@ pub struct SlotResult { pub state_diff: StateDiff, } +/// Helper struct which contains initial and final state roots. +pub struct StateRootTransition { + /// Initial state root + pub init_root: Root, + /// Final state root + pub final_root: Root, +} + /// Result of applying a soft confirmation to current state /// Where: /// - S - generic for state root @@ -151,8 +159,8 @@ pub struct SlotResult { /// - W - generic for witness /// - Da - generic for DA layer pub struct SoftConfirmationResult { - /// Finals state root after all soft confirmation txs are applied - pub state_root: S, + /// Contains state root before and after applying txs + pub state_root_transition: StateRootTransition, /// Container for all state alterations that happened during soft confirmation execution pub change_set: Cs, /// Witness after applying the whole block @@ -298,7 +306,6 @@ pub trait StateTransitionFunction { sequencer_public_key: &[u8], sequencer_da_public_key: &[u8], initial_state_root: &Self::StateRoot, - initial_batch_hash: [u8; 32], pre_state: Self::PreState, da_data: Vec<::BlobTransaction>, sequencer_commitments_range: (u32, u32), diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs index 0c45c2d8f..67e38f6b3 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs @@ -109,7 +109,33 @@ pub type CumulativeStateDiff = BTreeMap, Option>>; /// The period of time covered by a state transition proof is a range of L2 blocks whose sequencer /// commitments are included in the DA slot with hash `da_slot_hash`. The range is inclusive. #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, PartialEq, Eq)] -pub struct BatchProofCircuitOutput { +pub struct BatchProofCircuitOutputV2 { + /// The state of the rollup before the transition + pub initial_state_root: Root, + /// The state of the rollup after the transition + pub final_state_root: Root, + /// The hash of the last soft confirmation in the state transition + pub final_soft_confirmation_hash: [u8; 32], + /// State diff of L2 blocks in the processed sequencer commitments. + pub state_diff: CumulativeStateDiff, + /// The DA slot hash that the sequencer commitments causing this state transition were found in. + pub da_slot_hash: Da::SlotHash, + /// The range of sequencer commitments in the DA slot that were processed. + /// The range is inclusive. + pub sequencer_commitments_range: (u32, u32), + /// Sequencer public key. + pub sequencer_public_key: Vec, + /// Sequencer DA public key. + pub sequencer_da_public_key: Vec, + /// The last processed l2 height in the processed sequencer commitments. + pub last_l2_height: u64, + /// Pre-proven commitments L2 ranges which also exist in the current L1 `da_data`. + pub preproven_commitments: Vec, +} + +/// Version 1 of `BatchProofCircuitOutput` +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct BatchProofCircuitOutputV1 { /// The state of the rollup before the transition pub initial_state_root: Root, /// The state of the rollup after the transition @@ -147,7 +173,7 @@ pub trait Matches { // StateTransitionFunction, DA, and Zkvm traits. #[serde(bound = "StateRoot: Serialize + DeserializeOwned, Witness: Serialize + DeserializeOwned")] /// Data required to verify a state transition. -pub struct BatchProofCircuitInput<'txs, StateRoot, Witness, Da: DaSpec> { +pub struct BatchProofCircuitInputV1<'txs, StateRoot, Witness, Da: DaSpec> { /// The state root before the state transition pub initial_state_root: StateRoot, /// The state root after the state transition @@ -179,6 +205,39 @@ pub struct BatchProofCircuitInput<'txs, StateRoot, Witness, Da: DaSpec> { pub sequencer_commitments_range: (u32, u32), } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize)] +// Prevent serde from generating spurious trait bounds. The correct serde bounds are already enforced by the +// StateTransitionFunction, DA, and Zkvm traits. +#[serde(bound = "StateRoot: Serialize + DeserializeOwned, Witness: Serialize + DeserializeOwned")] +/// Data required to verify a state transition. +pub struct BatchProofCircuitInputV2<'txs, StateRoot, Witness, Da: DaSpec> { + /// The state root before the state transition + pub initial_state_root: StateRoot, + /// The `crate::da::DaData` that are being processed as blobs. Everything that's not `crate::da::DaData::SequencerCommitment` will be ignored. + pub da_data: Vec, + /// DA block header that the sequencer commitments were found in. + pub da_block_header_of_commitments: Da::BlockHeader, + /// The inclusion proof for all DA data. + pub inclusion_proof: Da::InclusionMultiProof, + /// The completeness proof for all DA data. + pub completeness_proof: Da::CompletenessProof, + /// Pre-proven commitments L2 ranges which also exist in the current L1 `da_data`. + pub preproven_commitments: Vec, + /// The soft confirmations that are inside the sequencer commitments. + pub soft_confirmations: VecDeque>>, + /// Corresponding witness for the soft confirmations. + pub state_transition_witnesses: VecDeque>, + /// DA block headers the soft confirmations was constructed on. + pub da_block_headers_of_soft_confirmations: VecDeque>, + /// Sequencer soft confirmation public key. + pub sequencer_public_key: Vec, + /// Sequencer DA public_key: Vec, + pub sequencer_da_public_key: Vec, + /// The range of sequencer commitments that are being processed. + /// The range is inclusive. + pub sequencer_commitments_range: (u32, u32), +} + /// The batch proof that was not verified in the light client circuit because it was missing another proof for state root chaining /// This struct is passed as an output to the light client circuit /// After that the new circuit will read that info to update the state root if possible