diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4fd437cf7..11a156d91 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -101,7 +101,7 @@ jobs: # building dependencies, only chceking them, so we can share caches # effectively. needs: check - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: buildjet-8vcpu-ubuntu-2204 timeout-minutes: 60 env: CI_SKIP_GUEST_BUILD: "1" diff --git a/Cargo.lock b/Cargo.lock index 9b9948826..705c5e45b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7363,6 +7363,33 @@ dependencies = [ "librocksdb-sys", ] +[[package]] +name = "rollup-template" +version = "0.2.0" +dependencies = [ + "anyhow", + "borsh", + "clap 4.4.6", + "jsonrpsee 0.20.1", + "serde", + "serde_json", + "sov-accounts", + "sov-bank", + "sov-db", + "sov-modules-api", + "sov-modules-stf-template", + "sov-risc0-adapter", + "sov-rollup-interface", + "sov-sequencer", + "sov-sequencer-registry", + "sov-state", + "sov-stf-runner", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber 0.3.17", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -8908,6 +8935,7 @@ dependencies = [ "hex", "jsonrpsee 0.20.1", "serde", + "serde_json", "sov-accounts", "sov-bank", "sov-celestia-adapter", diff --git a/Cargo.toml b/Cargo.toml index c510e9aed..2088c0eff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ members = [ "full-node/sov-ethereum", "full-node/sov-stf-runner", + "templates/rollup-template", + "utils/zk-cycle-macros", "utils/zk-cycle-utils", "utils/bashtestmd", diff --git a/examples/demo-rollup/provers/risc0/guest-celestia/Cargo.lock b/examples/demo-rollup/provers/risc0/guest-celestia/Cargo.lock index 64196e35e..5b2043023 100644 --- a/examples/demo-rollup/provers/risc0/guest-celestia/Cargo.lock +++ b/examples/demo-rollup/provers/risc0/guest-celestia/Cargo.lock @@ -2144,6 +2144,7 @@ dependencies = [ "borsh", "hex", "serde", + "serde_json", "sov-rollup-interface", ] diff --git a/examples/demo-rollup/provers/risc0/guest-mock/Cargo.lock b/examples/demo-rollup/provers/risc0/guest-mock/Cargo.lock index 72c93ef3e..7fef493c9 100644 --- a/examples/demo-rollup/provers/risc0/guest-mock/Cargo.lock +++ b/examples/demo-rollup/provers/risc0/guest-mock/Cargo.lock @@ -1064,6 +1064,7 @@ dependencies = [ "borsh", "hex", "serde", + "serde_json", "sov-rollup-interface", ] diff --git a/examples/demo-rollup/src/register_rpc.rs b/examples/demo-rollup/src/register_rpc.rs index 9f119dc69..13d8d8f28 100644 --- a/examples/demo-rollup/src/register_rpc.rs +++ b/examples/demo-rollup/src/register_rpc.rs @@ -12,8 +12,8 @@ use sov_rollup_interface::zk::Zkvm; use sov_sequencer::get_sequencer_rpc; use sov_stf_runner::get_ledger_rpc; -/// register sequencer rpc methods. -pub fn register_sequencer( +// register sequencer rpc methods. +pub(crate) fn register_sequencer( da_service: Da, app: &mut App, methods: &mut jsonrpsee::RpcModule<()>, @@ -29,8 +29,8 @@ where .context("Failed to merge Txs RPC modules") } -/// register ledger rpc methods. -pub fn register_ledger( +// register ledger rpc methods. +pub(crate) fn register_ledger( ledger_db: LedgerDB, methods: &mut jsonrpsee::RpcModule<()>, ) -> Result<(), anyhow::Error> { @@ -41,8 +41,8 @@ pub fn register_ledger( } #[cfg(feature = "experimental")] -/// register ethereum methods. -pub fn register_ethereum( +// register ethereum methods. +pub(crate) fn register_ethereum( da_service: Da, eth_rpc_config: EthRpcConfig, storage: C::Storage, diff --git a/examples/demo-rollup/src/rollup.rs b/examples/demo-rollup/src/rollup.rs index 038a24163..13ef9bfa6 100644 --- a/examples/demo-rollup/src/rollup.rs +++ b/examples/demo-rollup/src/rollup.rs @@ -44,22 +44,22 @@ type ZkStf = AppTemplate { - /// Implementation of the STF. - pub app: App, - /// Data availability service. - pub da_service: Da, - /// Ledger db. - pub ledger_db: LedgerDB, - /// Runner configuration. - pub runner_config: RunnerConfig, - /// Initial rollup configuration. - pub genesis_config: GenesisConfig, + // Implementation of the STF. + pub(crate) app: App, + // Data availability service. + pub(crate) da_service: Da, + // Ledger db. + pub(crate) ledger_db: LedgerDB, + // Runner configuration. + pub(crate) runner_config: RunnerConfig, + // Initial rollup configuration. + pub(crate) genesis_config: GenesisConfig, #[cfg(feature = "experimental")] - /// Configuration for the Ethereum RPC. - pub eth_rpc_config: EthRpcConfig, - /// Prover for the rollup. + // Configuration for the Ethereum RPC. + pub(crate) eth_rpc_config: EthRpcConfig, + // Prover for the rollup. #[allow(clippy::type_complexity)] - pub prover: Option, Da, Vm>>, + pub(crate) prover: Option, Da, Vm>>, } pub fn configure_prover( diff --git a/examples/demo-rollup/tests/test_helpers.rs b/examples/demo-rollup/tests/test_helpers.rs index 4b769fc01..bb4db679b 100644 --- a/examples/demo-rollup/tests/test_helpers.rs +++ b/examples/demo-rollup/tests/test_helpers.rs @@ -3,7 +3,7 @@ use std::path::Path; use demo_stf::genesis_config::GenesisPaths; use sov_demo_rollup::{new_rollup_with_mock_da_from_config, DemoProverConfig}; -use sov_rollup_interface::mocks::MockDaConfig; +use sov_rollup_interface::mocks::{MockAddress, MockDaConfig}; use sov_rollup_interface::zk::ZkvmHost; use sov_stf_runner::{RollupConfig, RpcConfig, RunnerConfig, StorageConfig}; use tokio::sync::oneshot; @@ -27,7 +27,9 @@ pub async fn start_rollup>( bind_port: 0, }, }, - da: MockDaConfig {}, + da: MockDaConfig { + sender_address: MockAddress { addr: [0; 32] }, + }, }; let rollup = new_rollup_with_mock_da_from_config(rollup_config, prover, genesis_paths) diff --git a/examples/demo-stf/src/genesis_config.rs b/examples/demo-stf/src/genesis_config.rs index 8386ba469..caeb2ffac 100644 --- a/examples/demo-stf/src/genesis_config.rs +++ b/examples/demo-stf/src/genesis_config.rs @@ -5,8 +5,7 @@ use std::convert::AsRef; use std::path::{Path, PathBuf}; -use anyhow::{bail, Context as AnyhowContext}; -use serde::de::DeserializeOwned; +use anyhow::bail; use sov_accounts::AccountConfig; use sov_bank::BankConfig; use sov_chain_state::ChainStateConfig; @@ -18,6 +17,7 @@ use sov_nft_module::NonFungibleTokenConfig; use sov_rollup_interface::da::DaSpec; use sov_sequencer_registry::SequencerConfig; pub use sov_state::config::Config as StorageConfig; +use sov_stf_runner::read_json_file; use sov_value_setter::ValueSetterConfig; /// Creates config for a rollup with some default settings, the config is used in demos and tests. @@ -141,17 +141,6 @@ fn create_genesis_config>( )) } -fn read_json_file>(path: P) -> anyhow::Result { - let path_str = path.as_ref().display(); - - let data = std::fs::read_to_string(&path) - .with_context(|| format!("Failed to read genesis from {}", path_str))?; - let config: T = serde_json::from_str(&data) - .with_context(|| format!("Failed to parse genesis from {}", path_str))?; - - Ok(config) -} - #[cfg(feature = "experimental")] fn get_evm_config>( evm_path: P, diff --git a/examples/demo-stf/src/hooks_impl.rs b/examples/demo-stf/src/hooks_impl.rs index e41290789..5f16940c0 100644 --- a/examples/demo-stf/src/hooks_impl.rs +++ b/examples/demo-stf/src/hooks_impl.rs @@ -19,6 +19,8 @@ impl TxHooks for Runtime { tx: &Transaction, working_set: &mut WorkingSet, ) -> anyhow::Result<::Address> { + // Before executing a transaction, retrieve the sender's address from the accounts module + // and check the nonce self.accounts.pre_dispatch_tx_hook(tx, working_set) } @@ -27,6 +29,7 @@ impl TxHooks for Runtime { tx: &Transaction, working_set: &mut WorkingSet, ) -> anyhow::Result<()> { + // After executing each transaction, update the nonce self.accounts.post_dispatch_tx_hook(tx, working_set) } } @@ -41,6 +44,7 @@ impl ApplyBlobHooks for Runtime, ) -> anyhow::Result<()> { + // Before executing each batch, check that the sender is registered as a sequencer self.sequencer_registry.begin_blob_hook(blob, working_set) } diff --git a/full-node/sov-stf-runner/Cargo.toml b/full-node/sov-stf-runner/Cargo.toml index 4eb66df57..6f3931ca0 100644 --- a/full-node/sov-stf-runner/Cargo.toml +++ b/full-node/sov-stf-runner/Cargo.toml @@ -14,6 +14,7 @@ publish = false [dependencies] anyhow = { workspace = true } borsh = { workspace = true } +serde_json = { workspace = true } serde = { workspace = true } toml = { workspace = true, optional = true } jsonrpsee = { workspace = true, features = ["http-client", "server"], optional = true } diff --git a/full-node/sov-stf-runner/src/config.rs b/full-node/sov-stf-runner/src/config.rs index 44e4a63b1..fac7e2e39 100644 --- a/full-node/sov-stf-runner/src/config.rs +++ b/full-node/sov-stf-runner/src/config.rs @@ -48,6 +48,8 @@ pub fn from_toml_path, R: DeserializeOwned>(path: P) -> anyhow::R let mut file = File::open(path)?; file.read_to_string(&mut contents)?; } + tracing::debug!("Config file size: {} bytes", contents.len()); + tracing::trace!("Config file contents: {}", &contents); let result: R = toml::from_str(&contents)?; diff --git a/full-node/sov-stf-runner/src/lib.rs b/full-node/sov-stf-runner/src/lib.rs index 40543c7e3..d40cb39b1 100644 --- a/full-node/sov-stf-runner/src/lib.rs +++ b/full-node/sov-stf-runner/src/lib.rs @@ -4,6 +4,11 @@ #[cfg(feature = "native")] mod config; +#[cfg(feature = "native")] +use std::path::Path; + +#[cfg(feature = "native")] +use anyhow::Context; use borsh::{BorshDeserialize, BorshSerialize}; #[cfg(feature = "native")] pub use config::RpcConfig; @@ -17,6 +22,8 @@ pub use config::{from_toml_path, RollupConfig, RunnerConfig, StorageConfig}; pub use ledger_rpc::get_ledger_rpc; #[cfg(feature = "native")] pub use runner::*; +#[cfg(feature = "native")] +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use sov_rollup_interface::da::DaSpec; use sov_rollup_interface::stf::StateTransitionFunction; @@ -47,3 +54,16 @@ where /// The witness for the state transition pub state_transition_witness: ST::Witness, } + +#[cfg(feature = "native")] +/// Reads json file. +pub fn read_json_file>(path: P) -> anyhow::Result { + let path_str = path.as_ref().display(); + + let data = std::fs::read_to_string(&path) + .with_context(|| format!("Failed to read genesis from {}", path_str))?; + let config: T = serde_json::from_str(&data) + .with_context(|| format!("Failed to parse genesis from {}", path_str))?; + + Ok(config) +} diff --git a/rollup-interface/src/state_machine/mocks/da.rs b/rollup-interface/src/state_machine/mocks/da.rs index bc53b88df..df492f220 100644 --- a/rollup-interface/src/state_machine/mocks/da.rs +++ b/rollup-interface/src/state_machine/mocks/da.rs @@ -373,7 +373,10 @@ impl DaService for MockDaService { /// The configuration for mock da #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct MockDaConfig {} +pub struct MockDaConfig { + /// The address to use to "submit" blobs on the mock da layer + pub sender_address: MockAddress, +} #[derive(Clone, Default)] /// DaVerifier used in tests. diff --git a/templates/rollup-template/Cargo.toml b/templates/rollup-template/Cargo.toml new file mode 100644 index 000000000..8f7746654 --- /dev/null +++ b/templates/rollup-template/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "rollup-template" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +homepage.workspace = true +publish.workspace = true +repository.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +#stf dependencies +sov-modules-api = { path = "../../module-system/sov-modules-api" } # needs to re-export anyhow, serde, and borsh. native needs clap, serde_json, and jsonrpsee. Also, tracing +sov-state = { path = "../../module-system/sov-state" } +sov-accounts = { path = "../../module-system/module-implementations/sov-accounts" } +sov-bank = { path = "../../module-system/module-implementations/sov-bank" } +sov-sequencer-registry = { path = "../../module-system/module-implementations/sov-sequencer-registry" } +sov-modules-stf-template = { path = "../../module-system/sov-modules-stf-template/" } +sov-stf-runner = { path = "../../full-node/sov-stf-runner" } +borsh = { workspace = true } +clap = { workspace = true, optional = true } +serde = { workspace = true } +tracing = { workspace = true } +serde_json = { workspace = true, optional = true } +jsonrpsee = { workspace = true, optional = true } +anyhow = { workspace = true } + +sov-db = { path = "../../full-node/db/sov-db", optional = true } +sov-sequencer = { path = "../../full-node/sov-sequencer", optional = true } +sov-rollup-interface = { path = "../../rollup-interface", features = ["mocks"] } +tokio = { workspace = true, optional = true } + +# Change dependencies here to use a different DA layer or ZKVM +sov-risc0-adapter = { path = "../../adapters/risc0" } + +# binary dependencies +tracing-subscriber = { version = "0.3.17", features = ["env-filter"], optional = true } + +[dev-dependencies] +tempfile = { workspace = true } + +[features] +default = ["native"] +native = [ + "sov-modules-api/native", + "sov-accounts/native", + "sov-bank/native", + "sov-sequencer-registry/native", + "sov-risc0-adapter/native", + "sov-stf-runner/native", + "sov-modules-stf-template/native", + "sov-db", + "sov-sequencer", + "tokio", + "clap", + "serde_json", + "jsonrpsee", + "tracing-subscriber", +] + +[[bin]] +name = "node" +path = "src/bin/node.rs" +required-features = ["native"] + +[[bin]] +name = "cli_wallet" +path = "src/bin/cli_wallet.rs" +required-features = ["native"] diff --git a/templates/rollup-template/README.md b/templates/rollup-template/README.md new file mode 100644 index 000000000..c9041e7ba --- /dev/null +++ b/templates/rollup-template/README.md @@ -0,0 +1,3 @@ +# Rollup Template + +This package is a convenient starting point for building a rollup using the Sovereign SDK diff --git a/templates/rollup-template/rollup_config.toml b/templates/rollup-template/rollup_config.toml new file mode 100644 index 000000000..94fe44868 --- /dev/null +++ b/templates/rollup-template/rollup_config.toml @@ -0,0 +1,16 @@ +[da] +sender_address = "0101010101010101010101010101010101010101010101010101010101010101" + +[storage] +# The path to the rollup's data directory. Paths that do not begin with `/` are interpreted as relative paths. +path = "demo_data" + +# We define the rollup's genesis to occur at block number `start_height`. The rollup will ignore +# any blocks before this height +[runner] +start_height = 1 + +[runner.rpc_config] +# the host and port to bind the rpc server for +bind_host = "127.0.0.1" +bind_port = 12345 diff --git a/templates/rollup-template/src/bin/cli_wallet.rs b/templates/rollup-template/src/bin/cli_wallet.rs new file mode 100644 index 000000000..2f0f7fb22 --- /dev/null +++ b/templates/rollup-template/src/bin/cli_wallet.rs @@ -0,0 +1,3 @@ +//! This binary defines a cli wallet for interacting +//! with the rollup. +fn main() {} diff --git a/templates/rollup-template/src/bin/node.rs b/templates/rollup-template/src/bin/node.rs new file mode 100644 index 000000000..37accb33c --- /dev/null +++ b/templates/rollup-template/src/bin/node.rs @@ -0,0 +1,53 @@ +//! This binary runs the rollup full node. + +use std::env; +use std::path::PathBuf; + +use anyhow::Context; +use rollup_template::da::{start_da_service, DaConfig}; +use rollup_template::rollup::Rollup; +use rollup_template::stf::{get_genesis_config, GenesisPaths}; +use sov_risc0_adapter::host::Risc0Host; +use sov_rollup_interface::mocks::{MockAddress, MOCK_SEQUENCER_DA_ADDRESS}; +use sov_stf_runner::{from_toml_path, RollupConfig}; +use tracing::info; +use tracing_subscriber::filter::LevelFilter; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + // Initialize a logger for the demo + let subscriber = tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) // If no logging config is set. default to `info` level logs + .from_env_lossy(), // Parse the log level from the RUST_LOG env var if set + ) // Try to override logging config from RUST_LOG env var + .finish(); + tracing::subscriber::set_global_default(subscriber) + .context("Unable to set global default subscriber")?; + + // Read the rollup config from a file + let rollup_config_path = env::args() + .nth(1) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("rollup_config.toml")); + info!("Reading rollup config from {rollup_config_path:?}"); + + let rollup_config: RollupConfig = + from_toml_path(rollup_config_path).context("Failed to read rollup configuration")?; + info!("Initializing DA service"); + let da_service = start_da_service(&rollup_config).await; + + let sequencer_da_address = MockAddress::from(MOCK_SEQUENCER_DA_ADDRESS); + let genesis_paths = GenesisPaths::from_dir("../../test-data/genesis/"); + let genesis_config = get_genesis_config(sequencer_da_address, &genesis_paths); + + // Start rollup + let rollup: Rollup = + Rollup::new(da_service, genesis_config, rollup_config, None)?; + + rollup.run().await?; + + Ok(()) +} diff --git a/templates/rollup-template/src/bin/verifier.rs b/templates/rollup-template/src/bin/verifier.rs new file mode 100644 index 000000000..a20683082 --- /dev/null +++ b/templates/rollup-template/src/bin/verifier.rs @@ -0,0 +1,13 @@ +//! This binary implements the verification logic for the rollup. This is the code that runs inside +//! of the zkvm in order to generate proofs for the rollup. +use rollup_template::da::new_da_verifier; +use rollup_template::stf::{zk_stf, RollupVerifier}; +use rollup_template::zkvm::ZkvmGuest; + +fn main() { + let guest = ZkvmGuest::new(); + let mut stf_verifier = RollupVerifier::new(zk_stf(), new_da_verifier()); + stf_verifier + .run_block(guest) + .expect("Prover must be honest"); +} diff --git a/templates/rollup-template/src/da.rs b/templates/rollup-template/src/da.rs new file mode 100644 index 000000000..742a19729 --- /dev/null +++ b/templates/rollup-template/src/da.rs @@ -0,0 +1,32 @@ +//! This crate configures the da layer used by the rollup. To switch to a +//! different DA layer: +//! 1. Switch the `sov_celestia_adapter` dependency in you Cargo.toml to the adapter for your chosen DA layer +//! 2. Update the `rollup_config.toml` to include the required configuration for your chosen DA layer +//! 3. Update the three `pub` declarations in this file for your DA layer +//! +//! Your rollup full node will automatically switch to the new DA layer. + +/// The type alias for the DA layer configuration. Change the contents of this alias if you change DA layers. +#[cfg(feature = "native")] +pub type DaConfig = sov_rollup_interface::mocks::MockDaConfig; +/// The type alias for the DA layer verifier. Change the contents of this alias if you change DA layers. +pub type DaVerifier = MockDaVerifier; +/// The type alias for the DA service. Change the contents of this alias if you change DA layers. +#[cfg(feature = "native")] +pub type DaService = sov_rollup_interface::mocks::MockDaService; + +use sov_rollup_interface::da::DaVerifier as _; +use sov_rollup_interface::mocks::MockDaVerifier; +#[cfg(feature = "native")] +use sov_stf_runner::RollupConfig; + +/// Creates a new instance of the DA Service +#[cfg(feature = "native")] +pub async fn start_da_service(rollup_config: &RollupConfig) -> DaService { + DaService::new(rollup_config.da.sender_address) +} + +/// Creates a new verifier for the rollup's DA. +pub fn new_da_verifier() -> DaVerifier { + DaVerifier::new(()) +} diff --git a/templates/rollup-template/src/lib.rs b/templates/rollup-template/src/lib.rs new file mode 100644 index 000000000..c0be2c381 --- /dev/null +++ b/templates/rollup-template/src/lib.rs @@ -0,0 +1,8 @@ +#![deny(missing_docs)] +#![doc = include_str!("../README.md")] +pub mod da; +pub mod stf; + +#[cfg(feature = "native")] +pub mod rollup; +pub mod zkvm; diff --git a/templates/rollup-template/src/rollup/mod.rs b/templates/rollup-template/src/rollup/mod.rs new file mode 100644 index 000000000..4a277385c --- /dev/null +++ b/templates/rollup-template/src/rollup/mod.rs @@ -0,0 +1,98 @@ +//! Defines the rollup full node implementation, including logic for configuring +//! and starting the rollup node. + +#[cfg(feature = "native")] +mod rpc; + +use serde::de::DeserializeOwned; +use sov_db::ledger_db::LedgerDB; +use sov_modules_api::default_context::{DefaultContext, ZkDefaultContext}; +use sov_modules_stf_template::AppTemplate; +use sov_rollup_interface::services::da::DaService; +use sov_rollup_interface::zk::ZkvmHost; +use sov_stf_runner::{Prover, RollupConfig, RunnerConfig, StateTransitionRunner}; +use tokio::sync::oneshot; + +use self::rpc::{register_ledger, register_sequencer}; +use crate::stf::{get_rpc_methods, GenesisConfig, Runtime, StfWithBuilder}; + +type ZkStf = AppTemplate>; + +/// Dependencies needed to run the rollup. +/// This is duplicated exactly from demo-rollup. Should go to stf-runner crate? +pub struct Rollup { + // Implementation of the STF. + pub(crate) app: StfWithBuilder, + // Data availability service. + pub(crate) da_service: Da, + // Ledger db. + pub(crate) ledger_db: LedgerDB, + // Runner configuration. + pub(crate) runner_config: RunnerConfig, + // Initial rollup configuration. + pub(crate) genesis_config: GenesisConfig, + // Prover for the rollup. + #[allow(clippy::type_complexity)] + pub(crate) prover: Option, Da, Vm>>, +} + +impl + Clone> Rollup { + /// Creates a new rollup instance + #[allow(clippy::type_complexity)] + pub fn new( + da_service: Da, + genesis_config: GenesisConfig, + config: RollupConfig, + prover: Option, Da, Vm>>, + ) -> Result { + let ledger_db = LedgerDB::with_path(&config.storage.path)?; + let app = StfWithBuilder::new(config.storage.clone()); + Ok(Self { + app, + da_service, + ledger_db, + runner_config: config.runner, + genesis_config, + prover, + }) + } + + /// Runs the rollup. + pub async fn run(self) -> Result<(), anyhow::Error> { + self.run_and_report_rpc_port(None).await + } + + /// Runs the rollup. Reports rpc port to the caller using the provided channel. + pub async fn run_and_report_rpc_port( + mut self, + channel: Option>, + ) -> Result<(), anyhow::Error> { + let storage = self.app.get_storage(); + let last_slot_opt = self.ledger_db.get_head_slot()?; + let prev_root = last_slot_opt + .map(|(number, _)| storage.get_root_hash(number.0)) + .transpose()?; + let mut methods = get_rpc_methods::(storage.clone()); + + // register rpc methods + { + register_ledger::(self.ledger_db.clone(), &mut methods)?; + register_sequencer(self.da_service.clone(), &mut self.app, &mut methods)?; + } + + let mut runner = StateTransitionRunner::new( + self.runner_config, + self.da_service, + self.ledger_db, + self.app.stf, + prev_root, + self.genesis_config, + self.prover, + )?; + + runner.start_rpc_server(methods, channel).await; + runner.run_in_process().await?; + + Ok(()) + } +} diff --git a/templates/rollup-template/src/rollup/rpc.rs b/templates/rollup-template/src/rollup/rpc.rs new file mode 100644 index 000000000..135ccd4e2 --- /dev/null +++ b/templates/rollup-template/src/rollup/rpc.rs @@ -0,0 +1,38 @@ +use anyhow::Context; +use sov_db::ledger_db::LedgerDB; +use sov_modules_api::{DaSpec, Zkvm}; +use sov_modules_stf_template::{SequencerOutcome, TxEffect}; +use sov_rollup_interface::services::da::DaService; +use sov_sequencer::get_sequencer_rpc; +use sov_stf_runner::get_ledger_rpc; + +use crate::stf::StfWithBuilder; + +/// register sequencer rpc methods. +pub(crate) fn register_sequencer( + da_service: Da, + app: &mut StfWithBuilder, + methods: &mut jsonrpsee::RpcModule<()>, +) -> Result<(), anyhow::Error> +where + Da: DaService, + Vm: Zkvm, +{ + let batch_builder = app.batch_builder.take().unwrap(); + let sequencer_rpc = get_sequencer_rpc(batch_builder, da_service); + methods + .merge(sequencer_rpc) + .context("Failed to merge Txs RPC modules") +} + +/// register ledger rpc methods. +pub(crate) fn register_ledger( + ledger_db: LedgerDB, + methods: &mut jsonrpsee::RpcModule<()>, +) -> Result<(), anyhow::Error> { + let ledger_rpc = + get_ledger_rpc::::Address>, TxEffect>(ledger_db); + methods + .merge(ledger_rpc) + .context("Failed to merge ledger RPC modules") +} diff --git a/templates/rollup-template/src/stf/builder.rs b/templates/rollup-template/src/stf/builder.rs new file mode 100644 index 000000000..ae1c1729a --- /dev/null +++ b/templates/rollup-template/src/stf/builder.rs @@ -0,0 +1,48 @@ +//! This module implements the batch builder for the rollup. +//! To swap out the batch builder, simply replace the +//! FiFoStrictBatchBuilder in `StfWithBuilder` with a type of your choosing. +use sov_modules_api::default_context::DefaultContext; +#[cfg(feature = "native")] +use sov_modules_api::Spec; +use sov_modules_api::{DaSpec, Zkvm}; +use sov_modules_stf_template::AppTemplate; +#[cfg(feature = "native")] +use sov_sequencer::batch_builder::FiFoStrictBatchBuilder; +#[cfg(feature = "native")] +use sov_state::{ProverStorage, Storage}; + +use super::runtime::Runtime; + +/// The "native" version of the STF and a batch builder +pub(crate) struct StfWithBuilder { + pub stf: AppTemplate>, + pub batch_builder: Option, DefaultContext>>, +} + +#[cfg(feature = "native")] +impl StfWithBuilder { + /// Create a new rollup instance + pub(crate) fn new(storage_config: sov_stf_runner::StorageConfig) -> Self { + let config = sov_state::config::Config { + path: storage_config.path, + }; + + let storage = ProverStorage::with_config(config).expect("Failed to open prover storage"); + let app = AppTemplate::new(storage.clone(), Runtime::default()); + let batch_size_bytes = 1024 * 100; // 100 KB + let batch_builder = FiFoStrictBatchBuilder::new( + batch_size_bytes, + u32::MAX as usize, + Runtime::default(), + storage, + ); + Self { + stf: app, + batch_builder: Some(batch_builder), + } + } + + pub(crate) fn get_storage(&self) -> ::Storage { + self.stf.current_storage.clone() + } +} diff --git a/templates/rollup-template/src/stf/genesis_config.rs b/templates/rollup-template/src/stf/genesis_config.rs new file mode 100644 index 000000000..55b7320d9 --- /dev/null +++ b/templates/rollup-template/src/stf/genesis_config.rs @@ -0,0 +1,83 @@ +use std::path::{Path, PathBuf}; + +use anyhow::bail; +use sov_accounts::AccountConfig; +use sov_bank::BankConfig; +use sov_modules_api::{Context, DaSpec}; +use sov_sequencer_registry::SequencerConfig; +use sov_stf_runner::read_json_file; + +use super::GenesisConfig; + +/// Paths to genesis files. +pub struct GenesisPaths> { + /// Accounts genesis path. + pub accounts_genesis_path: P, + /// Bank genesis path. + pub bank_genesis_path: P, + /// Sequencer Registry genesis path. + pub sequencer_genesis_path: P, +} + +impl GenesisPaths { + /// Creates a new [`GenesisPaths`] from the files contained in the given + /// directory. + /// + /// Take a look at the contents of the `test_data` directory to see the + /// expected files. + pub fn from_dir(dir: impl AsRef) -> Self { + Self { + accounts_genesis_path: dir.as_ref().join("accounts.json"), + bank_genesis_path: dir.as_ref().join("bank.json"), + sequencer_genesis_path: dir.as_ref().join("sequencer_registry.json"), + } + } +} + +// Configure our rollup with a centralized sequencer using the SEQUENCER_DA_ADDRESS +/// address constant. Since the centralize sequencer's address is consensus critical, +/// it has to be hardcoded as a constant, rather than read from the config at runtime. +pub fn get_genesis_config>( + sequencer_da_address: Da::Address, + genesis_paths: &GenesisPaths

, +) -> GenesisConfig { + create_genesis_config(sequencer_da_address, genesis_paths) + .expect("Unable to read genesis configuration") +} + +fn create_genesis_config>( + seq_da_address: Da::Address, + genesis_paths: &GenesisPaths

, +) -> anyhow::Result> { + let accounts_config: AccountConfig = read_json_file(&genesis_paths.accounts_genesis_path)?; + let bank_config: BankConfig = read_json_file(&genesis_paths.bank_genesis_path)?; + + let mut sequencer_registry_config: SequencerConfig = + read_json_file(&genesis_paths.sequencer_genesis_path)?; + + // The `seq_da_address` is overridden with the value from rollup binary. + sequencer_registry_config.seq_da_address = seq_da_address; + + // Sanity check: `token_address` in `sequencer_registry_config` match `token_address` from the bank module. + { + let token_address = &sov_bank::get_genesis_token_address::( + &bank_config.tokens[0].token_name, + bank_config.tokens[0].salt, + ); + + let coins_token_addr = &sequencer_registry_config.coins_to_lock.token_address; + if coins_token_addr != token_address { + bail!( + "Wrong token address in `sequencer_registry_config` expected {} but found {}", + token_address, + coins_token_addr + ) + } + } + + Ok(GenesisConfig::new( + accounts_config, + bank_config, + sequencer_registry_config, + )) +} diff --git a/templates/rollup-template/src/stf/hooks.rs b/templates/rollup-template/src/stf/hooks.rs new file mode 100644 index 000000000..e77d0cdf4 --- /dev/null +++ b/templates/rollup-template/src/stf/hooks.rs @@ -0,0 +1,111 @@ +//! This module implements the various "hooks" that are called by the STF during execution. +//! These hooks can be used to add custom logic at various points in the slot lifecycle: +//! - Before and after each transaction is executed. +//! - At the beginning and end of each batch ("blob") +//! - At the beginning and end of each slot (DA layer block) + +use sov_modules_api::hooks::{ApplyBlobHooks, FinalizeHook, SlotHooks, TxHooks}; +use sov_modules_api::transaction::Transaction; +use sov_modules_api::{AccessoryWorkingSet, BlobReaderTrait, Context, DaSpec, Spec, WorkingSet}; +use sov_modules_stf_template::SequencerOutcome; +use sov_sequencer_registry::SequencerRegistry; +use sov_state::Storage; +use tracing::info; + +use super::runtime::Runtime; + +impl TxHooks for Runtime { + type Context = C; + + fn pre_dispatch_tx_hook( + &self, + tx: &Transaction, + working_set: &mut WorkingSet, + ) -> anyhow::Result<::Address> { + // Before executing a transaction, retrieve the sender's address from the accounts module + // and check the nonce + self.accounts.pre_dispatch_tx_hook(tx, working_set) + } + + fn post_dispatch_tx_hook( + &self, + tx: &Transaction, + working_set: &mut WorkingSet, + ) -> anyhow::Result<()> { + // After executing each transaction, update the nonce + self.accounts.post_dispatch_tx_hook(tx, working_set) + } +} + +impl ApplyBlobHooks for Runtime { + type Context = C; + type BlobResult = + SequencerOutcome<<::BlobTransaction as BlobReaderTrait>::Address>; + + fn begin_blob_hook( + &self, + blob: &mut Da::BlobTransaction, + working_set: &mut WorkingSet, + ) -> anyhow::Result<()> { + // Before executing each batch, check that the sender is regsitered as a sequencer + self.sequencer_registry.begin_blob_hook(blob, working_set) + } + + fn end_blob_hook( + &self, + result: Self::BlobResult, + working_set: &mut WorkingSet, + ) -> anyhow::Result<()> { + // After processing each blob, reward or slash the sequencer if appropriate + match result { + SequencerOutcome::Rewarded(_reward) => { + // TODO: Process reward here or above. + as ApplyBlobHooks>::end_blob_hook( + &self.sequencer_registry, + sov_sequencer_registry::SequencerOutcome::Completed, + working_set, + ) + } + SequencerOutcome::Ignored => Ok(()), + SequencerOutcome::Slashed { + reason, + sequencer_da_address, + } => { + info!("Sequencer {} slashed: {:?}", sequencer_da_address, reason); + as ApplyBlobHooks>::end_blob_hook( + &self.sequencer_registry, + sov_sequencer_registry::SequencerOutcome::Slashed { + sequencer: sequencer_da_address, + }, + working_set, + ) + } + } + } +} + +impl SlotHooks for Runtime { + type Context = C; + + fn begin_slot_hook( + &self, + _slot_header: &Da::BlockHeader, + _validity_condition: &Da::ValidityCondition, + _pre_state_root: &<::Storage as Storage>::Root, + _working_set: &mut sov_modules_api::WorkingSet, + ) { + } + + fn end_slot_hook(&self, _working_set: &mut sov_modules_api::WorkingSet) {} +} + +impl FinalizeHook for Runtime { + type Context = C; + + fn finalize_hook( + &self, + _root_hash: &<::Storage as Storage>::Root, + _accessory_working_set: &mut AccessoryWorkingSet, + ) { + } +} diff --git a/templates/rollup-template/src/stf/mod.rs b/templates/rollup-template/src/stf/mod.rs new file mode 100644 index 000000000..11e55a087 --- /dev/null +++ b/templates/rollup-template/src/stf/mod.rs @@ -0,0 +1,13 @@ +//! The rollup State Transition Function. +#[cfg(feature = "native")] +mod builder; +#[cfg(feature = "native")] +mod genesis_config; +mod hooks; +mod runtime; + +#[cfg(feature = "native")] +pub(crate) use builder::*; +#[cfg(feature = "native")] +pub use genesis_config::*; +pub use runtime::*; diff --git a/templates/rollup-template/src/stf/runtime.rs b/templates/rollup-template/src/stf/runtime.rs new file mode 100644 index 000000000..6b645a783 --- /dev/null +++ b/templates/rollup-template/src/stf/runtime.rs @@ -0,0 +1,112 @@ +#![allow(unused_doc_comments)] +//! This module implements the core logic of the rollup. +//! To add new functionality to your rollup: +//! 1. Add a new module dependency to your `Cargo.toml` file +//! 2. Add the module to the `Runtime` below +//! 3. Update `genesis.json` with any additional data required by your new module +#[cfg(feature = "native")] +pub use sov_accounts::{AccountsRpcImpl, AccountsRpcServer}; +#[cfg(feature = "native")] +pub use sov_bank::{BankRpcImpl, BankRpcServer}; +use sov_modules_api::capabilities::{BlobRefOrOwned, BlobSelector}; +use sov_modules_api::default_context::ZkDefaultContext; +use sov_modules_api::macros::DefaultRuntime; +use sov_modules_api::{Context, DaSpec, DispatchCall, Genesis, MessageCodec, Zkvm}; +use sov_modules_stf_template::AppTemplate; +use sov_rollup_interface::da::DaVerifier; +#[cfg(feature = "native")] +pub use sov_sequencer_registry::{SequencerRegistryRpcImpl, SequencerRegistryRpcServer}; +use sov_state::ZkStorage; +use sov_stf_runner::verifier::StateTransitionVerifier; + +/// The runtime defines the logic of the rollup. +/// +/// At a high level, the rollup node receives serialized "call messages" from the DA layer and executes them as atomic transactions. +/// Upon reception, the message is deserialized and forwarded to an appropriate module. +/// +/// The module-specific logic is implemented by module creators, but all the glue code responsible for message +/// deserialization/forwarding is handled by a rollup `runtime`. +/// +/// In order to define the runtime we need to specify all the modules supported by our rollup (see the `Runtime` struct bellow) +/// +/// The `Runtime` defines: +/// - how the rollup modules are wired up together. +/// - how the state of the rollup is initialized. +/// - how messages are dispatched to appropriate modules. +/// +/// Runtime lifecycle: +/// +/// 1. Initialization: +/// When a rollup is deployed for the first time, it needs to set its genesis state. +/// The `#[derive(Genesis)` macro will generate `Runtime::genesis(config)` method which returns +/// `Storage` with the initialized state. +/// +/// 2. Calls: +/// The `Module` interface defines a `call` method which accepts a module-defined type and triggers the specific `module logic.` +/// In general, the point of a call is to change the module state, but if the call throws an error, +/// no state is updated (the transaction is reverted). +/// +/// `#[derive(MessageCodec)` adds deserialization capabilities to the `Runtime` (by implementing the `decode_call` method). +/// `Runtime::decode_call` accepts a serialized call message and returns a type that implements the `DispatchCall` trait. +/// The `DispatchCall` implementation (derived by a macro) forwards the message to the appropriate module and executes its `call` method. +#[cfg_attr( + feature = "native", + derive(sov_modules_api::macros::CliWallet), + sov_modules_api::macros::expose_rpc +)] +#[derive(Genesis, DispatchCall, MessageCodec, DefaultRuntime)] +#[serialization(borsh::BorshDeserialize, borsh::BorshSerialize)] +#[cfg_attr( + feature = "native", + serialization(serde::Serialize, serde::Deserialize) +)] +pub struct Runtime { + /// The `accounts` module is responsible for managing user accounts and their nonces + pub accounts: sov_accounts::Accounts, + /// The bank module is responsible for minting, transferring, and burning tokens + pub bank: sov_bank::Bank, + /// The sequencer registry module is responsible for authorizing users to sequencer rollup transactions + pub sequencer_registry: sov_sequencer_registry::SequencerRegistry, +} + +impl sov_modules_stf_template::Runtime for Runtime +where + C: Context, + Da: DaSpec, +{ +} + +// Select which blobs will be executed in this slot. In this implementation simply execute all +// available blobs in the order they appeared on the DA layer +impl BlobSelector for Runtime { + type Context = C; + fn get_blobs_for_this_slot<'a, I>( + &self, + current_blobs: I, + _working_set: &mut sov_modules_api::WorkingSet, + ) -> anyhow::Result>> + where + I: IntoIterator, + { + Ok(current_blobs.into_iter().map(BlobRefOrOwned::Ref).collect()) + } +} + +/// Create the zk version of the STF. +pub fn zk_stf( +) -> AppTemplate> { + let storage = ZkStorage::new(); + AppTemplate::new(storage, Runtime::default()) +} + +/// A verifier for the rollup +pub type RollupVerifier = StateTransitionVerifier< + AppTemplate< + ZkDefaultContext, + ::Spec, + Zk, + Runtime::Spec>, + >, + DA, + Zk, +>; diff --git a/templates/rollup-template/src/zkvm.rs b/templates/rollup-template/src/zkvm.rs new file mode 100644 index 000000000..34cc6a2be --- /dev/null +++ b/templates/rollup-template/src/zkvm.rs @@ -0,0 +1,13 @@ +//! This module selects the ZKVM to be used to prove the rollup. +//! To change ZKVMs: +//! 1. Switch the `sov_risc0_adapter` dependency in you Cargo.toml to the adapter for your chosen ZKVM +//! 2. Update the two type aliases in this file +use sov_risc0_adapter::guest::Risc0Guest; +#[cfg(feature = "native")] +use sov_risc0_adapter::host::Risc0Host; + +/// The type alias for the host ("prover"). +#[cfg(feature = "native")] +pub type ZkvmHost = Risc0Host<'static>; +/// The type alias for the guest ("verifier"). +pub type ZkvmGuest = Risc0Guest; diff --git a/templates/rollup-template/test-data/genesis/accounts.json b/templates/rollup-template/test-data/genesis/accounts.json new file mode 100644 index 000000000..3a134d04d --- /dev/null +++ b/templates/rollup-template/test-data/genesis/accounts.json @@ -0,0 +1,3 @@ +{ + "pub_keys":[] +} \ No newline at end of file diff --git a/templates/rollup-template/test-data/genesis/bank.json b/templates/rollup-template/test-data/genesis/bank.json new file mode 100644 index 000000000..35b065659 --- /dev/null +++ b/templates/rollup-template/test-data/genesis/bank.json @@ -0,0 +1,10 @@ +{ + "tokens":[ + { + "token_name":"sov-demo-token", + "address_and_balances":[["sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94",100000000]], + "authorized_minters":["sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94"] + ,"salt":0 + } + ] +} \ No newline at end of file diff --git a/templates/rollup-template/test-data/genesis/sequencer_registry.json b/templates/rollup-template/test-data/genesis/sequencer_registry.json new file mode 100644 index 000000000..fdee012ec --- /dev/null +++ b/templates/rollup-template/test-data/genesis/sequencer_registry.json @@ -0,0 +1,9 @@ +{ + "seq_rollup_address":"sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94", + "seq_da_address":"0000000000000000000000000000000000000000000000000000000000000000", + "coins_to_lock":{ + "amount":50, + "token_address":"sov1zsnx7n2wjvtkr0ttscfgt06pjca3v2e6stxeu49qwynavmk7a8xqlxkkjp" + }, + "is_preferred_sequencer":true +} \ No newline at end of file diff --git a/templates/rollup-template/tests/all_tests.rs b/templates/rollup-template/tests/all_tests.rs new file mode 100644 index 000000000..570e72d4a --- /dev/null +++ b/templates/rollup-template/tests/all_tests.rs @@ -0,0 +1,3 @@ +mod bank; +// Add additional tests here +mod test_helpers; diff --git a/templates/rollup-template/tests/bank/mod.rs b/templates/rollup-template/tests/bank/mod.rs new file mode 100644 index 000000000..6e977f959 --- /dev/null +++ b/templates/rollup-template/tests/bank/mod.rs @@ -0,0 +1,84 @@ +use std::net::SocketAddr; + +use borsh::BorshSerialize; +use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; +use jsonrpsee::rpc_params; +use rollup_template::stf::RuntimeCall; +use sov_modules_api::default_context::DefaultContext; +use sov_modules_api::default_signature::private_key::DefaultPrivateKey; +use sov_modules_api::transaction::Transaction; +use sov_modules_api::{PrivateKey, Spec}; +use sov_risc0_adapter::host::Risc0Host; +use sov_rollup_interface::mocks::MockDaSpec; +use sov_sequencer::utils::SimpleClient; + +use super::test_helpers::start_rollup; +const TOKEN_SALT: u64 = 0; +const TOKEN_NAME: &str = "test_token"; + +async fn send_test_create_token_tx(rpc_address: SocketAddr) -> Result<(), anyhow::Error> { + let key = DefaultPrivateKey::generate(); + let user_address: ::Address = key.to_address(); + + let token_address = sov_bank::get_token_address::( + TOKEN_NAME, + user_address.as_ref(), + TOKEN_SALT, + ); + + let msg = RuntimeCall::::bank(sov_bank::CallMessage::< + DefaultContext, + >::CreateToken { + salt: TOKEN_SALT, + token_name: TOKEN_NAME.to_string(), + initial_balance: 1000, + minter_address: user_address, + authorized_minters: vec![], + }); + let tx = Transaction::::new_signed_tx(&key, msg.try_to_vec().unwrap(), 0); + + let port = rpc_address.port(); + let client = SimpleClient::new("localhost", port).await?; + + let mut slot_processed_subscription: Subscription = client + .ws() + .subscribe( + "ledger_subscribeSlots", + rpc_params![], + "ledger_unsubscribeSlots", + ) + .await?; + + client.send_transaction(tx).await?; + + // Wait until the rollup has processed the next slot + let _ = slot_processed_subscription.next().await; + + let balance_response = sov_bank::BankRpcClient::::balance_of( + client.http(), + user_address, + token_address, + ) + .await?; + assert_eq!(balance_response.amount.unwrap_or_default(), 1000); + Ok(()) +} + +#[tokio::test] +async fn bank_tx_tests() -> Result<(), anyhow::Error> { + let (port_tx, port_rx) = tokio::sync::oneshot::channel(); + + let rollup_task = tokio::spawn(async { + start_rollup::>(port_tx).await; + }); + + // Wait for rollup task to start: + let port = port_rx.await.unwrap(); + + // If the rollup throws an error, return it and stop trying to send the transaction + tokio::select! { + err = rollup_task => err?, + res = send_test_create_token_tx(port) => res?, + }; + Ok(()) +} diff --git a/templates/rollup-template/tests/test_genesis.json b/templates/rollup-template/tests/test_genesis.json new file mode 100644 index 000000000..38807789b --- /dev/null +++ b/templates/rollup-template/tests/test_genesis.json @@ -0,0 +1,31 @@ +{ + "accounts": { + "pub_keys": [] + }, + "bank": { + "tokens": [ + { + "token_name": "sov-demo-token", + "address_and_balances": [ + [ + "sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94", + 100000000 + ] + ], + "authorized_minters": [ + "sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94" + ], + "salt": 0 + } + ] + }, + "sequencer_registry": { + "seq_rollup_address": "sov1l6n2cku82yfqld30lanm2nfw43n2auc8clw7r5u5m6s7p8jrm4zqrr8r94", + "seq_da_address": "0101010101010101010101010101010101010101010101010101010101010101", + "coins_to_lock": { + "token_address": "sov1zsnx7n2wjvtkr0ttscfgt06pjca3v2e6stxeu49qwynavmk7a8xqlxkkjp", + "amount": 100000000 + }, + "is_preferred_sequencer": false + } +} diff --git a/templates/rollup-template/tests/test_helpers.rs b/templates/rollup-template/tests/test_helpers.rs new file mode 100644 index 000000000..05b62a9f1 --- /dev/null +++ b/templates/rollup-template/tests/test_helpers.rs @@ -0,0 +1,50 @@ +use std::net::SocketAddr; + +use rollup_template::rollup::Rollup; +use rollup_template::stf::GenesisConfig; +use sov_modules_api::default_context::DefaultContext; +use sov_rollup_interface::mocks::{MockDaConfig, MockDaService, MockDaSpec}; +use sov_rollup_interface::zk::ZkvmHost; +use sov_stf_runner::{RollupConfig, RpcConfig, RunnerConfig, StorageConfig}; +use tokio::sync::oneshot; + +pub async fn start_rollup(rpc_reporting_channel: oneshot::Sender) { + let temp_dir = tempfile::tempdir().unwrap(); + let temp_path = temp_dir.path(); + let genesis_config = serde_json::from_str::>( + include_str!("test_genesis.json"), + ) + .expect("Test genesis configuration must be valid"); + + let rollup_config = RollupConfig { + storage: StorageConfig { + path: temp_path.to_path_buf(), + }, + runner: RunnerConfig { + start_height: 0, + rpc_config: RpcConfig { + bind_host: "127.0.0.1".into(), + bind_port: 0, + }, + }, + da: MockDaConfig { + sender_address: genesis_config.sequencer_registry.seq_da_address, + }, + }; + + let rollup = Rollup::::new( + MockDaService::new(genesis_config.sequencer_registry.seq_da_address), + genesis_config, + rollup_config, + None, + ) + .unwrap(); + + rollup + .run_and_report_rpc_port(Some(rpc_reporting_channel)) + .await + .unwrap(); + + // Close the tempdir explicitly to ensure that rustc doesn't see that it's unused and drop it unexpectedly + temp_dir.close().unwrap(); +}