diff --git a/Cargo.lock b/Cargo.lock index e37c1ebd6..638328d70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1864,13 +1864,19 @@ dependencies = [ "sequencer-client", "serde", "serde_json", + "sha2 0.10.8", "shared-backup-db", "sov-db", + "sov-mock-da", + "sov-mock-zkvm", "sov-modules-api", "sov-modules-rollup-blueprint", "sov-modules-stf-blueprint", + "sov-prover-storage-manager", "sov-rollup-interface", + "sov-state", "sov-stf-runner", + "tempfile", "tokio", "tracing", ] @@ -1892,22 +1898,31 @@ name = "citrea-prover" version = "0.4.0-rc.3" dependencies = [ "anyhow", + "async-trait", "backoff", "borsh", "citrea-primitives", + "citrea-stf", "futures", "hex", "jsonrpsee", + "num_cpus", "rand 0.8.5", + "rayon", "rs_merkle", "sequencer-client", + "serde", + "sha2 0.10.8", "shared-backup-db", "sov-db", + "sov-mock-da", + "sov-mock-zkvm", "sov-modules-api", "sov-modules-rollup-blueprint", "sov-modules-stf-blueprint", "sov-rollup-interface", "sov-stf-runner", + "tempfile", "tokio", "tracing", ] @@ -8676,7 +8691,6 @@ dependencies = [ "borsh", "hex", "jsonrpsee", - "sequencer-client", "serde", "serde_json", "sov-cli", @@ -8907,7 +8921,6 @@ version = "0.4.0-rc.3" dependencies = [ "anyhow", "async-trait", - "backoff", "borsh", "futures", "hex", @@ -8915,28 +8928,21 @@ dependencies = [ "jsonrpsee", "num_cpus", "rand 0.8.5", - "rayon", - "rs_merkle", - "sequencer-client", "serde", "serde_json", "sha2 0.10.8", "shared-backup-db", "sov-db", "sov-mock-da", - "sov-mock-zkvm", "sov-modules-api", - "sov-modules-stf-blueprint", "sov-prover-storage-manager", "sov-rollup-interface", "sov-state", - "sov-stf-runner", "tempfile", "thiserror", "tokio", "toml", "tower", - "tower-http", "tracing", ] diff --git a/bin/citrea/provers/risc0/guest-bitcoin/Cargo.lock b/bin/citrea/provers/risc0/guest-bitcoin/Cargo.lock index a1836e856..5a8c88155 100644 --- a/bin/citrea/provers/risc0/guest-bitcoin/Cargo.lock +++ b/bin/citrea/provers/risc0/guest-bitcoin/Cargo.lock @@ -3180,11 +3180,9 @@ dependencies = [ "borsh", "hex", "num_cpus", - "rs_merkle", "serde", "serde_json", "sov-modules-api", - "sov-modules-stf-blueprint", "sov-rollup-interface", ] diff --git a/bin/citrea/provers/risc0/guest-mock/Cargo.lock b/bin/citrea/provers/risc0/guest-mock/Cargo.lock index 8d3440a90..42f4783ec 100644 --- a/bin/citrea/provers/risc0/guest-mock/Cargo.lock +++ b/bin/citrea/provers/risc0/guest-mock/Cargo.lock @@ -2965,11 +2965,9 @@ dependencies = [ "borsh", "hex", "num_cpus", - "rs_merkle", "serde", "serde_json", "sov-modules-api", - "sov-modules-stf-blueprint", "sov-rollup-interface", ] diff --git a/bin/citrea/src/rollup/bitcoin.rs b/bin/citrea/src/rollup/bitcoin.rs index fbbc05da5..3326add5d 100644 --- a/bin/citrea/src/rollup/bitcoin.rs +++ b/bin/citrea/src/rollup/bitcoin.rs @@ -3,6 +3,7 @@ use bitcoin_da::service::{BitcoinService, DaServiceConfig}; use bitcoin_da::spec::{BitcoinSpec, RollupParams}; use bitcoin_da::verifier::BitcoinVerifier; use citrea_primitives::{DA_TX_ID_LEADING_ZEROS, ROLLUP_NAME}; +use citrea_prover::prover_service::ParallelProverService; use citrea_risc0_bonsai_adapter::host::Risc0BonsaiHost; use citrea_risc0_bonsai_adapter::Digest; use citrea_stf::genesis_config::StorageConfig; @@ -16,7 +17,7 @@ use sov_prover_storage_manager::ProverStorageManager; use sov_rollup_interface::da::DaVerifier; use sov_rollup_interface::zk::{Zkvm, ZkvmHost}; use sov_state::{DefaultStorageSpec, Storage, ZkStorage}; -use sov_stf_runner::{FullNodeConfig, ParallelProverService, ProverConfig}; +use sov_stf_runner::{FullNodeConfig, ProverConfig}; use tracing::instrument; use crate::CitreaRollupBlueprint; diff --git a/bin/citrea/src/rollup/mock.rs b/bin/citrea/src/rollup/mock.rs index 8cfdc2d8f..b0991f0ea 100644 --- a/bin/citrea/src/rollup/mock.rs +++ b/bin/citrea/src/rollup/mock.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use citrea_prover::prover_service::ParallelProverService; use citrea_risc0_bonsai_adapter::host::Risc0BonsaiHost; use citrea_risc0_bonsai_adapter::Digest; use citrea_stf::genesis_config::StorageConfig; @@ -12,7 +13,7 @@ use sov_modules_stf_blueprint::StfBlueprint; use sov_prover_storage_manager::ProverStorageManager; use sov_rollup_interface::zk::{Zkvm, ZkvmHost}; use sov_state::{DefaultStorageSpec, Storage, ZkStorage}; -use sov_stf_runner::{FullNodeConfig, ParallelProverService, ProverConfig}; +use sov_stf_runner::{FullNodeConfig, ProverConfig}; use crate::CitreaRollupBlueprint; diff --git a/crates/citrea-stf/src/lib.rs b/crates/citrea-stf/src/lib.rs index cda62a303..bac321973 100644 --- a/crates/citrea-stf/src/lib.rs +++ b/crates/citrea-stf/src/lib.rs @@ -5,7 +5,8 @@ pub mod genesis_config; mod hooks_impl; pub mod runtime; -mod verifier; +/// Implements the `StateTransitionVerifier` type for checking the validity of a state transition +pub mod verifier; use sov_modules_stf_blueprint::StfBlueprint; use sov_rollup_interface::da::DaVerifier; use verifier::StateTransitionVerifier; diff --git a/crates/fullnode/Cargo.toml b/crates/fullnode/Cargo.toml index 49bb4e984..a0a4ed808 100644 --- a/crates/fullnode/Cargo.toml +++ b/crates/fullnode/Cargo.toml @@ -35,3 +35,12 @@ serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } + +[dev-dependencies] +sha2 = { workspace = true } +tempfile = { workspace = true } + +sov-mock-da = { path = "../sovereign-sdk/adapters/mock-da", features = ["native"] } +sov-mock-zkvm = { path = "../sovereign-sdk/adapters/mock-zkvm" } +sov-prover-storage-manager = { path = "../sovereign-sdk/full-node/sov-prover-storage-manager", features = ["test-utils"] } +sov-state = { path = "../sovereign-sdk/module-system/sov-state", features = ["native"] } diff --git a/crates/fullnode/src/runner.rs b/crates/fullnode/src/runner.rs index dd394ba46..aca4208c8 100644 --- a/crates/fullnode/src/runner.rs +++ b/crates/fullnode/src/runner.rs @@ -625,6 +625,11 @@ where }); (sequencer_commitments, zk_proofs) } + + /// Allows to read current state root + pub fn get_state_root(&self) -> &Stf::StateRoot { + &self.state_root + } } async fn l1_sync( diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/hash_stf.rs b/crates/fullnode/tests/hash_stf.rs similarity index 100% rename from crates/sovereign-sdk/full-node/sov-stf-runner/tests/hash_stf.rs rename to crates/fullnode/tests/hash_stf.rs diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_initialization_tests.rs b/crates/fullnode/tests/runner_initialization_tests.rs similarity index 88% rename from crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_initialization_tests.rs rename to crates/fullnode/tests/runner_initialization_tests.rs index ea0c4df3e..46181c348 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_initialization_tests.rs +++ b/crates/fullnode/tests/runner_initialization_tests.rs @@ -1,11 +1,11 @@ +use citrea_fullnode::CitreaFullnode; use sov_db::ledger_db::LedgerDB; use sov_mock_da::{MockAddress, MockDaConfig, MockDaService, MockDaSpec, MockValidityCond}; use sov_mock_zkvm::{MockCodeCommitment, MockZkvm}; use sov_prover_storage_manager::ProverStorageManager; -use sov_state::{ArrayWitness, DefaultStorageSpec}; +use sov_state::DefaultStorageSpec; use sov_stf_runner::{ - FullNodeConfig, InitVariant, ParallelProverService, RollupPublicKeys, RpcConfig, RunnerConfig, - StateTransitionRunner, StorageConfig, + FullNodeConfig, InitVariant, RollupPublicKeys, RpcConfig, RunnerConfig, StorageConfig, }; mod hash_stf; @@ -39,22 +39,14 @@ async fn init_and_restart() { assert_eq!(state_root_after_genesis, state_root_2); } -type MockProverService = ParallelProverService< - [u8; 32], - ArrayWitness, - MockDaService, - MockZkvm, - HashStf, ->; fn initialize_runner( storage_path: &std::path::Path, init_variant: MockInitVariant, -) -> StateTransitionRunner< +) -> CitreaFullnode< HashStf, StorageManager, MockDaService, MockZkvm, - MockProverService, sov_modules_api::default_context::DefaultContext, > { let da_storage_path = storage_path.join("da").to_path_buf(); @@ -111,7 +103,7 @@ fn initialize_runner( // let vm = MockZkvm::new(MockValidityCond::default()); // let verifier = MockDaVerifier::default(); - StateTransitionRunner::new( + CitreaFullnode::new( rollup_config.runner.unwrap(), rollup_config.public_keys, rollup_config.rpc, @@ -120,9 +112,8 @@ fn initialize_runner( stf, storage_manager, init_variant, - None, - None, MockCodeCommitment([1u8; 32]), + 10, ) .unwrap() } diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_reorg_tests.rs b/crates/fullnode/tests/runner_reorg_tests.rs similarity index 83% rename from crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_reorg_tests.rs rename to crates/fullnode/tests/runner_reorg_tests.rs index f49a14eb2..d89afb303 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/runner_reorg_tests.rs +++ b/crates/fullnode/tests/runner_reorg_tests.rs @@ -1,12 +1,12 @@ +use citrea_fullnode::CitreaFullnode; use sov_mock_da::{ MockAddress, MockBlob, MockBlock, MockBlockHeader, MockDaConfig, MockDaService, MockDaSpec, - MockDaVerifier, MockValidityCond, PlannedFork, + MockValidityCond, PlannedFork, }; use sov_mock_zkvm::MockZkvm; use sov_modules_api::default_context::DefaultContext; use sov_stf_runner::{ - FullNodeConfig, InitVariant, ParallelProverService, ProverGuestRunConfig, RollupPublicKeys, - RpcConfig, RunnerConfig, StateTransitionRunner, StorageConfig, + FullNodeConfig, InitVariant, RollupPublicKeys, RpcConfig, RunnerConfig, StorageConfig, }; mod hash_stf; @@ -154,41 +154,24 @@ async fn runner_execution( let storage_config = sov_state::config::Config { path: rollup_storage_path, }; - let mut storage_manager = ProverStorageManager::new(storage_config).unwrap(); - - let vm = MockZkvm::new(MockValidityCond::default()); - let verifier = MockDaVerifier::default(); - let prover_config = ProverGuestRunConfig::Skip; - - let prover_service = ParallelProverService::new( - vm, - stf.clone(), - verifier, - prover_config, - // Should be ZkStorage, but we don't need it for this test - storage_manager.create_finalized_storage().unwrap(), - 1, + let storage_manager = ProverStorageManager::new(storage_config).unwrap(); + + let mut runner: CitreaFullnode<_, _, _, _, DefaultContext> = CitreaFullnode::new( + rollup_config.runner.unwrap(), + rollup_config.public_keys, + rollup_config.rpc, + da_service, + ledger_db, + stf, + storage_manager, + init_variant, + MockCodeCommitment([1u8; 32]), + 10, ) - .expect("Should be able to instiate prover service"); - - let mut runner: StateTransitionRunner<_, _, _, _, _, DefaultContext> = - StateTransitionRunner::new( - rollup_config.runner.unwrap(), - rollup_config.public_keys, - rollup_config.rpc, - da_service, - ledger_db, - stf, - storage_manager, - init_variant, - Some(prover_service), - None, - MockCodeCommitment([1u8; 32]), - ) - .unwrap(); + .unwrap(); let before = *runner.get_state_root(); - let end = runner.run_in_process().await; + let end = runner.run().await; assert!(end.is_err()); let after = *runner.get_state_root(); diff --git a/crates/prover/Cargo.toml b/crates/prover/Cargo.toml index 6e22105cb..5a200305c 100644 --- a/crates/prover/Cargo.toml +++ b/crates/prover/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] # Citrea Deps citrea-primitives = { path = "../primitives", features = ["native"] } +citrea-stf = { path = "../citrea-stf" } sequencer-client = { path = "../sequencer-client" } shared-backup-db = { path = "../shared-backup-db" } @@ -24,12 +25,26 @@ sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner" } # 3rd-party deps anyhow = { workspace = true } +async-trait = { workspace = true } backoff = { workspace = true } borsh = { workspace = true } futures = { workspace = true } hex = { workspace = true } jsonrpsee = { workspace = true } +num_cpus = { workspace = true } rand = { workspace = true } +rayon = { workspace = true } rs_merkle = { workspace = true } +serde = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } + +[dev-dependencies] +sha2 = { workspace = true } +tempfile = { workspace = true } + +sov-mock-da = { path = "../sovereign-sdk/adapters/mock-da", features = ["native"] } +sov-mock-zkvm = { path = "../sovereign-sdk/adapters/mock-zkvm" } +sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner", features = ["mock"] } + +citrea-stf = { path = "../citrea-stf", features = ["native"] } diff --git a/crates/prover/src/lib.rs b/crates/prover/src/lib.rs index d47d5dffa..ebc730ece 100644 --- a/crates/prover/src/lib.rs +++ b/crates/prover/src/lib.rs @@ -5,6 +5,7 @@ use sov_modules_stf_blueprint::StfBlueprint; use tokio::sync::oneshot; use tracing::instrument; +pub mod prover_service; mod runner; pub use runner::*; diff --git a/crates/prover/src/prover_service/mod.rs b/crates/prover/src/prover_service/mod.rs new file mode 100644 index 000000000..e23997a6d --- /dev/null +++ b/crates/prover/src/prover_service/mod.rs @@ -0,0 +1,23 @@ +use citrea_stf::verifier::StateTransitionVerifier; +use sov_rollup_interface::services::da::DaService; +use sov_rollup_interface::stf::StateTransitionFunction; +use sov_rollup_interface::zk::ZkvmHost; + +mod parallel; +pub use parallel::*; + +/// Represents the possible modes of execution for a zkVM program +pub enum ProofGenConfig +where + Stf: StateTransitionFunction, +{ + /// Skips proving. + Skip, + /// The simulator runs the rollup verifier logic without even emulating the zkVM + Simulate(StateTransitionVerifier), + /// The executor runs the rollup verification logic in the zkVM, but does not actually + /// produce a zk proof + Execute, + /// The prover runs the rollup verification logic in the zkVM and produces a zk proof + Prover, +} diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/mod.rs b/crates/prover/src/prover_service/parallel/mod.rs similarity index 95% rename from crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/mod.rs rename to crates/prover/src/prover_service/parallel/mod.rs index d32b65f01..311278eba 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/mod.rs +++ b/crates/prover/src/prover_service/parallel/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; +use citrea_stf::verifier::StateTransitionVerifier; use prover::Prover; use serde::de::DeserializeOwned; use serde::Serialize; @@ -10,12 +11,14 @@ use sov_rollup_interface::da::{DaData, DaSpec}; use sov_rollup_interface::services::da::DaService; use sov_rollup_interface::stf::StateTransitionFunction; use sov_rollup_interface::zk::{Proof, StateTransitionData, ZkvmHost}; +use sov_stf_runner::config::ProverConfig; +use sov_stf_runner::{ + ProofProcessingStatus, ProverGuestRunConfig, ProverService, ProverServiceError, + WitnessSubmissionStatus, +}; use self::prover::ProverStatus; -use super::{ProverService, ProverServiceError}; -use crate::config::ProverConfig; -use crate::verifier::StateTransitionVerifier; -use crate::{ProofGenConfig, ProofProcessingStatus, ProverGuestRunConfig, WitnessSubmissionStatus}; +use crate::prover_service::ProofGenConfig; /// Prover service that generates proofs in parallel. pub struct ParallelProverService diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/prover.rs b/crates/prover/src/prover_service/parallel/prover.rs similarity index 98% rename from crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/prover.rs rename to crates/prover/src/prover_service/parallel/prover.rs index a1267ad95..7f5a6e315 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/parallel/prover.rs +++ b/crates/prover/src/prover_service/parallel/prover.rs @@ -11,9 +11,9 @@ use sov_rollup_interface::da::{BlockHeaderTrait, DaSpec}; use sov_rollup_interface::services::da::DaService; use sov_rollup_interface::stf::StateTransitionFunction; use sov_rollup_interface::zk::{Proof, StateTransitionData, ZkvmHost}; +use sov_stf_runner::{ProofProcessingStatus, ProverServiceError, WitnessSubmissionStatus}; -use super::ProverServiceError; -use crate::{ProofGenConfig, ProofProcessingStatus, WitnessSubmissionStatus}; +use crate::prover_service::ProofGenConfig; pub(crate) enum ProverStatus { WitnessSubmitted(StateTransitionData), diff --git a/crates/prover/src/runner.rs b/crates/prover/src/runner.rs index 303fba253..453fc4982 100644 --- a/crates/prover/src/runner.rs +++ b/crates/prover/src/runner.rs @@ -784,6 +784,11 @@ where } } } + + /// Allows to read current state root + pub fn get_state_root(&self) -> &Stf::StateRoot { + &self.state_root + } } async fn l1_sync( diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/prover_tests.rs b/crates/prover/tests/prover_tests.rs similarity index 97% rename from crates/sovereign-sdk/full-node/sov-stf-runner/tests/prover_tests.rs rename to crates/prover/tests/prover_tests.rs index 8c91b3f71..ebdd6f9df 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/tests/prover_tests.rs +++ b/crates/prover/tests/prover_tests.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; +use citrea_prover::prover_service::ParallelProverService; use sov_mock_da::{ MockAddress, MockBlockHeader, MockDaService, MockDaSpec, MockDaVerifier, MockHash, MockValidityCond, @@ -9,8 +10,8 @@ use sov_rollup_interface::da::Time; use sov_rollup_interface::zk::StateTransitionData; use sov_stf_runner::mock::MockStf; use sov_stf_runner::{ - ParallelProverService, ProofProcessingStatus, ProverGuestRunConfig, ProverService, - ProverServiceError, WitnessSubmissionStatus, + ProofProcessingStatus, ProverGuestRunConfig, ProverService, ProverServiceError, + WitnessSubmissionStatus, }; #[tokio::test] diff --git a/crates/sovereign-sdk/examples/demo-stf/src/lib.rs b/crates/sovereign-sdk/examples/demo-stf/src/lib.rs index 281b83dfe..6faa65785 100644 --- a/crates/sovereign-sdk/examples/demo-stf/src/lib.rs +++ b/crates/sovereign-sdk/examples/demo-stf/src/lib.rs @@ -7,11 +7,3 @@ mod hooks_impl; pub mod runtime; #[cfg(test)] mod tests; - -use sov_modules_stf_blueprint::StfBlueprint; -use sov_rollup_interface::da::DaVerifier; -use sov_stf_runner::verifier::StateTransitionVerifier; - -/// Alias for StateTransitionVerifier. -pub type StfVerifier = - StateTransitionVerifier::Spec, Vm, RT>, DA, Vm>; diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/Cargo.toml b/crates/sovereign-sdk/full-node/sov-stf-runner/Cargo.toml index a96f0cbeb..894d05be8 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/Cargo.toml +++ b/crates/sovereign-sdk/full-node/sov-stf-runner/Cargo.toml @@ -1,90 +1,66 @@ [package] name = "sov-stf-runner" +version = { workspace = true } authors = { workspace = true } -description = "Runs Sovereign SDK rollups and their verifiers" edition = { workspace = true } homepage = { workspace = true } license = { workspace = true } -repository = { workspace = true } -version = { workspace = true } readme = "README.md" +repository = { workspace = true } resolver = "2" +description = "Runs Sovereign SDK rollups and their verifiers" [dependencies] +# 3rd-Party deps anyhow = { workspace = true } -backoff = { workspace = true, optional = true } -num_cpus = { workspace = true } -thiserror = { workspace = true, optional = true } +async-trait = { workspace = true, optional = true } borsh = { workspace = true } -serde_json = { workspace = true } -serde = { workspace = true } -toml = { workspace = true, optional = true } -rs_merkle = { workspace = true } +futures = { workspace = true, optional = true } +hex = { workspace = true } +hyper = { workspace = true, optional = true } +jsonrpsee = { workspace = true, features = ["http-client", "server"], optional = true } +num_cpus = { workspace = true } rand = { workspace = true, optional = true } -jsonrpsee = { workspace = true, features = [ - "http-client", - "server", -], optional = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true, optional = true } tokio = { workspace = true, optional = true } -hex = { workspace = true } +toml = { workspace = true, optional = true } +tower = { workspace = true, optional = true } tracing = { workspace = true, optional = true } -futures = { workspace = true, optional = true } -async-trait = { workspace = true, optional = true } -rayon = { workspace = true, optional = true } -sov-db = { path = "../db/sov-db", optional = true } -sov-rollup-interface = { path = "../../rollup-interface" } -sov-modules-stf-blueprint = { path = "../../module-system/sov-modules-stf-blueprint" } -sequencer-client = { path = "../../../sequencer-client", optional = true } +# Sovereign-SDK deps +sov-db = { path = "../db/sov-db", optional = true } sov-modules-api = { path = "../../module-system/sov-modules-api", default-features = false } +sov-rollup-interface = { path = "../../rollup-interface" } +# Citrea-Deps shared-backup-db = { path = "../../../shared-backup-db", optional = true } -tower-http = { workspace = true, optional = true } -tower = { workspace = true, optional = true } -hyper = { workspace = true, optional = true } - [dev-dependencies] -tempfile = { workspace = true } sha2 = { workspace = true } - -sov-modules-stf-blueprint = { path = "../../module-system/sov-modules-stf-blueprint", features = [ - "native", -] } - -sov-state = { path = "../../module-system/sov-state", features = ["native"] } -sov-modules-api = { path = "../../module-system/sov-modules-api", features = [ - "native", -] } -sov-stf-runner = { path = ".", features = ["mock"] } +tempfile = { workspace = true } sov-mock-da = { path = "../../adapters/mock-da", features = ["native"] } -sov-mock-zkvm = { path = "../../adapters/mock-zkvm" } -sov-prover-storage-manager = { path = "../sov-prover-storage-manager", features = [ - "test-utils", -] } - +sov-modules-api = { path = "../../module-system/sov-modules-api", features = ["native"] } +sov-prover-storage-manager = { path = "../sov-prover-storage-manager", features = ["test-utils"] } +sov-state = { path = "../../module-system/sov-state", features = ["native"] } [features] default = [] mock = ["native"] native = [ - "sov-db", - "sequencer-client", - "sov-modules-api/native", - "sov-modules-stf-blueprint/native", - "jsonrpsee", - "toml", - "tokio", - "tracing", - "futures", - "async-trait", - "rayon", - "thiserror", - "shared-backup-db", - "backoff", - "rand", - "tower-http", - "tower", - "hyper", + "sov-db", + "sov-modules-api/native", + "jsonrpsee", + "toml", + "tokio", + "tracing", + "futures", + "async-trait", + "thiserror", + "shared-backup-db", + "rand", + "tower", + "hyper", ] diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/lib.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/lib.rs index 3c027c995..700f8fab4 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/lib.rs +++ b/crates/sovereign-sdk/full-node/sov-stf-runner/src/lib.rs @@ -2,13 +2,12 @@ #![doc = include_str!("../README.md")] #[cfg(feature = "native")] -mod config; +/// Config +pub mod config; /// Testing utilities. #[cfg(feature = "mock")] pub mod mock; #[cfg(feature = "native")] -mod prover_helpers; -#[cfg(feature = "native")] mod prover_service; #[cfg(feature = "native")] @@ -17,16 +16,18 @@ use std::path::Path; #[cfg(feature = "native")] use anyhow::Context; #[cfg(feature = "native")] -pub use prover_service::*; +pub use config::*; #[cfg(feature = "native")] -mod runner; +pub use prover_service::*; #[cfg(feature = "native")] -pub use config::*; +use sov_modules_api::{DaSpec, Zkvm}; #[cfg(feature = "native")] -pub use runner::*; +use sov_rollup_interface::stf::StateTransitionFunction; -/// Implements the `StateTransitionVerifier` type for checking the validity of a state transition -pub mod verifier; +#[cfg(feature = "native")] +type GenesisParams = >::GenesisParams; +#[cfg(feature = "native")] +type SoftConfirmationHash = [u8; 32]; #[cfg(feature = "native")] /// Reads json file. @@ -42,3 +43,13 @@ pub fn read_json_file>( Ok(config) } + +#[cfg(feature = "native")] +/// How [`StateTransitionRunner`] is initialized +pub enum InitVariant, Vm: Zkvm, Da: DaSpec> { + /// From given state root and soft confirmation hash + Initialized((Stf::StateRoot, SoftConfirmationHash)), + /// From empty state root + /// Genesis params for Stf::init + Genesis(GenesisParams), +} diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_helpers.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_helpers.rs deleted file mode 100644 index 9c444aa13..000000000 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_helpers.rs +++ /dev/null @@ -1,15 +0,0 @@ -use sequencer_client::SequencerClient; -use sov_modules_api::DaSpec; - -pub(crate) async fn get_initial_slot_height(client: &SequencerClient) -> u64 { - loop { - match client.get_soft_batch::(1).await { - Ok(Some(batch)) => return batch.da_slot_height, - _ => { - // sleep 1 - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - continue; - } - } - } -} diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/mod.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/mod.rs index 809555cf4..0977562ee 100644 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/mod.rs +++ b/crates/sovereign-sdk/full-node/sov-stf-runner/src/prover_service/mod.rs @@ -1,6 +1,4 @@ -mod parallel; use async_trait::async_trait; -pub use parallel::ParallelProverService; use serde::{Deserialize, Serialize}; use sov_modules_api::Zkvm; use sov_rollup_interface::da::DaSpec; diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/runner.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/runner.rs deleted file mode 100644 index dc1190287..000000000 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/runner.rs +++ /dev/null @@ -1,1086 +0,0 @@ -use std::collections::VecDeque; -use std::marker::PhantomData; -use std::net::SocketAddr; - -use anyhow::{anyhow, bail}; -use backoff::future::retry as retry_backoff; -use backoff::ExponentialBackoffBuilder; -use borsh::de::BorshDeserialize; -use hyper::Method; -use jsonrpsee::core::client::Error as JsonrpseeError; -use jsonrpsee::RpcModule; -use rand::Rng; -use rs_merkle::algorithms::Sha256; -use rs_merkle::MerkleTree; -use sequencer_client::{GetSoftBatchResponse, SequencerClient}; -use shared_backup_db::{PostgresConnector, ProofType}; -use sov_db::ledger_db::{LedgerDB, SlotCommit}; -use sov_db::schema::types::{BatchNumber, SlotNumber, StoredSoftBatch, StoredStateTransition}; -use sov_modules_api::{Context, SignedSoftConfirmationBatch}; -use sov_modules_stf_blueprint::StfBlueprintTrait; -use sov_rollup_interface::da::{ - BlobReaderTrait, BlockHeaderTrait, DaData, DaSpec, SequencerCommitment, -}; -use sov_rollup_interface::rpc::SoftConfirmationStatus; -use sov_rollup_interface::services::da::{DaService, SlotData}; -pub use sov_rollup_interface::stf::BatchReceipt; -use sov_rollup_interface::stf::{SoftBatchReceipt, StateTransitionFunction}; -use sov_rollup_interface::storage::HierarchicalStorageManager; -use sov_rollup_interface::zk::{Proof, StateTransitionData, Zkvm, ZkvmHost}; -use tokio::sync::oneshot; -use tokio::time::{sleep, Duration}; -use tower_http::cors::{Any, CorsLayer}; -use tracing::{debug, error, info, instrument, warn}; - -use crate::prover_helpers::get_initial_slot_height; -use crate::verifier::StateTransitionVerifier; -use crate::{ProverConfig, ProverService, RollupPublicKeys, RpcConfig, RunnerConfig}; - -type StateRoot = >::StateRoot; -type GenesisParams = >::GenesisParams; -type SoftConfirmationHash = [u8; 32]; - -/// Combines `DaService` with `StateTransitionFunction` and "runs" the rollup. -pub struct StateTransitionRunner -where - Da: DaService, - Vm: ZkvmHost + Zkvm, - Sm: HierarchicalStorageManager, - Stf: StateTransitionFunction::ValidityCondition> - + StfBlueprintTrait, - Ps: ProverService, - C: Context, -{ - start_height: u64, - da_service: Da, - stf: Stf, - storage_manager: Sm, - /// made pub so that sequencer can clone it - pub ledger_db: LedgerDB, - state_root: StateRoot, - batch_hash: SoftConfirmationHash, - rpc_config: RpcConfig, - #[allow(dead_code)] - prover_service: Option, - sequencer_client: SequencerClient, - sequencer_pub_key: Vec, - sequencer_da_pub_key: Vec, - prover_da_pub_key: Vec, - phantom: std::marker::PhantomData, - include_tx_body: bool, - prover_config: Option, - code_commitment: Vm::CodeCommitment, - accept_public_input_as_proven: bool, -} - -/// Represents the possible modes of execution for a zkVM program -pub enum ProofGenConfig -where - Stf: StateTransitionFunction, -{ - /// Skips proving. - Skip, - /// The simulator runs the rollup verifier logic without even emulating the zkVM - Simulate(StateTransitionVerifier), - /// The executor runs the rollup verification logic in the zkVM, but does not actually - /// produce a zk proof - Execute, - /// The prover runs the rollup verification logic in the zkVM and produces a zk proof - Prover, -} - -/// How [`StateTransitionRunner`] is initialized -pub enum InitVariant, Vm: Zkvm, Da: DaSpec> { - /// From given state root and soft confirmation hash - Initialized((Stf::StateRoot, SoftConfirmationHash)), - /// From empty state root - /// Genesis params for Stf::init - Genesis(GenesisParams), -} - -impl StateTransitionRunner -where - Da: DaService + Clone + Send + Sync + 'static, - Vm: ZkvmHost + Zkvm, - Sm: HierarchicalStorageManager, - Stf: StateTransitionFunction< - Vm, - Da::Spec, - Condition = ::ValidityCondition, - PreState = Sm::NativeStorage, - ChangeSet = Sm::NativeChangeSet, - > + StfBlueprintTrait, - C: Context, - Ps: ProverService, -{ - /// Creates a new `StateTransitionRunner`. - /// - /// If a previous state root is provided, uses that as the starting point - /// for execution. Otherwise, initializes the chain using the provided - /// genesis config. - #[allow(clippy::too_many_arguments)] - pub fn new( - runner_config: RunnerConfig, - public_keys: RollupPublicKeys, - rpc_config: RpcConfig, - da_service: Da, - ledger_db: LedgerDB, - stf: Stf, - mut storage_manager: Sm, - init_variant: InitVariant, - prover_service: Option, - prover_config: Option, - code_commitment: Vm::CodeCommitment, - ) -> Result { - let (prev_state_root, prev_batch_hash) = match init_variant { - InitVariant::Initialized((state_root, batch_hash)) => { - debug!("Chain is already initialized. Skipping initialization."); - (state_root, batch_hash) - } - InitVariant::Genesis(params) => { - info!("No history detected. Initializing chain..."); - let storage = storage_manager.create_storage_on_l2_height(0)?; - let (genesis_root, initialized_storage) = stf.init_chain(storage, params); - storage_manager.save_change_set_l2(0, initialized_storage)?; - storage_manager.finalize_l2(0)?; - info!( - "Chain initialization is done. Genesis root: 0x{}", - hex::encode(genesis_root.as_ref()), - ); - (genesis_root, [0; 32]) - } - }; - - // Start the main rollup loop - let item_numbers = ledger_db.get_next_items_numbers(); - let last_soft_batch_processed_before_shutdown = item_numbers.soft_batch_number; - - let start_height = last_soft_batch_processed_before_shutdown; - - Ok(Self { - start_height, - da_service, - stf, - storage_manager, - ledger_db, - state_root: prev_state_root, - batch_hash: prev_batch_hash, - rpc_config, - prover_service, - sequencer_client: SequencerClient::new(runner_config.sequencer_client_url), - sequencer_pub_key: public_keys.sequencer_public_key, - sequencer_da_pub_key: public_keys.sequencer_da_pub_key, - prover_da_pub_key: public_keys.prover_da_pub_key, - phantom: std::marker::PhantomData, - include_tx_body: runner_config.include_tx_body, - prover_config, - code_commitment, - accept_public_input_as_proven: runner_config - .accept_public_input_as_proven - .unwrap_or(false), - }) - } - - /// Starts a RPC server with provided rpc methods. - pub async fn start_rpc_server( - &self, - methods: RpcModule<()>, - channel: Option>, - ) { - let bind_host = match self.rpc_config.bind_host.parse() { - Ok(bind_host) => bind_host, - Err(e) => { - error!("Failed to parse bind host: {}", e); - return; - } - }; - let listen_address = SocketAddr::new(bind_host, self.rpc_config.bind_port); - - let max_connections = self.rpc_config.max_connections; - - let cors = CorsLayer::new() - .allow_methods([Method::POST, Method::OPTIONS]) - .allow_origin(Any) - .allow_headers(Any); - let middleware = tower::ServiceBuilder::new().layer(cors); - - let _handle = tokio::spawn(async move { - let server = jsonrpsee::server::ServerBuilder::default() - .max_connections(max_connections) - .set_http_middleware(middleware) - .build([listen_address].as_ref()) - .await; - - match server { - Ok(server) => { - let bound_address = match server.local_addr() { - Ok(address) => address, - Err(e) => { - error!("{}", e); - return; - } - }; - if let Some(channel) = channel { - if let Err(e) = channel.send(bound_address) { - error!("Could not send bound_address {}: {}", bound_address, e); - return; - } - } - info!("Starting RPC server at {} ", &bound_address); - - let _server_handle = server.start(methods); - futures::future::pending::<()>().await; - } - Err(e) => { - error!("Could not start RPC server: {}", e); - } - } - }); - } - - /// Returns the head soft batch - #[instrument(level = "trace", skip_all, err)] - pub fn get_head_soft_batch(&self) -> anyhow::Result> { - self.ledger_db.get_head_soft_batch() - } - - /// Runs the prover process. - #[instrument(level = "trace", skip_all, err)] - pub async fn run_prover_process(&mut self) -> Result<(), anyhow::Error> { - let skip_submission_until_l1 = std::env::var("SKIP_PROOF_SUBMISSION_UNTIL_L1") - .map_or(0u64, |v| v.parse().unwrap_or(0)); - - // Prover node should sync when a new sequencer commitment arrives - // Check da block get and sync up to the latest block in the latest commitment - let last_scanned_l1_height = self - .ledger_db - .get_prover_last_scanned_l1_height() - .unwrap_or_else(|_| panic!("Failed to get last scanned l1 height from the ledger db")); - - let mut l1_height = match last_scanned_l1_height { - Some(height) => height.0 + 1, - None => get_initial_slot_height::(&self.sequencer_client).await, - }; - - let mut l2_height = self.start_height; - - let prover_config = self.prover_config.clone().unwrap(); - - let pg_client = match prover_config.db_config { - Some(db_config) => { - tracing::debug!("Connecting to postgres"); - Some(PostgresConnector::new(db_config.clone()).await) - } - None => None, - }; - - loop { - let da_service = &self.da_service; - - let exponential_backoff = ExponentialBackoffBuilder::new() - .with_initial_interval(Duration::from_secs(1)) - .with_max_elapsed_time(Some(Duration::from_secs(5 * 60))) - .build(); - let last_finalized_height = retry_backoff(exponential_backoff.clone(), || async { - da_service - .get_last_finalized_block_header() - .await - .map_err(backoff::Error::transient) - }) - .await? - .height(); - - if l1_height > last_finalized_height { - sleep(Duration::from_secs(1)).await; - continue; - } - - let filtered_block = retry_backoff(exponential_backoff.clone(), || async { - da_service - .get_block_at(l1_height) - .await - .map_err(backoff::Error::transient) - }) - .await?; - - // map the height to the hash - self.ledger_db - .set_l1_height_of_l1_hash(filtered_block.header().hash().into(), l1_height) - .unwrap(); - - let mut sequencer_commitments = Vec::::new(); - - self.da_service - .extract_relevant_blobs(&filtered_block) - .into_iter() - .for_each(|mut tx| { - let data = DaData::try_from_slice(tx.full_data()); - - if tx.sender().as_ref() == self.sequencer_da_pub_key.as_slice() { - match data { - Ok(DaData::SequencerCommitment(seq_com)) => { - sequencer_commitments.push(seq_com); - } - Ok(_) => { // We don't care about other types here - // Skip - } - Err(ref e) => { - tracing::error!( - "Found broken DA data in block 0x{}: {:?}. Error: {}", - hex::encode(filtered_block.hash()), - data, - e - ); - } - } - } else if tx.sender().as_ref() == self.prover_da_pub_key.as_slice() { - // The prover doesn't really care about proofs in DA blocks. - // They've already been proven so we can skip here. - } else { - warn!("Force transactions are not implemented yet"); - // TODO: This is where force transactions will land - try to parse DA data force transaction - } - }); - - if sequencer_commitments.is_empty() { - tracing::debug!("No sequencer commitment found at height {}", l1_height,); - - self.ledger_db - .set_prover_last_scanned_l1_height(SlotNumber(l1_height)) - .unwrap_or_else(|_| { - panic!( - "Failed to put prover last scanned l1 height in the ledger db {}", - l1_height - ) - }); - - l1_height += 1; - continue; - } - - tracing::info!( - "Processing {} sequencer commitments at height {}", - sequencer_commitments.len(), - filtered_block.header().height(), - ); - - let initial_state_root = self.state_root.clone(); - let initial_batch_hash = self.batch_hash; - - let mut da_data = self.da_service.extract_relevant_blobs(&filtered_block); - let da_block_header_of_commitments = filtered_block.header().clone(); - let (inclusion_proof, completeness_proof) = self - .da_service - .get_extraction_proof(&filtered_block, &da_data) - .await; - - // if we don't do this, the zk circuit can't read the sequencer commitments - da_data.iter_mut().for_each(|blob| { - blob.full_data(); - }); - - let mut soft_confirmations: VecDeque> = - VecDeque::new(); - let mut state_transition_witnesses: VecDeque> = VecDeque::new(); - let mut da_block_headers_of_soft_confirmations: VecDeque< - Vec<<::Spec as DaSpec>::BlockHeader>, - > = VecDeque::new(); - - let mut sof_soft_confirmations_to_push = vec![]; - let mut state_transition_witnesses_to_push = vec![]; - let mut da_block_headers_to_push: Vec< - <::Spec as DaSpec>::BlockHeader, - > = vec![]; - - // start fetching blocks from sequencer, when you see a soft batch with l1 height more than end_l1_height, stop - // while getting the blocks to all the same ops as full node - // after stopping call continue and look for a new seq_commitment - // change the item numbers only after the sync is done so not for every da block - - loop { - let inner_client = &self.sequencer_client; - let soft_batch = match retry_backoff(exponential_backoff.clone(), || async move { - match inner_client.get_soft_batch::(l2_height).await { - Ok(Some(soft_batch)) => Ok(soft_batch), - Ok(None) => { - debug!("Soft Batch: no batch at height {}", l2_height); - - // Return a Permanent error so that we exit the retry. - Err(backoff::Error::Permanent( - "No soft batch published".to_owned(), - )) - } - Err(e) => match e.downcast_ref::() { - Some(JsonrpseeError::Transport(e)) => { - let error_msg = format!( - "Soft Batch: connection error during RPC call: {:?}", - e - ); - error!(error_msg); - Err(backoff::Error::Transient { - err: error_msg, - retry_after: None, - }) - } - _ => { - let error_msg = - format!("Soft Batch: unknown error from RPC call: {:?}", e); - error!(error_msg); - Err(backoff::Error::Transient { - err: error_msg, - retry_after: None, - }) - } - }, - } - }) - .await - { - Ok(soft_batch) => soft_batch, - Err(_) => { - break; - } - }; - - debug!( - "Running soft confirmation batch #{} with hash: 0x{} on DA block #{}", - l2_height, - hex::encode(soft_batch.hash), - soft_batch.da_slot_height - ); - - let mut signed_soft_confirmation: SignedSoftConfirmationBatch = - soft_batch.clone().into(); - - sof_soft_confirmations_to_push.push(signed_soft_confirmation.clone()); - - // The filtered block of soft batch, which is the block at the da_slot_height of soft batch - let filtered_block = retry_backoff(exponential_backoff.clone(), || async { - da_service - .get_block_at(soft_batch.da_slot_height) - .await - .map_err(backoff::Error::transient) - }) - .await?; - - if da_block_headers_to_push.is_empty() - || da_block_headers_to_push.last().unwrap().height() - != filtered_block.header().height() - { - da_block_headers_to_push.push(filtered_block.header().clone()); - } - - let mut data_to_commit = SlotCommit::new(filtered_block.clone()); - - let pre_state = self - .storage_manager - .create_storage_on_l2_height(l2_height)?; - - let slot_result = self.stf.apply_soft_batch( - self.sequencer_pub_key.as_slice(), - // TODO(https://github.com/Sovereign-Labs/sovereign-sdk/issues/1247): incorrect pre-state root in case of re-org - &self.state_root, - pre_state, - Default::default(), - filtered_block.header(), - &filtered_block.validity_condition(), - &mut signed_soft_confirmation, - ); - - state_transition_witnesses_to_push.push(slot_result.witness); - - for receipt in slot_result.batch_receipts { - data_to_commit.add_batch(receipt); - } - - self.storage_manager - .save_change_set_l2(l2_height, slot_result.change_set)?; - - let batch_receipt = data_to_commit.batch_receipts()[0].clone(); - - let next_state_root = slot_result.state_root; - - // Check if post state root is the same as the one in the soft batch - if next_state_root.as_ref().to_vec() != soft_batch.state_root { - bail!("Post state root mismatch") - } - - let soft_batch_receipt = SoftBatchReceipt::<_, _, Da::Spec> { - state_root: next_state_root.as_ref().to_vec(), - phantom_data: PhantomData::, - hash: batch_receipt.hash, - prev_hash: batch_receipt.prev_hash, - da_slot_hash: filtered_block.header().hash(), - da_slot_height: filtered_block.header().height(), - da_slot_txs_commitment: filtered_block.header().txs_commitment(), - tx_receipts: batch_receipt.tx_receipts, - soft_confirmation_signature: soft_batch.soft_confirmation_signature, - pub_key: soft_batch.pub_key, - deposit_data: soft_batch.deposit_data.into_iter().map(|x| x.tx).collect(), - l1_fee_rate: soft_batch.l1_fee_rate, - timestamp: soft_batch.timestamp, - }; - - self.ledger_db.commit_soft_batch(soft_batch_receipt, true)?; - self.ledger_db.extend_l2_range_of_l1_slot( - SlotNumber(filtered_block.header().height()), - BatchNumber(l2_height), - )?; - - self.state_root = next_state_root; - self.batch_hash = soft_batch.hash; - - debug!( - "New State Root after soft confirmation #{} is: {:?}", - l2_height, self.state_root - ); - - self.storage_manager.finalize_l2(l2_height)?; - - l2_height += 1; - } - - soft_confirmations.push_back(sof_soft_confirmations_to_push); - state_transition_witnesses.push_back(state_transition_witnesses_to_push); - da_block_headers_of_soft_confirmations.push_back(da_block_headers_to_push); - - let hash = da_block_header_of_commitments.hash(); - - let transition_data: StateTransitionData = - StateTransitionData { - initial_state_root, - final_state_root: self.state_root.clone(), - initial_batch_hash, - da_data, - da_block_header_of_commitments, - inclusion_proof, - completeness_proof, - soft_confirmations, - state_transition_witnesses, - da_block_headers_of_soft_confirmations, - - sequencer_public_key: self.sequencer_pub_key.clone(), - sequencer_da_public_key: self.sequencer_da_pub_key.clone(), - }; - - let should_prove: bool = { - let mut rng = rand::thread_rng(); - // if proof_sampling_number is 0, then we always prove and submit - // otherwise we submit and prove with a probability of 1/proof_sampling_number - if prover_config.proof_sampling_number == 0 { - true - } else { - rng.gen_range(0..prover_config.proof_sampling_number) == 0 - } - }; - - // Skip submission until l1 height - if l1_height >= skip_submission_until_l1 && should_prove { - tracing::info!("Sending for proving"); - - let prover_service = self - .prover_service - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Prover service is not initialized"))?; - - prover_service.submit_witness(transition_data).await; - - prover_service.prove(hash.clone()).await?; - - let (tx_id, proof) = prover_service - .wait_for_proving_and_send_to_da(hash.clone(), &self.da_service) - .await?; - - let tx_id_u8 = tx_id.into(); - - // l1_height => (tx_id, proof, transition_data) - // save proof along with tx id to db, should be queriable by slot number or slot hash - let transition_data: sov_modules_api::StateTransition< - ::Spec, - Stf::StateRoot, - > = Vm::extract_output(&proof).expect("Proof should be deserializable"); - - match proof { - Proof::PublicInput(_) => { - tracing::debug!("Proof is public input, skipping"); - } - Proof::Full(ref proof) => { - tracing::debug!("Verifying proof!"); - let transition_data_from_proof = Vm::verify_and_extract_output::< - ::Spec, - Stf::StateRoot, - >( - &proof.clone(), &self.code_commitment - ) - .expect("Proof should be verifiable"); - - tracing::debug!( - "transition data from proof: {:?}", - transition_data_from_proof - ); - } - } - - tracing::trace!("transition data: {:?}", transition_data); - - let stored_state_transition = StoredStateTransition { - initial_state_root: transition_data.initial_state_root.as_ref().to_vec(), - final_state_root: transition_data.final_state_root.as_ref().to_vec(), - state_diff: transition_data.state_diff, - da_slot_hash: transition_data.da_slot_hash.into(), - sequencer_public_key: transition_data.sequencer_public_key, - sequencer_da_public_key: transition_data.sequencer_da_public_key, - validity_condition: borsh::to_vec(&transition_data.validity_condition).unwrap(), - }; - - match pg_client.as_ref() { - Some(Ok(pool)) => { - tracing::debug!("Inserting proof data into postgres"); - let (proof_data, proof_type) = match proof.clone() { - Proof::Full(full_proof) => (full_proof, ProofType::Full), - Proof::PublicInput(public_input) => { - (public_input, ProofType::PublicInput) - } - }; - pool.insert_proof_data( - tx_id_u8.to_vec(), - proof_data, - stored_state_transition.clone().into(), - proof_type, - ) - .await - .unwrap(); - } - _ => { - tracing::warn!("No postgres client found"); - } - } - - self.ledger_db.put_proof_data( - l1_height, - tx_id_u8, - proof, - stored_state_transition, - )?; - } else { - tracing::info!("Skipping proving for l1 height {}", l1_height); - } - - for sequencer_commitment in sequencer_commitments.into_iter() { - // Save commitments on prover ledger db - self.ledger_db - .update_commitments_on_da_slot(l1_height, sequencer_commitment.clone()) - .unwrap(); - let l2_start_height = sequencer_commitment.l2_start_block_number; - let l2_end_height = sequencer_commitment.l2_end_block_number; - for i in l2_start_height..=l2_end_height { - self.ledger_db - .put_soft_confirmation_status( - BatchNumber(i), - SoftConfirmationStatus::Finalized, - ) - .unwrap_or_else(|_| { - panic!( - "Failed to put soft confirmation status in the ledger db {}", - i - ) - }); - } - } - - self.ledger_db - .set_prover_last_scanned_l1_height(SlotNumber(l1_height))?; - l1_height += 1; - } - } - - /// Runs the rollup. - #[instrument(level = "trace", skip_all, err)] - pub async fn run_in_process(&mut self) -> Result<(), anyhow::Error> { - let mut last_l1_height = 0; - let mut cur_l1_block = None; - - let mut height = self.start_height; - info!("Starting to sync from height {}", height); - - loop { - let exponential_backoff = ExponentialBackoffBuilder::new() - .with_initial_interval(Duration::from_secs(1)) - .with_max_elapsed_time(Some(Duration::from_secs(15 * 60))) - .build(); - let inner_client = &self.sequencer_client; - let soft_batches: Vec = - match retry_backoff(exponential_backoff.clone(), || async move { - match inner_client - .get_soft_batch_range::(height..height + 10) - .await - { - Ok(soft_batches) => { - Ok(soft_batches.into_iter().flatten().collect::>()) - } - Err(e) => match e.downcast_ref::() { - Some(JsonrpseeError::Transport(e)) => { - let error_msg = format!( - "Soft Batch: connection error during RPC call: {:?}", - e - ); - debug!(error_msg); - Err(backoff::Error::Transient { - err: error_msg, - retry_after: None, - }) - } - _ => Err(backoff::Error::Transient { - err: format!("Soft Batch: unknown error from RPC call: {:?}", e), - retry_after: None, - }), - }, - } - }) - .await - { - Ok(soft_batches) => soft_batches, - Err(_) => { - continue; - } - }; - - if soft_batches.is_empty() { - debug!( - "Soft Batch: no batch at starting height {}, retrying...", - height - ); - - // We wait for 2 seconds and then return a Permanent error so that we exit the retry. - // This should not backoff exponentially - sleep(Duration::from_secs(2)).await; - continue; - } - - for soft_batch in soft_batches { - if last_l1_height != soft_batch.da_slot_height || cur_l1_block.is_none() { - last_l1_height = soft_batch.da_slot_height; - // TODO: for a node, the da block at slot_height might not have been finalized yet - // should wait for it to be finalized - let da_service = &self.da_service; - let filtered_block = retry_backoff(exponential_backoff.clone(), || async { - da_service - .get_block_at(soft_batch.da_slot_height) - .await - .map_err(backoff::Error::transient) - }) - .await?; - - let mut sequencer_commitments = Vec::::new(); - let mut zk_proofs = Vec::::new(); - - self.da_service - .extract_relevant_blobs(&filtered_block) - .into_iter() - .for_each(|mut tx| { - let data = DaData::try_from_slice(tx.full_data()); - // Check for commitment - if tx.sender().as_ref() == self.sequencer_da_pub_key.as_slice() { - match data { - Ok(DaData::SequencerCommitment(seq_com)) => { - sequencer_commitments.push(seq_com); - } - Ok(_) => { - // We don't care about other types here - // Skip - } - Err(ref e) => { - tracing::error!( - "Found broken DA data in block 0x{}: {:?}. Error: {}", - hex::encode(filtered_block.hash()), - data, - e - ); - } - } - } - let data = DaData::try_from_slice(tx.full_data()); - // Check for proof - if tx.sender().as_ref() == self.prover_da_pub_key.as_slice() { - match data { - Ok(DaData::ZKProof(proof)) => { - zk_proofs.push(proof); - } - Ok(_) => { - // We don't care about other types here - // Skip - } - Err(ref e) => { - tracing::error!( - "Found broken DA data in block 0x{}: {:?}. Error: {}", - hex::encode(filtered_block.hash()), - data, - e - ); - } - } - } else { - warn!("Force transactions are not implemented yet"); - // TODO: This is where force transactions will land - try to parse DA data force transaction - } - }); - - for proof in zk_proofs { - tracing::warn!("Processing zk proof: {:?}", proof); - let state_transition = match proof.clone() { - Proof::Full(proof) => { - let code_commitment = self.code_commitment.clone(); - - tracing::warn!( - "using code commitment: {:?}", - serde_json::to_string(&code_commitment).unwrap() - ); - - if let Ok(proof_data) = - Vm::verify_and_extract_output::< - ::Spec, - Stf::StateRoot, - >(&proof, &code_commitment) - { - if proof_data.sequencer_da_public_key - != self.sequencer_da_pub_key - || proof_data.sequencer_public_key != self.sequencer_pub_key - { - tracing::warn!( - "Proof verification: Sequencer public key or sequencer da public key mismatch. Skipping proof." - ); - continue; - } - proof_data - } else { - tracing::warn!( - "Proof verification: SNARK verification failed. Skipping to next proof.." - ); - continue; - } - } - Proof::PublicInput(_) => { - if !self.accept_public_input_as_proven { - tracing::warn!("Found public input in da block number: {:?}, Skipping to next proof..", soft_batch.da_slot_height); - continue; - } - // public input is accepted only in tests, so ok to expect - Vm::extract_output(&proof).expect("Proof should be deserializable") - } - }; - - let stored_state_transition = StoredStateTransition { - initial_state_root: state_transition - .initial_state_root - .as_ref() - .to_vec(), - final_state_root: state_transition.final_state_root.as_ref().to_vec(), - state_diff: state_transition.state_diff, - da_slot_hash: state_transition.da_slot_hash.clone().into(), - sequencer_public_key: state_transition.sequencer_public_key, - sequencer_da_public_key: state_transition.sequencer_da_public_key, - validity_condition: borsh::to_vec(&state_transition.validity_condition) - .unwrap(), - }; - - let l1_hash = state_transition.da_slot_hash.into(); - - // This is the l1 height where the sequencer commitment was read by the prover and proof generated by those commitments - // We need to get commitments in this l1 hegight and set them as proven - let l1_height = match self.ledger_db.get_l1_height_of_l1_hash(l1_hash)? { - Some(l1_height) => l1_height, - None => { - tracing::warn!( - "Proof verification: L1 height not found for l1 hash: {:?}. Skipping proof.", - l1_hash - ); - continue; - } - }; - - let proven_commitments = match self - .ledger_db - .get_commitments_on_da_slot(l1_height)? - { - Some(commitments) => commitments, - None => { - tracing::warn!( - "Proof verification: No commitments found for l1 height: {}. Skipping proof.", - l1_height - ); - continue; - } - }; - - let l2_height = proven_commitments[0].l2_start_block_number; - let prior_soft_batches = self.ledger_db.get_soft_batch_range( - &(BatchNumber(l2_height - 1)..BatchNumber(l2_height)), - )?; - - let prior_soft_batch = prior_soft_batches.first().unwrap(); - if prior_soft_batch.state_root.as_slice() - != state_transition.initial_state_root.as_ref() - { - tracing::warn!( - "Proof verification: For a known and verified sequencer commitment. Pre state root mismatch - expected 0x{} but got 0x{}. Skipping proof.", - hex::encode(&prior_soft_batch.state_root), - hex::encode(&state_transition.initial_state_root) - ); - continue; - } - - for commitment in proven_commitments { - let l2_height_start = commitment.l2_start_block_number; - let l2_height_end = commitment.l2_end_block_number; - - // All soft confirmations in these blocks are now proven - for i in l2_height_start..=l2_height_end { - self.ledger_db.put_soft_confirmation_status( - BatchNumber(i), - SoftConfirmationStatus::Proven, - )?; - } - } - // store in ledger db - self.ledger_db.update_verified_proof_data( - soft_batch.da_slot_height, - proof.clone(), - stored_state_transition, - )?; - } - - for sequencer_commitment in sequencer_commitments.iter() { - tracing::warn!( - "Processing sequencer commitment: {:?}", - sequencer_commitment - ); - - let start_l2_height = sequencer_commitment.l2_start_block_number; - let end_l2_height = sequencer_commitment.l2_end_block_number; - - let range_end = BatchNumber(end_l2_height + 1); - // Traverse each item's field of vector of transactions, put them in merkle tree - // and compare the root with the one from the ledger - let stored_soft_batches: Vec = self - .ledger_db - .get_soft_batch_range(&(BatchNumber(start_l2_height)..range_end))?; - - let soft_batches_tree = MerkleTree::::from_leaves( - stored_soft_batches - .iter() - .map(|x| x.hash) - .collect::>() - .as_slice(), - ); - - if soft_batches_tree.root() != Some(sequencer_commitment.merkle_root) { - tracing::warn!( - "Merkle root mismatch - expected 0x{} but got 0x{}. Skipping commitment.", - hex::encode( - soft_batches_tree - .root() - .ok_or(anyhow!("Could not calculate soft batch tree root"))? - ), - hex::encode(sequencer_commitment.merkle_root) - ); - } else { - self.ledger_db.update_commitments_on_da_slot( - soft_batch.da_slot_height, - sequencer_commitment.clone(), - )?; - - for i in start_l2_height..=end_l2_height { - self.ledger_db.put_soft_confirmation_status( - BatchNumber(i), - SoftConfirmationStatus::Finalized, - )?; - } - } - } - - cur_l1_block = Some(filtered_block); - } - - let cur_l1_block = cur_l1_block.clone().unwrap(); - - info!( - "Running soft confirmation batch #{} with hash: 0x{} on DA block #{}", - height, - hex::encode(soft_batch.hash), - cur_l1_block.header().height() - ); - - let mut data_to_commit = SlotCommit::new(cur_l1_block.clone()); - - let pre_state = self.storage_manager.create_storage_on_l2_height(height)?; - - let slot_result = self.stf.apply_soft_batch( - self.sequencer_pub_key.as_slice(), - // TODO(https://github.com/Sovereign-Labs/sovereign-sdk/issues/1247): incorrect pre-state root in case of re-org - &self.state_root, - pre_state, - Default::default(), - cur_l1_block.header(), - &cur_l1_block.validity_condition(), - &mut soft_batch.clone().into(), - ); - - let next_state_root = slot_result.state_root; - // Check if post state root is the same as the one in the soft batch - if next_state_root.as_ref().to_vec() != soft_batch.state_root { - warn!("Post state root mismatch at height: {}", height); - continue; - } - - for receipt in slot_result.batch_receipts { - data_to_commit.add_batch(receipt); - } - - self.storage_manager - .save_change_set_l2(height, slot_result.change_set)?; - - let batch_receipt = data_to_commit.batch_receipts()[0].clone(); - - let soft_batch_receipt = SoftBatchReceipt::<_, _, Da::Spec> { - state_root: next_state_root.as_ref().to_vec(), - phantom_data: PhantomData::, - hash: soft_batch.hash, - prev_hash: soft_batch.prev_hash, - da_slot_hash: cur_l1_block.header().hash(), - da_slot_height: cur_l1_block.header().height(), - da_slot_txs_commitment: cur_l1_block.header().txs_commitment(), - tx_receipts: batch_receipt.tx_receipts, - soft_confirmation_signature: soft_batch.soft_confirmation_signature, - pub_key: soft_batch.pub_key, - deposit_data: soft_batch.deposit_data.into_iter().map(|x| x.tx).collect(), - l1_fee_rate: soft_batch.l1_fee_rate, - timestamp: soft_batch.timestamp, - }; - - self.ledger_db - .commit_soft_batch(soft_batch_receipt, self.include_tx_body)?; - self.ledger_db.extend_l2_range_of_l1_slot( - SlotNumber(cur_l1_block.header().height()), - BatchNumber(height), - )?; - - self.state_root = next_state_root; - self.batch_hash = soft_batch.hash; - - info!( - "New State Root after soft confirmation #{} is: {:?}", - height, self.state_root - ); - - self.storage_manager.finalize_l2(height)?; - - height += 1; - } - } - } - - /// Allows to read current state root - pub fn get_state_root(&self) -> &Stf::StateRoot { - &self.state_root - } -} diff --git a/crates/sovereign-sdk/full-node/sov-stf-runner/src/verifier.rs b/crates/sovereign-sdk/full-node/sov-stf-runner/src/verifier.rs deleted file mode 100644 index 73123e9e4..000000000 --- a/crates/sovereign-sdk/full-node/sov-stf-runner/src/verifier.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::marker::PhantomData; - -use sov_rollup_interface::da::{BlockHeaderTrait, DaVerifier}; -use sov_rollup_interface::stf::StateTransitionFunction; -use sov_rollup_interface::zk::{StateTransition, StateTransitionData, Zkvm, ZkvmGuest}; - -/// 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, - Stf: StateTransitionFunction, -{ - /// Create a [`StateTransitionVerifier`] - pub fn new(app: Stf, da_verifier: Da) -> Self { - Self { - app, - da_verifier, - phantom: Default::default(), - } - } - - /// Verify the next block - pub fn run_sequencer_commitments_in_da_slot( - &self, - zkvm: Zk, - pre_state: Stf::PreState, - ) -> Result<(), Da::Error> { - println!("Running sequencer commitments in DA slot"); - let data: StateTransitionData = zkvm.read_from_host(); - let validity_condition = self.da_verifier.verify_relevant_tx_list( - &data.da_block_header_of_commitments, - &data.da_data, - data.inclusion_proof, - data.completeness_proof, - )?; - - println!("going into apply_soft_confirmations_from_sequencer_commitments"); - let (final_state_root, state_diff) = self - .app - .apply_soft_confirmations_from_sequencer_commitments( - data.sequencer_public_key.as_ref(), - data.sequencer_da_public_key.as_ref(), - &data.initial_state_root, - data.initial_batch_hash, - pre_state, - data.da_data, - data.state_transition_witnesses, - data.da_block_headers_of_soft_confirmations, - &validity_condition, - data.soft_confirmations, - ); - - 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: StateTransition = StateTransition { - initial_state_root: data.initial_state_root, - final_state_root, - initial_batch_hash: data.initial_batch_hash, - validity_condition, // TODO: not sure about what to do with this yet - state_diff, - da_slot_hash: data.da_block_header_of_commitments.hash(), - sequencer_public_key: data.sequencer_public_key, - sequencer_da_public_key: data.sequencer_da_public_key, - }; - - zkvm.commit(&out); - Ok(()) - } -} diff --git a/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/Cargo.toml b/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/Cargo.toml index c51d31fad..706a2f309 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/Cargo.toml +++ b/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/Cargo.toml @@ -12,7 +12,6 @@ resolver = "2" description = "This crate contains abstractions needed to create a new rollup" [dependencies] -sequencer-client = { path = "../../../sequencer-client" } sov-cli = { path = "../../module-system/sov-cli" } sov-modules-api = { path = "../../module-system/sov-modules-api", features = ["native"] } sov-rollup-interface = { path = "../../rollup-interface", features = ["native"] } diff --git a/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/src/lib.rs b/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/src/lib.rs index 65bd5f2e5..c65781edf 100644 --- a/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/src/lib.rs +++ b/crates/sovereign-sdk/module-system/sov-modules-rollup-blueprint/src/lib.rs @@ -3,23 +3,17 @@ mod runtime_rpc; mod wallet; -use std::net::SocketAddr; use async_trait::async_trait; pub use runtime_rpc::*; use sov_db::ledger_db::LedgerDB; use sov_modules_api::{Context, DaSpec, Spec}; -use sov_modules_stf_blueprint::{GenesisParams, Runtime as RuntimeTrait, StfBlueprint}; +use sov_modules_stf_blueprint::{GenesisParams, Runtime as RuntimeTrait}; use sov_rollup_interface::services::da::DaService; use sov_rollup_interface::storage::HierarchicalStorageManager; use sov_rollup_interface::zk::{Zkvm, ZkvmHost}; -use sov_state::storage::NativeStorage; use sov_state::Storage; -use sov_stf_runner::{ - FullNodeConfig, InitVariant, ProverConfig, ProverService, StateTransitionRunner, -}; -use tokio::sync::oneshot; -use tracing::{instrument, Instrument}; +use sov_stf_runner::{FullNodeConfig, ProverConfig, ProverService}; pub use wallet::*; /// This trait defines how to crate all the necessary dependencies required by a rollup. @@ -124,134 +118,4 @@ pub trait RollupBlueprint: Sized + Send + Sync { fn create_ledger_db(&self, rollup_config: &FullNodeConfig) -> LedgerDB { LedgerDB::with_path(&rollup_config.storage.path).expect("Ledger DB failed to open") } - - /// Creates a new rollup. - #[instrument(level = "trace", skip_all)] - async fn create_new_rollup( - &self, - runtime_genesis_paths: &>::GenesisPaths, - rollup_config: FullNodeConfig, - ) -> Result, anyhow::Error> - where - ::Storage: NativeStorage, - { - let da_service = self.create_da_service(&rollup_config).await; - - // TODO: Double check what kind of storage needed here. - // Maybe whole "prev_root" can be initialized inside runner - // Getting block here, so prover_service doesn't have to be `Send` - - let ledger_db = self.create_ledger_db(&rollup_config); - let genesis_config = self.create_genesis_config(runtime_genesis_paths, &rollup_config)?; - - let mut storage_manager = self.create_storage_manager(&rollup_config)?; - let prover_storage = storage_manager.create_finalized_storage()?; - - let head_soft_batch = ledger_db.get_head_soft_batch()?; - let prev_data = match head_soft_batch { - Some((number, soft_batch)) => { - Some((prover_storage.get_root_hash(number.0 + 1)?, soft_batch.hash)) - } - None => None, - }; - - let runner_config = rollup_config.runner.expect("Runner config is missing"); - // TODO(https://github.com/Sovereign-Labs/sovereign-sdk/issues/1218) - let rpc_methods = self.create_rpc_methods( - &prover_storage, - &ledger_db, - &da_service, - Some(runner_config.sequencer_client_url.clone()), - )?; - - let native_stf = StfBlueprint::new(); - - let genesis_root = prover_storage.get_root_hash(1); - - let init_variant = match prev_data { - Some((root_hash, batch_hash)) => InitVariant::Initialized((root_hash, batch_hash)), - None => match genesis_root { - Ok(root_hash) => InitVariant::Initialized((root_hash, [0; 32])), - _ => InitVariant::Genesis(genesis_config), - }, - }; - - let code_commitment = self.get_code_commitment(); - - let runner = StateTransitionRunner::new( - runner_config, - rollup_config.public_keys, - rollup_config.rpc, - da_service, - ledger_db, - native_stf, - storage_manager, - init_variant, - None, - None, - code_commitment, - )?; - - Ok(FullNode { - runner, - rpc_methods, - }) - } -} - -/// Dependencies needed to run the rollup. -pub struct FullNode { - /// The State Transition Runner. - #[allow(clippy::type_complexity)] - pub runner: StateTransitionRunner< - StfBlueprint, - S::StorageManager, - S::DaService, - S::Vm, - S::ProverService, - S::NativeContext, - >, - /// Rpc methods for the rollup. - pub rpc_methods: jsonrpsee::RpcModule<()>, -} - -impl FullNode { - /// Runs the rollup. - #[instrument( - name = "FullNode", - level = "info", - skip(self), - err, - ret(level = "error") - )] - pub async fn run(self) -> Result<(), anyhow::Error> { - self.run_and_report_rpc_port(None).await - } - - /// Only run the rpc. - pub async fn run_rpc(self) -> Result<(), anyhow::Error> { - self.runner - .start_rpc_server(self.rpc_methods, None) - .instrument(tracing::Span::current()) - .await; - Ok(()) - } - - /// Runs the rollup. Reports rpc port to the caller using the provided channel. - pub async fn run_and_report_rpc_port( - self, - channel: Option>, - ) -> Result<(), anyhow::Error> { - let mut runner = self.runner; - runner - .start_rpc_server(self.rpc_methods, channel) - .instrument(tracing::Span::current()) - .await; - - runner.run_in_process().await?; - Ok(()) - } }