diff --git a/.github/workflows/rust-build-test.yml b/.github/workflows/rust-build-test.yml index c3ebac95f0..14945712bd 100644 --- a/.github/workflows/rust-build-test.yml +++ b/.github/workflows/rust-build-test.yml @@ -34,7 +34,7 @@ jobs: - name: Build the project run: | - cargo build --release --workspace + cargo build --release --features madara-runtime --workspace - name: Run integration tests - run: cargo test --release + run: cargo test --features madara-runtime --release diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8512f8f6..fccdd408a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,14 @@ madara node - feat(cache-option): add an option to enable aggressive caching in command-line parameters +- feat: Added a `madara-hotstuff-runtime` runtime that replaces the Grandpa + pallet with the HotStuff pallet +- feat: Added two features, `madara-hotstuff-runtime` and `madara-runtime`, + which are used to select between HotStuff and Grandpa consensus at + compile-time. Running `cargo build --features madara-hotstuff-runtime` will + use HotStuff consensus to replace Grandpa consensus, while using + `cargo build --features madara-runtime` will use the original runtime with + Grandpa consensus ## v0.4.0 diff --git a/Cargo.lock b/Cargo.lock index fee509d4a8..0e35acb503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -729,6 +729,17 @@ dependencies = [ "pin-project-lite 0.2.13", ] +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "async-trait" version = "0.1.74" @@ -4817,6 +4828,61 @@ dependencies = [ "winapi", ] +[[package]] +name = "hotstuff-consensus" +version = "0.1.0" +source = "git+https://github.com/Generative-Labs/Substrate-HotStuff.git#9a7d31a5a0a361b747e405e29291464c6efe9f69" +dependencies = [ + "array-bytes 6.2.0", + "async-recursion", + "async-trait", + "futures", + "hotstuff-primitives", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-basic-authorship", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-consensus-slots", + "sc-network", + "sc-network-gossip", + "sc-service", + "sc-telemetry", + "sc-utils", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-blockchain", + "sp-consensus", + "sp-core 21.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-inherents", + "sp-keyring", + "sp-keystore 0.27.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-runtime 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "tokio", +] + +[[package]] +name = "hotstuff-primitives" +version = "0.1.0" +source = "git+https://github.com/Generative-Labs/Substrate-HotStuff.git#9a7d31a5a0a361b747e405e29291464c6efe9f69" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto 23.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-consensus-slots", + "sp-core 21.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-inherents", + "sp-runtime 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-timestamp", +] + [[package]] name = "http" version = "0.2.11" @@ -6391,8 +6457,11 @@ dependencies = [ "frame-system", "futures", "hex", + "hotstuff-consensus", + "hotstuff-primitives", "jsonrpsee 0.16.3", "log", + "madara-hotstuff-runtime", "madara-runtime", "mc-commitment-state-diff", "mc-data-availability", @@ -6449,6 +6518,47 @@ dependencies = [ "url", ] +[[package]] +name = "madara-hotstuff-runtime" +version = "0.5.0" +dependencies = [ + "blockifier", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hotstuff-primitives", + "mp-chain-id", + "mp-felt", + "mp-hashers", + "mp-transactions", + "pallet-aura", + "pallet-hotstuff", + "pallet-starknet", + "pallet-starknet-runtime-api", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core 21.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-inherents", + "sp-offchain", + "sp-runtime 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-session", + "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-transaction-pool", + "sp-version", + "starknet-ff", + "starknet_api", + "substrate-wasm-builder", +] + [[package]] name = "madara-runtime" version = "0.5.0" @@ -7768,6 +7878,23 @@ dependencies = [ "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", ] +[[package]] +name = "pallet-hotstuff" +version = "0.1.0" +source = "git+https://github.com/Generative-Labs/Substrate-HotStuff.git#9a7d31a5a0a361b747e405e29291464c6efe9f69" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hotstuff-primitives", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-runtime 24.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", + "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.3.0)", +] + [[package]] name = "pallet-session" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index a0c577f169..5b53f9d76e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ resolver = "2" members = [ "crates/node", - "crates/runtime", + "crates/runtime/madara", + "crates/runtime/madara-hotstuff", "crates/pallets/starknet/runtime_api/", "crates/pallets/starknet", "crates/primitives/digest-log", @@ -28,7 +29,8 @@ members = [ # We don't want `cargo test` to trigger its tests default-members = [ "crates/node", - "crates/runtime", + "crates/runtime/madara", + "crates/runtime/madara-hotstuff", "crates/pallets/starknet/runtime_api/", "crates/pallets/starknet", "crates/primitives/digest-log", @@ -173,7 +175,8 @@ mc-data-availability = { path = "crates/client/data-availability" } mc-commitment-state-diff = { path = "crates/client/commitment-state-diff" } # Madara runtime -madara-runtime = { path = "crates/runtime" } +madara-runtime = { path = "crates/runtime/madara" } +madara-hotstuff-runtime = { path = "crates/runtime/madara-hotstuff" } # Starknet dependencies # Cairo Virtual Machine @@ -203,6 +206,11 @@ cairo-lang-casm-contract-class = { git = "https://github.com/keep-starknet-stran cairo-lang-casm = { git = "https://github.com/keep-starknet-strange/cairo.git", branch = "no_std-support-8bbf530", default-features = false } cairo-lang-utils = { git = "https://github.com/keep-starknet-strange/cairo.git", branch = "no_std-support-8bbf530", default-features = false } +# Hotstuff consensus +hotstuff-consensus = { git = "https://github.com/Generative-Labs/Substrate-HotStuff.git" } +hotstuff-primitives = { git = "https://github.com/Generative-Labs/Substrate-HotStuff.git", default-features = false } +pallet-hotstuff = { git = "https://github.com/Generative-Labs/Substrate-HotStuff.git", default-features = false } + # Other third party dependencies anyhow = "1.0.75" flate2 = "1.0.28" diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 7593440aac..c4eda66c79 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -78,6 +78,7 @@ frame-benchmarking-cli = { workspace = true } # Starknet blockifier = { workspace = true } hex = { workspace = true } +madara-hotstuff-runtime = { workspace = true } madara-runtime = { workspace = true } mc-commitment-state-diff = { workspace = true } mc-data-availability = { workspace = true } @@ -102,6 +103,10 @@ reqwest = { workspace = true } serde_json = { workspace = true } url = { workspace = true } +# Hotstuff dependencies +hotstuff-consensus = { workspace = true } +hotstuff-primitives = { workspace = true } + [build-dependencies] substrate-build-script-utils = { workspace = true } @@ -120,3 +125,5 @@ disable-transaction-fee = ["madara-runtime/disable-transaction-fee"] # Load sharingan chain-specs during the compilation # This is the way to run a sharingan chain sharingan = [] + +with-hotstuff-runtime = [] diff --git a/crates/node/src/benchmarking.rs b/crates/node/src/benchmarking.rs index c7dd637c10..9cd475078a 100644 --- a/crates/node/src/benchmarking.rs +++ b/crates/node/src/benchmarking.rs @@ -23,6 +23,9 @@ use std::sync::Arc; use std::time::Duration; +#[cfg(feature = "with-hotstuff-runtime")] +use madara_hotstuff_runtime as runtime; +#[cfg(not(feature = "with-hotstuff-runtime"))] use madara_runtime as runtime; use runtime::SystemCall; use sc_cli::Result; diff --git a/crates/node/src/chain_spec.rs b/crates/node/src/chain_spec.rs index 95171c8f8f..d974f37009 100644 --- a/crates/node/src/chain_spec.rs +++ b/crates/node/src/chain_spec.rs @@ -1,5 +1,11 @@ use std::path::PathBuf; +use hotstuff_primitives::AuthorityId as HotStuffId; +#[cfg(feature = "with-hotstuff-runtime")] +use madara_hotstuff_runtime::{ + AuraConfig, HotstuffConfig, RuntimeGenesisConfig, SealingMode, SystemConfig, WASM_BINARY, +}; +#[cfg(not(feature = "with-hotstuff-runtime"))] use madara_runtime::{AuraConfig, GrandpaConfig, RuntimeGenesisConfig, SealingMode, SystemConfig, WASM_BINARY}; use mp_felt::Felt252Wrapper; use pallet_starknet::genesis_loader::{GenesisData, GenesisLoader, HexFelt}; @@ -39,7 +45,10 @@ pub struct DevGenesisExt { impl sp_runtime::BuildStorage for DevGenesisExt { fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { BasicExternalities::execute_with_storage(storage, || { + #[cfg(feature = "madara-runtime")] madara_runtime::Sealing::set(&self.sealing); + #[cfg(feature = "madara-hotstuff-runtime")] + madara_hotstuff_runtime::Sealing::set(&self.sealing); }); self.genesis_config.assimilate_storage(storage) } @@ -51,8 +60,8 @@ pub fn get_from_seed(seed: &str) -> ::Pu } /// Generate an Aura authority key. -pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { - (get_from_seed::(s), get_from_seed::(s)) +pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId, HotStuffId) { + (get_from_seed::(s), get_from_seed::(s), get_from_seed::(s)) } pub fn development_config(sealing: SealingMode, base_path: BasePath) -> Result { @@ -165,7 +174,7 @@ fn load_genesis(data_path: PathBuf) -> GenesisLoader { fn testnet_genesis( genesis_loader: GenesisLoader, wasm_binary: &[u8], - initial_authorities: Vec<(AuraId, GrandpaId)>, + initial_authorities: Vec<(AuraId, GrandpaId, HotStuffId)>, _enable_println: bool, ) -> RuntimeGenesisConfig { let starknet_genesis_config: madara_runtime::pallet_starknet::GenesisConfig<_> = genesis_loader.into(); @@ -179,11 +188,14 @@ fn testnet_genesis( // Authority-based consensus protocol used for block production aura: AuraConfig { authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect() }, // Deterministic finality mechanism used for block finalization + #[cfg(not(feature = "with-hotstuff-runtime"))] grandpa: GrandpaConfig { authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), _config: Default::default(), }, /// Starknet Genesis configuration. starknet: starknet_genesis_config, + #[cfg(feature = "with-hotstuff-runtime")] + hotstuff: HotstuffConfig { authorities: initial_authorities.iter().map(|x| (x.2.clone())).collect() }, } } diff --git a/crates/node/src/command.rs b/crates/node/src/command.rs index 900189844c..809eef0ab4 100644 --- a/crates/node/src/command.rs +++ b/crates/node/src/command.rs @@ -1,4 +1,7 @@ use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; +#[cfg(feature = "with-hotstuff-runtime")] +use madara_hotstuff_runtime::Block; +#[cfg(not(feature = "with-hotstuff-runtime"))] use madara_runtime::Block; use sc_cli::{ChainSpec, SubstrateCli}; diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index f4549ceba7..e8ccf9f6b5 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -1,5 +1,8 @@ use std::path::PathBuf; +#[cfg(feature = "with-hotstuff-runtime")] +use madara_hotstuff_runtime::SealingMode; +#[cfg(not(feature = "with-hotstuff-runtime"))] use madara_runtime::SealingMode; use mc_data_availability::DaLayer; use sc_cli::{Result, RpcMethods, RunCmd, SubstrateCli}; @@ -86,6 +89,7 @@ pub fn run_node(mut cli: Cli) -> Result<()> { None } }; + runner.run_node_until_exit(|config| async move { let sealing = cli.run.sealing.map(Into::into).unwrap_or_default(); let cache = cli.run.cache; diff --git a/crates/node/src/service.rs b/crates/node/src/service.rs index a863284d08..c8227d3554 100644 --- a/crates/node/src/service.rs +++ b/crates/node/src/service.rs @@ -9,8 +9,12 @@ use futures::channel::mpsc; use futures::future; use futures::future::BoxFuture; use futures::prelude::*; -use madara_runtime::opaque::Block; -use madara_runtime::{self, Hash, RuntimeApi, SealingMode, StarknetHasher}; +#[cfg(feature = "with-hotstuff-runtime")] +use hotstuff_consensus::LinkHalf as HotstuffLinkHalf; +#[cfg(feature = "with-hotstuff-runtime")] +use madara_hotstuff_runtime::{self, opaque::Block, Hash, RuntimeApi, SealingMode, StarknetHasher, SLOT_DURATION}; +#[cfg(not(feature = "with-hotstuff-runtime"))] +use madara_runtime::{self, opaque::Block, Hash, RuntimeApi, SealingMode, StarknetHasher, SLOT_DURATION}; use mc_commitment_state_diff::{log_commitment_state_diff, CommitmentStateDiffWorker}; use mc_data_availability::avail::config::AvailConfig; use mc_data_availability::avail::AvailClient; @@ -29,10 +33,11 @@ use sc_basic_authorship::ProposerFactory; use sc_client_api::{Backend, BlockBackend, BlockchainEvents, HeaderBackend}; use sc_consensus::BasicQueue; use sc_consensus_aura::{SlotProportion, StartAuraParams}; -use sc_consensus_grandpa::{GrandpaBlockImport, SharedVoterState}; +#[cfg(not(feature = "with-hotstuff-runtime"))] +use sc_consensus_grandpa::{LinkHalf as GrandpaLinkHalf, SharedVoterState}; pub use sc_executor::NativeElseWasmExecutor; use sc_service::error::Error as ServiceError; -use sc_service::{new_db_backend, Configuration, TaskManager, WarpSyncParams}; +use sc_service::{new_db_backend, Configuration, TaskManager}; use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker}; use sc_transaction_pool::FullPool; use sc_transaction_pool_api::OffchainTransactionPoolFactory; @@ -47,6 +52,25 @@ use crate::starknet::{db_config_dir, MadaraBackend}; // Our native executor instance. pub struct ExecutorDispatch; +#[cfg(feature = "with-hotstuff-runtime")] +impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); + + fn dispatch(method: &str, data: &[u8]) -> Option> { + madara_hotstuff_runtime::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + madara_hotstuff_runtime::native_version() + } +} + +#[cfg(not(feature = "with-hotstuff-runtime"))] impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { /// Only enable the benchmarking host functions when we actually want to benchmark. #[cfg(feature = "runtime-benchmarks")] @@ -73,8 +97,19 @@ type BoxBlockImport = sc_consensus::BoxBlockImport; /// The minimum period of blocks on which justifications will be /// imported and generated. +#[cfg(not(feature = "with-hotstuff-runtime"))] const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; +// Link half used in different consensus. +pub enum ConsensusLinkHalf { + #[cfg(not(feature = "with-hotstuff-runtime"))] + Link(GrandpaLinkHalf), + #[cfg(feature = "with-hotstuff-runtime")] + Link(HotstuffLinkHalf), + + None, +} + #[allow(clippy::type_complexity)] pub fn new_partial( config: &Configuration, @@ -87,12 +122,7 @@ pub fn new_partial( FullSelectChain, sc_consensus::DefaultImportQueue, sc_transaction_pool::FullPool, - ( - BoxBlockImport, - sc_consensus_grandpa::LinkHalf, - Option, - Arc, - ), + (BoxBlockImport, ConsensusLinkHalf, Option, Arc), >, ServiceError, > @@ -104,9 +134,9 @@ where &Configuration, &TaskManager, Option, - GrandpaBlockImport, + Arc, Arc, - ) -> Result<(BasicImportQueue, BoxBlockImport), ServiceError>, + ) -> Result<(BasicImportQueue, BoxBlockImport, ConsensusLinkHalf), ServiceError>, { let telemetry = config .telemetry_endpoints @@ -161,22 +191,14 @@ where client.clone(), ); - let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import( - client.clone(), - GRANDPA_JUSTIFICATION_PERIOD, - &client as &Arc<_>, - select_chain.clone(), - telemetry.as_ref().map(|x| x.handle()), - )?; - let madara_backend = Arc::new(MadaraBackend::open(&config.database, &db_config_dir(config), cache_more_things)?); - let (import_queue, block_import) = build_import_queue( + let (import_queue, block_import, link) = build_import_queue( client.clone(), config, &task_manager, telemetry.as_ref().map(|x| x.handle()), - grandpa_block_import, + backend.clone(), madara_backend.clone(), )?; @@ -188,24 +210,36 @@ where keystore_container, select_chain, transaction_pool, - other: (block_import, grandpa_link, telemetry, madara_backend), + other: (block_import, link, telemetry, madara_backend), }) } -/// Build the import queue for the template runtime (aura + grandpa). -pub fn build_aura_grandpa_import_queue( +/// Build the import queue for the template runtime (aura + hotstuff). +pub fn build_common_import_queue( client: Arc, config: &Configuration, task_manager: &TaskManager, telemetry: Option, - grandpa_block_import: GrandpaBlockImport, + #[allow(unused_variables)] backend: Arc, _madara_backend: Arc, -) -> Result<(BasicImportQueue, BoxBlockImport), ServiceError> +) -> Result<(BasicImportQueue, BoxBlockImport, ConsensusLinkHalf), ServiceError> where RuntimeApi: ConstructRuntimeApi, RuntimeApi: Send + Sync + 'static, { - let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + #[cfg(feature = "with-hotstuff-runtime")] + let (block_import, link) = hotstuff_consensus::block_import(client.clone(), &client as &Arc<_>)?; + + #[cfg(not(feature = "with-hotstuff-runtime"))] + let (block_import, link) = sc_consensus_grandpa::block_import( + client.clone(), + GRANDPA_JUSTIFICATION_PERIOD, + &client as &Arc<_>, + sc_consensus::LongestChain::new(backend.clone()), + telemetry.clone(), + )?; + + let slot_duration: hotstuff_primitives::SlotDuration = sc_consensus_aura::slot_duration(&*client)?; let create_inherent_data_providers = move |_, ()| async move { let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); @@ -218,8 +252,8 @@ where let import_queue = sc_consensus_aura::import_queue::(sc_consensus_aura::ImportQueueParams { - block_import: grandpa_block_import.clone(), - justification_import: Some(Box::new(grandpa_block_import.clone())), + block_import: block_import.clone(), + justification_import: Some(Box::new(block_import.clone())), client, create_inherent_data_providers, spawner: &task_manager.spawn_essential_handle(), @@ -230,7 +264,7 @@ where }) .map_err::(Into::into)?; - Ok((import_queue, Box::new(grandpa_block_import))) + Ok((import_queue, Box::new(block_import), ConsensusLinkHalf::Link(link))) } /// Build the import queue for the template runtime (manual seal). @@ -239,9 +273,9 @@ pub fn build_manual_seal_import_queue( config: &Configuration, task_manager: &TaskManager, _telemetry: Option, - _grandpa_block_import: GrandpaBlockImport, + _backend: Arc, _madara_backend: Arc, -) -> Result<(BasicImportQueue, BoxBlockImport), ServiceError> +) -> Result<(BasicImportQueue, BoxBlockImport, ConsensusLinkHalf), ServiceError> where RuntimeApi: ConstructRuntimeApi, RuntimeApi: Send + Sync + 'static, @@ -253,6 +287,7 @@ where config.prometheus_registry(), ), Box::new(client), + ConsensusLinkHalf::None, )) } @@ -268,7 +303,7 @@ pub fn new_full( cache_more_things: bool, ) -> Result { let build_import_queue = - if sealing.is_default() { build_aura_grandpa_import_queue } else { build_manual_seal_import_queue }; + if sealing.is_default() { build_common_import_queue } else { build_manual_seal_import_queue }; let sc_service::PartialComponents { client, @@ -278,25 +313,47 @@ pub fn new_full( keystore_container, select_chain, transaction_pool, - other: (block_import, grandpa_link, mut telemetry, madara_backend), + other: (block_import, link, mut telemetry, madara_backend), } = new_partial(&config, build_import_queue, cache_more_things)?; + #[allow(unused_mut)] let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); + #[cfg(not(feature = "with-hotstuff-runtime"))] let grandpa_protocol_name = sc_consensus_grandpa::protocol_standard_name( &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), &config.chain_spec, ); + #[cfg(feature = "with-hotstuff-runtime")] + let hotstuff_protocol_name = hotstuff_consensus::config::standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); + let warp_sync_params = if sealing.is_default() { - net_config - .add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); - let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new( - backend.clone(), - grandpa_link.shared_authority_set().clone(), - Vec::default(), - )); - Some(WarpSyncParams::WithProvider(warp_sync)) + #[cfg(not(feature = "with-hotstuff-runtime"))] + if let ConsensusLinkHalf::Link(link) = &link { + net_config.add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config( + grandpa_protocol_name.clone(), + )); + + let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new( + backend.clone(), + link.shared_authority_set().clone(), + Vec::default(), + )); + Some(sc_service::WarpSyncParams::WithProvider(warp_sync)) + } else { + None + } + #[cfg(feature = "with-hotstuff-runtime")] + { + net_config.add_notification_protocol(hotstuff_consensus::config::hotstuff_peers_set_config( + hotstuff_protocol_name.clone(), + )); + None + } } else { None }; @@ -336,6 +393,7 @@ pub fn new_full( let role = config.role.clone(); let force_authoring = config.force_authoring; let backoff_authoring_blocks: Option<()> = None; + #[cfg(not(feature = "with-hotstuff-runtime"))] let name = config.network.node_name.clone(); let enable_grandpa = !config.disable_grandpa && sealing.is_default(); let prometheus_registry = config.prometheus_registry().cloned(); @@ -532,47 +590,67 @@ pub fn new_full( } if enable_grandpa { - // if the node isn't actively participating in consensus then it doesn't - // need a keystore, regardless of which protocol we use below. - let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; - - let grandpa_config = sc_consensus_grandpa::Config { - // FIXME #1578 make this available through chainspec - gossip_duration: Duration::from_millis(333), - justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD, - name: Some(name), - observer_enabled: false, - keystore, - local_role: role, - telemetry: telemetry.as_ref().map(|x| x.handle()), - protocol_name: grandpa_protocol_name, - }; - - // start the full GRANDPA voter - // NOTE: non-authorities could run the GRANDPA observer protocol, but at - // this point the full voter should provide better guarantees of block - // and vote data availability than the observer. The observer has not - // been tested extensively yet and having most nodes in a network run it - // could lead to finality stalls. - let grandpa_config = sc_consensus_grandpa::GrandpaParams { - config: grandpa_config, - link: grandpa_link, - network, - sync: Arc::new(sync_service), - voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(), - prometheus_registry, - shared_voter_state: SharedVoterState::empty(), - telemetry: telemetry.as_ref().map(|x| x.handle()), - offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), - }; + #[cfg(not(feature = "with-hotstuff-runtime"))] + { + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; + + let grandpa_config = sc_consensus_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; - // the GRANDPA voter task is considered infallible, i.e. - // if it fails we take down the service with it. - task_manager.spawn_essential_handle().spawn_blocking( - "grandpa-voter", - None, - sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?, - ); + if let ConsensusLinkHalf::Link(link) = link { + // start the full GRANDPA voter + // NOTE: non-authorities could run the GRANDPA observer protocol, but at + // this point the full voter should provide better guarantees of block + // and vote data availability than the observer. The observer has not + // been tested extensively yet and having most nodes in a network run it + // could lead to finality stalls. + let grandpa_config = sc_consensus_grandpa::GrandpaParams { + config: grandpa_config, + link, + network, + sync: Arc::new(sync_service), + voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(), + prometheus_registry, + shared_voter_state: SharedVoterState::empty(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), + }; + + // the GRANDPA voter task is considered infallible, i.e. + // if it fails we take down the service with it. + task_manager.spawn_essential_handle().spawn_blocking( + "grandpa-voter", + None, + sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?, + ); + } + } + #[cfg(feature = "with-hotstuff-runtime")] + { + if let ConsensusLinkHalf::Link(link) = link { + let (voter, hotstuff_network) = hotstuff_consensus::consensus::start_hotstuff( + network, + link, + Arc::new(sync_service), + hotstuff_protocol_name, + keystore_container.keystore(), + )?; + + task_manager.spawn_essential_handle().spawn_blocking("hotstuff block voter", None, voter); + task_manager.spawn_essential_handle().spawn_blocking("hotstuff network", None, hotstuff_network); + } + } } network_starter.start_network(); @@ -616,7 +694,7 @@ where inherent_data: &mut sp_inherents::InherentData, ) -> Result<(), sp_inherents::Error> { TIMESTAMP.with(|x| { - *x.borrow_mut() += madara_runtime::SLOT_DURATION; + *x.borrow_mut() += SLOT_DURATION; inherent_data.put_data(sp_timestamp::INHERENT_IDENTIFIER, &*x.borrow()) }) } @@ -679,6 +757,6 @@ type ChainOpsResult = pub fn new_chain_ops(config: &mut Configuration, cache_more_things: bool) -> ChainOpsResult { config.keystore = sc_service::config::KeystoreConfig::InMemory; let sc_service::PartialComponents { client, backend, import_queue, task_manager, other, .. } = - new_partial::<_>(config, build_aura_grandpa_import_queue, cache_more_things)?; + new_partial::<_>(config, build_common_import_queue, cache_more_things)?; Ok((client, backend, import_queue, task_manager, other.3)) } diff --git a/crates/node/src/starknet.rs b/crates/node/src/starknet.rs index d419b3174e..b23a0452ab 100644 --- a/crates/node/src/starknet.rs +++ b/crates/node/src/starknet.rs @@ -1,5 +1,8 @@ use std::path::PathBuf; +#[cfg(not(feature = "with-hotstuff-runtime"))] +use madara_hotstuff_runtime::opaque::Block; +#[cfg(feature = "with-hotstuff-runtime")] use madara_runtime::opaque::Block; use sc_service::Configuration; diff --git a/crates/runtime/madara-hotstuff/Cargo.toml b/crates/runtime/madara-hotstuff/Cargo.toml new file mode 100644 index 0000000000..31bea0d1cf --- /dev/null +++ b/crates/runtime/madara-hotstuff/Cargo.toml @@ -0,0 +1,132 @@ +[package] +name = "madara-hotstuff-runtime" +version.workspace = true +edition.workspace = true +description = "Madara runtime with hotstuff consensus." +authors = [ + "Abdelhamid Bakhta <@abdelhamidbakhta>", + "Substrate DevHub ", +] +homepage = "https://github.com/keep-starknet-strange/madara" +license = "MIT" +publish = false +repository = "https://github.com/keep-starknet-strange/madara" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, features = [] } +scale-info = { workspace = true, features = [] } +serde = { workspace = true } + +sp-api = { workspace = true } +sp-block-builder = { workspace = true } +sp-consensus-aura = { workspace = true } +sp-inherents = { workspace = true } +sp-offchain = { workspace = true } +sp-session = { workspace = true } +sp-transaction-pool = { workspace = true } +sp-version = { workspace = true } + +frame-executive = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-try-runtime = { workspace = true, optional = true } +pallet-aura = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Used for the node RPCs +frame-system-rpc-runtime-api = { workspace = true } + +# Used for runtime benchmarking +frame-benchmarking = { workspace = true, optional = true } +frame-system-benchmarking = { workspace = true, optional = true } + +# Madara Local Dependencies +# Madara Pallets +pallet-starknet = { workspace = true } +pallet-starknet-runtime-api = { workspace = true } + +# Madara Primitives +mp-chain-id = { workspace = true } +mp-felt = { workspace = true } +mp-hashers = { workspace = true } +mp-transactions = { workspace = true } +# Starknet dependencies +blockifier = { workspace = true } +starknet-ff = { workspace = true } +starknet_api = { workspace = true } + +# Hotstuff dependencies +hotstuff-primitives = { workspace = true } +pallet-hotstuff = { workspace = true } + +[build-dependencies] +substrate-wasm-builder = { workspace = true } + +[features] +std = [ + # Madara pallets + "pallet-starknet/std", + # Frame dependencies + "frame-try-runtime?/std", + "frame-system-benchmarking?/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-support/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime/std", + # Frame pallets dependencies + "pallet-aura/std", + "pallet-timestamp/std", + # Substrate primitives dependencies + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + # 3rd party dependencies + "parity-scale-codec/std", + "scale-info/std", + "blockifier/std", + "starknet_api/std", + # Hotstuff consensus dependencies + "pallet-hotstuff/std", + "hotstuff-primitives/std", +] +try-runtime = [ + "pallet-timestamp/try-runtime", + "frame-try-runtime/try-runtime", + "frame-executive/try-runtime", + "frame-system/try-runtime", + "frame-support/try-runtime", + "pallet-aura/try-runtime", + # Madara pallets + "pallet-starknet/try-runtime", + # Hotstuff pallet + "pallet-hotstuff/try-runtime", +] +default = ["std"] +madara-state-root = [] +disable-transaction-fee = [] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + # Madara pallets + "pallet-starknet/runtime-benchmarks", +] diff --git a/crates/runtime/build.rs b/crates/runtime/madara-hotstuff/build.rs similarity index 100% rename from crates/runtime/build.rs rename to crates/runtime/madara-hotstuff/build.rs diff --git a/crates/runtime/src/config.rs b/crates/runtime/madara-hotstuff/src/config.rs similarity index 100% rename from crates/runtime/src/config.rs rename to crates/runtime/madara-hotstuff/src/config.rs diff --git a/crates/runtime/madara-hotstuff/src/lib.rs b/crates/runtime/madara-hotstuff/src/lib.rs new file mode 100644 index 0000000000..31c5b55482 --- /dev/null +++ b/crates/runtime/madara-hotstuff/src/lib.rs @@ -0,0 +1,450 @@ +//! L2 validity rollup, settling on Ethereum or as a L3 application-specific rollup, settling on +//! public Starknet L2. +//! For now this is the same because we don't support yet validity proofs and state updates to +//! another layer. +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +/// Runtime modules. +mod config; +pub mod opaque; +mod pallets; +mod runtime_tests; +mod types; + +use blockifier::execution::contract_class::ContractClass; +pub use config::*; +pub use frame_support::traits::{ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo}; +pub use frame_support::weights::constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, +}; +pub use frame_support::weights::{IdentityFee, Weight}; +pub use frame_support::{construct_runtime, parameter_types, StorageValue}; +pub use frame_system::Call as SystemCall; +use frame_system::{EventRecord, Phase}; +use hotstuff_primitives::AuthorityId as HotstuffId; +use mp_felt::Felt252Wrapper; +use mp_transactions::compute_hash::ComputeTransactionHash; +use mp_transactions::{Transaction, UserTransaction}; +// Hotstuff consensus authority id. +pub use pallet_hotstuff; +/// Import the StarkNet pallet. +pub use pallet_starknet; +use pallet_starknet::pallet::Error as PalletError; +use pallet_starknet::Call::{consume_l1_message, declare, deploy_account, invoke}; +use pallet_starknet::{Config, Event}; +use pallet_starknet_runtime_api::StarknetTransactionExecutionError; +pub use pallet_timestamp::Call as TimestampCall; +use sp_api::impl_runtime_apis; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::crypto::KeyTypeId; +use sp_core::OpaqueMetadata; +use sp_runtime::traits::{BlakeTwo256, Block as BlockT}; +use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{generic, ApplyExtrinsicResult, DispatchError}; +pub use sp_runtime::{Perbill, Permill}; +use sp_std::prelude::*; +use sp_version::RuntimeVersion; +use starknet_api::api_core::{ClassHash, ContractAddress, EntryPointSelector, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; +use starknet_api::transaction::{Calldata, Event as StarknetEvent, TransactionHash}; +/// Import the types. +pub use types::*; + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub struct Runtime { + System: frame_system, + Timestamp: pallet_timestamp, + Aura: pallet_aura, + // Include Starknet pallet. + Starknet: pallet_starknet, + // Include hotstuff consensus pallet + Hotstuff: pallet_hotstuff, + } +); + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, +); +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; +/// Executive: handles dispatch to the various modules. +pub type Executive = + frame_executive::Executive, Runtime, AllPalletsWithSystem>; + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_benchmarking, BaselineBench::] + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_timestamp, Timestamp] + ); +} + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + opaque::SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + opaque::SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + // hotstuff runtime api + impl hotstuff_primitives::HotstuffApi for Runtime { + fn slot_duration() -> hotstuff_primitives::SlotDuration { + hotstuff_primitives::SlotDuration::from_millis(Hotstuff::slot_duration()) + } + + fn authorities() -> Vec { + Hotstuff::authorities().into_inner() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl pallet_starknet_runtime_api::StarknetRuntimeApi for Runtime { + + fn get_storage_at(address: ContractAddress, key: StorageKey) -> Result { + Starknet::get_storage_at(address, key) + } + + fn call(address: ContractAddress, function_selector: EntryPointSelector, calldata: Calldata) -> Result, DispatchError> { + Starknet::call_contract(address, function_selector, calldata) + } + + fn nonce(address: ContractAddress) -> Nonce{ + Starknet::nonce(address) + } + + fn contract_class_hash_by_address(address: ContractAddress) -> ClassHash { + Starknet::contract_class_hash_by_address(address) + } + + fn contract_class_by_class_hash(class_hash: ClassHash) -> Option { + Starknet::contract_class_by_class_hash(class_hash) + } + + fn chain_id() -> Felt252Wrapper { + Starknet::chain_id() + } + + fn is_transaction_fee_disabled() -> bool { + Starknet::is_transaction_fee_disabled() + } + + fn estimate_fee(transactions: Vec) -> Result, DispatchError> { + Starknet::estimate_fee(transactions) + } + + fn get_starknet_events_and_their_associated_tx_hash(block_extrinsics: Vec<::Extrinsic>, chain_id: Felt252Wrapper) -> Vec<(Felt252Wrapper, StarknetEvent)> { + System::read_events_no_consensus().filter_map(|event_record| { + let (phase, event) = match *event_record { + EventRecord { event: RuntimeEvent::Starknet(Event::StarknetEvent(event)), phase, .. } => (phase, event), + _ => return None, + }; + + let index = match phase { + Phase::ApplyExtrinsic(idx) => {idx}, + _ => return None + + }; + let extrinsic = &block_extrinsics[index as usize]; + let tx_hash = match &extrinsic.function { + RuntimeCall::Starknet( invoke { transaction }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( declare { transaction, .. }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( deploy_account { transaction }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( consume_l1_message { transaction, .. }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + _ => return None, + }; + + Some((tx_hash, event)) + }).collect() + } + + fn extrinsic_filter(xts: Vec<::Extrinsic>) -> Vec { + xts.into_iter().filter_map(|xt| match xt.function { + RuntimeCall::Starknet( invoke { transaction }) => Some(Transaction::Invoke(transaction)), + RuntimeCall::Starknet( declare { transaction, .. }) => Some(Transaction::Declare(transaction)), + RuntimeCall::Starknet( deploy_account { transaction }) => Some(Transaction::DeployAccount(transaction)), + RuntimeCall::Starknet( consume_l1_message { transaction, .. }) => Some(Transaction::L1Handler(transaction)), + _ => None + }).collect::>() + } + + fn get_index_and_tx_for_tx_hash(extrinsics: Vec<::Extrinsic>, chain_id: Felt252Wrapper, tx_hash: Felt252Wrapper) -> Option<(u32, Transaction)> { + // Find our tx and it's index + let (tx_index, tx) = extrinsics.into_iter().enumerate().find(|(_, xt)| { + let computed_tx_hash = match &xt.function { + RuntimeCall::Starknet( invoke { transaction }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( declare { transaction, .. }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( deploy_account { transaction }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + RuntimeCall::Starknet( consume_l1_message { transaction, .. }) => transaction.compute_hash::<::SystemHash>(chain_id, false), + _ => return false + }; + + computed_tx_hash == tx_hash + })?; + let transaction = match tx.function { + RuntimeCall::Starknet( invoke { transaction }) => Transaction::Invoke(transaction), + RuntimeCall::Starknet( declare { transaction, .. }) => Transaction::Declare(transaction), + RuntimeCall::Starknet( deploy_account { transaction }) => Transaction::DeployAccount(transaction), + RuntimeCall::Starknet( consume_l1_message { transaction, .. }) => Transaction::L1Handler(transaction), + _ => unreachable!("The previous match made sure that at this point tx is one of those starknet calls"), + }; + + let tx_index = u32::try_from(tx_index).expect("unexpected number of transactions"); + Some((tx_index, transaction)) + } + + fn get_events_for_tx_by_index(tx_index: u32) -> Option> { + + // Skip all the events that are not related to our tx + let event_iter = System::read_events_no_consensus().filter_map(|event| { + match *event { + EventRecord { event: RuntimeEvent::Starknet(Event::StarknetEvent(event)), phase, .. } => Some((phase, event)), + _ => None, + } + }).skip_while(|(phase, _)| { + let index = match phase { + Phase::ApplyExtrinsic(idx) => *idx, + _ => return true + }; + + tx_index != index + }); + + // Collect all the events related to our tx + // Event from the same transaction are stored one after another + // so we can use take_while rather and early exit rather than filtering + let events = event_iter.take_while(|(phase, _)| { + let index = match phase { + Phase::ApplyExtrinsic(idx) => *idx, + _ => panic!("The previous iteration made sure at this point phase is of ApplyExtrinsic variant"), + }; + + tx_index == index + }).map(|(_, event)| event).collect(); + + Some(events) + } + + fn get_tx_execution_outcome(tx_hash: TransactionHash) -> Option> { + Starknet::tx_revert_error(tx_hash).map(|s| s.into_bytes()) + } + + fn get_block_context() -> pallet_starknet_runtime_api::BlockContext { + Starknet::get_block_context().into() + } + } + + impl pallet_starknet_runtime_api::ConvertTransactionRuntimeApi for Runtime { + fn convert_transaction(transaction: UserTransaction) -> Result { + let call = match transaction { + UserTransaction::Declare(tx, contract_class) => { + pallet_starknet::Call::declare { transaction: tx, contract_class } + } + UserTransaction::DeployAccount(tx) => { + pallet_starknet::Call::deploy_account { transaction: tx } + } + UserTransaction::Invoke(tx) => { + pallet_starknet::Call::invoke { transaction: tx } + } + }; + + Ok(UncheckedExtrinsic::new_unsigned(call.into())) + } + + fn convert_error(error: DispatchError) -> StarknetTransactionExecutionError { + if error == PalletError::::ContractNotFound.into() { + return StarknetTransactionExecutionError::ContractNotFound; + } + if error == PalletError::::ClassHashAlreadyDeclared.into() { + return StarknetTransactionExecutionError::ClassAlreadyDeclared; + } + if error == PalletError::::ContractClassHashUnknown.into() { + return StarknetTransactionExecutionError::ClassHashNotFound; + } + if error == PalletError::::InvalidContractClass.into() { + return StarknetTransactionExecutionError::InvalidContractClass; + } + + StarknetTransactionExecutionError::ContractError + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch, TrackedStorageKey}; + + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} + + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade() -> (Weight, Weight) { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade().unwrap(); + (weight, BlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, select).expect("execute-block failed") + } + } +} diff --git a/crates/runtime/madara-hotstuff/src/opaque.rs b/crates/runtime/madara-hotstuff/src/opaque.rs new file mode 100644 index 0000000000..ad6dfd820c --- /dev/null +++ b/crates/runtime/madara-hotstuff/src/opaque.rs @@ -0,0 +1,38 @@ +// A few exports that help ease life for downstream crates. +pub use frame_support::traits::{ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo}; +pub use frame_support::weights::constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, +}; +pub use frame_support::weights::{IdentityFee, Weight}; +pub use frame_support::{construct_runtime, parameter_types, StorageValue}; +pub use frame_system::Call as SystemCall; +/// Import the StarkNet pallet. +pub use pallet_starknet; +pub use pallet_timestamp::Call as TimestampCall; +use sp_runtime::traits::BlakeTwo256; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; +use sp_runtime::{generic, impl_opaque_keys}; +pub use sp_runtime::{Perbill, Permill}; +use sp_std::prelude::*; + +use super::*; +use crate::{Aura, BlockNumber, Hotstuff}; +/// Opaque block header type. +pub type Header = generic::Header; +/// Opaque block type. +pub type Block = generic::Block; +/// Opaque block identifier type. +pub type BlockId = generic::BlockId; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + pub hotstuff: Hotstuff, + } +} diff --git a/crates/runtime/madara-hotstuff/src/pallets.rs b/crates/runtime/madara-hotstuff/src/pallets.rs new file mode 100644 index 0000000000..dbff8ffccf --- /dev/null +++ b/crates/runtime/madara-hotstuff/src/pallets.rs @@ -0,0 +1,173 @@ +//! Configuration of the pallets used in the runtime. +//! The pallets used in the runtime are configured here. +//! This file is used to generate the `construct_runtime!` macro. +pub use frame_support::traits::{ + ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, OnTimestampSet, Randomness, StorageInfo, +}; +pub use frame_support::weights::constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, +}; +pub use frame_support::weights::{IdentityFee, Weight}; +pub use frame_support::{construct_runtime, parameter_types, StorageValue}; +pub use frame_system::Call as SystemCall; +use hotstuff_primitives::AuthorityId as HotstuffId; +pub use mp_chain_id::SN_GOERLI_CHAIN_ID; +pub use pallet_hotstuff; +/// Import the StarkNet pallet. +pub use pallet_starknet; +pub use pallet_timestamp::Call as TimestampCall; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_runtime::traits::{AccountIdLookup, BlakeTwo256}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; +use sp_std::marker::PhantomData; + +use crate::*; + +// Configure FRAME pallets to include in runtime. + +// -------------------------------------- +// CUSTOM PALLETS +// -------------------------------------- + +/// Configure the Starknet pallet in pallets/starknet. +impl pallet_starknet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SystemHash = StarknetHasher; + type TimestampProvider = Timestamp; + type UnsignedPriority = UnsignedPriority; + type TransactionLongevity = TransactionLongevity; + #[cfg(not(feature = "disable-transaction-fee"))] + type DisableTransactionFee = ConstBool; + #[cfg(feature = "disable-transaction-fee")] + type DisableTransactionFee = ConstBool; + type DisableNonceValidation = ConstBool; + type InvokeTxMaxNSteps = InvokeTxMaxNSteps; + type ValidateMaxNSteps = ValidateMaxNSteps; + type ProtocolVersion = ProtocolVersion; + type ChainId = ChainId; + type MaxRecursionDepth = MaxRecursionDepth; +} + +/// -------------------------------------- +/// FRAME SYSTEM PALLET +/// -------------------------------------- + +/// Configuration of `frame_system` pallet. +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = BlockLength; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Nonce = Index; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The Block type. + type Block = Block; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Version of the runtime. + type Version = Version; + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = (); + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +// -------------------------------------- +// CONSENSUS RELATED FRAME PALLETS +// -------------------------------------- +// Notes: +// Aura is the consensus algorithm used for block production. +// Hotstuff is the consensus algorithm used for block finalization. +// We want to support multiple flavors of consensus algorithms. +// Specifically we want to implement some proposals defined in the Starknet community forum. +// For more information see: https://community.starknet.io/t/starknet-decentralized-protocol-i-introduction/2671 +// You can also follow this issue on github: https://github.com/keep-starknet-strange/madara/issues/83 + +/// Authority-based consensus protocol used for block production. +/// TODO: Comment and explain the rationale behind the configuration items. +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<32>; + type AllowMultipleBlocksPerSlot = ConstBool; +} + +/// -------------------------------------- +/// OTHER 3RD PARTY FRAME PALLETS +/// -------------------------------------- + +/// Timestamp manipulation. +/// For instance, we need it to set the timestamp of the Starknet block. +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = ConsensusOnTimestampSet; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = (); +} + +/// Hotstuff consensus pallet +impl pallet_hotstuff::Config for Runtime { + type AuthorityId = HotstuffId; + + type DisabledValidators = (); + type MaxAuthorities = ConstU32<32>; + type AllowMultipleBlocksPerSlot = ConstBool; + + type WeightInfo = (); +} + +parameter_types! { + pub const UnsignedPriority: u64 = 1 << 20; + pub const TransactionLongevity: u64 = u64::MAX; + pub const InvokeTxMaxNSteps: u32 = 1_000_000; + pub const ValidateMaxNSteps: u32 = 1_000_000; + pub const ProtocolVersion: u8 = 0; + pub const ChainId: Felt252Wrapper = SN_GOERLI_CHAIN_ID; + pub const MaxRecursionDepth: u32 = 50; +} + +/// Implement the OnTimestampSet trait to override the default Aura. +/// This is needed to suppress Aura validations in case of non-default sealing. +pub struct ConsensusOnTimestampSet(PhantomData); +impl OnTimestampSet for ConsensusOnTimestampSet { + fn on_timestamp_set(moment: T::Moment) { + if Sealing::get() != SealingMode::Default { + return; + } + as OnTimestampSet>::on_timestamp_set(moment) + } +} diff --git a/crates/runtime/src/runtime_tests.rs b/crates/runtime/madara-hotstuff/src/runtime_tests.rs similarity index 100% rename from crates/runtime/src/runtime_tests.rs rename to crates/runtime/madara-hotstuff/src/runtime_tests.rs diff --git a/crates/runtime/src/types.rs b/crates/runtime/madara-hotstuff/src/types.rs similarity index 100% rename from crates/runtime/src/types.rs rename to crates/runtime/madara-hotstuff/src/types.rs diff --git a/crates/runtime/Cargo.toml b/crates/runtime/madara/Cargo.toml similarity index 99% rename from crates/runtime/Cargo.toml rename to crates/runtime/madara/Cargo.toml index 52a6db7375..f2ac61533b 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/madara/Cargo.toml @@ -62,6 +62,7 @@ blockifier = { workspace = true } starknet-ff = { workspace = true } starknet_api = { workspace = true } + [build-dependencies] substrate-wasm-builder = { workspace = true } diff --git a/crates/runtime/madara/build.rs b/crates/runtime/madara/build.rs new file mode 100644 index 0000000000..5327cc8899 --- /dev/null +++ b/crates/runtime/madara/build.rs @@ -0,0 +1,5 @@ +use substrate_wasm_builder::WasmBuilder; + +fn main() { + WasmBuilder::new().with_current_project().export_heap_base().import_memory().build() +} diff --git a/crates/runtime/madara/src/config.rs b/crates/runtime/madara/src/config.rs new file mode 100644 index 0000000000..9e568f326d --- /dev/null +++ b/crates/runtime/madara/src/config.rs @@ -0,0 +1,115 @@ +//! Configuration of the runtime. +pub use frame_support::traits::{ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo}; +pub use frame_support::weights::constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, +}; +pub use frame_support::weights::{IdentityFee, Weight}; +pub use frame_support::{construct_runtime, parameter_types, StorageValue}; +pub use frame_system::Call as SystemCall; +use parity_scale_codec::{Decode, Encode}; +use serde::{Deserialize, Serialize}; +use sp_core::RuntimeDebug; +use sp_runtime::create_runtime_str; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +use crate::{BlockNumber, RUNTIME_API_VERSIONS}; + +/// This determines the average expected block time that we are targeting. +/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. +/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked +/// up by `pallet_aura` to implement `fn slot_duration()`. +/// +/// Change this to adjust the block time. +/// a.k.a `BLOCK_TIME` +pub const MILLISECS_PER_BLOCK: u64 = 6000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// To learn more about runtime versioning, see: +// https://docs.substrate.io/main-docs/build/upgrade#runtime-versioning +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("madara"), + impl_name: create_runtime_str!("madara"), + authoring_version: 1, + // The version of the runtime specification. A full node will not attempt to use its native + // runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, + // `spec_version`, and `authoring_version` are the same between Wasm and native. + // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use + // the compatible custom types. + spec_version: 100, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +// Time is measured by number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +/// Existential deposit. +pub const EXISTENTIAL_DEPOSIT: u128 = 500; + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; + /// We allow for 2 seconds of compute with a 6 second average block time. + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::with_sensible_defaults( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); + pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength + ::max_with_normal_ratio(20 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub const SS58Prefix: u8 = 42; +} + +/// The current sealing mode being used. This is needed for the runtime to adjust its behavior +/// accordingly, e.g. suppress Aura validations in `OnTimestampSet` for manual or instant sealing. +#[derive(Default, Clone, PartialEq, Decode, Encode, RuntimeDebug, Deserialize, Serialize)] +pub enum SealingMode { + #[default] + Default, + Manual, + Instant { + finalize: bool, + }, +} + +impl SealingMode { + pub fn is_default(&self) -> bool { + matches!(self, SealingMode::Default) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for SealingMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SealingMode::Default => write!(f, "Default"), + SealingMode::Manual => write!(f, "Manual"), + SealingMode::Instant { finalize } => write!(f, "Instant (finalize: {})", finalize), + } + } +} + +// This storage item will be used to check if we are in the manual sealing mode +parameter_types! { + pub storage Sealing: SealingMode = SealingMode::default(); +} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/madara/src/lib.rs similarity index 100% rename from crates/runtime/src/lib.rs rename to crates/runtime/madara/src/lib.rs diff --git a/crates/runtime/src/opaque.rs b/crates/runtime/madara/src/opaque.rs similarity index 100% rename from crates/runtime/src/opaque.rs rename to crates/runtime/madara/src/opaque.rs diff --git a/crates/runtime/src/pallets.rs b/crates/runtime/madara/src/pallets.rs similarity index 100% rename from crates/runtime/src/pallets.rs rename to crates/runtime/madara/src/pallets.rs diff --git a/crates/runtime/madara/src/runtime_tests.rs b/crates/runtime/madara/src/runtime_tests.rs new file mode 100644 index 0000000000..97d70dd07a --- /dev/null +++ b/crates/runtime/madara/src/runtime_tests.rs @@ -0,0 +1,27 @@ +//! Tests for the runtime. + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use sp_core::hexdisplay::HexDisplay; + + use crate::sp_api_hidden_includes_construct_runtime::hidden_include::traits::WhitelistedStorageKeys; + use crate::*; + #[test] + fn check_whitelist() { + let whitelist: HashSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|e| HexDisplay::from(&e.key).to_string()) + .collect(); + + // Block Number + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac")); + // Execution Phase + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a")); + // Event Count + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850")); + // System Events + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7")); + } +} diff --git a/crates/runtime/madara/src/types.rs b/crates/runtime/madara/src/types.rs new file mode 100644 index 0000000000..37e6c5f467 --- /dev/null +++ b/crates/runtime/madara/src/types.rs @@ -0,0 +1,31 @@ +//! Common types used in the runtime. +//! This file is the canonical source of truth for the types used in the runtime. + +use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::MultiSignature; + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// The maximum amount of steps allowed for an invoke transaction. (?) +pub type InvokeTxMaxNSteps = u32; +/// The maximum amount of steps allowed for validation. (?) +pub type ValidateMaxNSteps = u32; + +pub type StarknetHasher = mp_hashers::pedersen::PedersenHasher;