From 0464ce074cc1b7ba1c62af132b44472715d01aad Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 19 Dec 2022 22:26:31 +0800 Subject: [PATCH] Support custom genesis block (#12291) * Set genesis block data using the built genesis block * Make resolve_state_version_from_wasm a separate function and some small refactorings Useful for the commit following. * Introduce trait BuildGenesisBlock Substrate users can use this trait to implement their custom genesis block when constructing the client. * Make call_executor test compile * cargo +nightly fmt --all * Fix test * Remove unnecessary clone * FMT * Apply review suggestions * Revert changes to new_full_client() and new_full_parts() signature * Remove needless `Block` type in `resolve_state_version_from_wasm` --- bin/node/testing/src/bench.rs | 30 ++++-- client/service/src/builder.rs | 59 +++++++---- client/service/src/client/call_executor.rs | 33 +++--- client/service/src/client/client.rs | 118 +++++++++++---------- client/service/src/client/genesis.rs | 67 +++++++++++- client/service/src/client/mod.rs | 2 +- client/service/src/config.rs | 17 +++ client/service/src/lib.rs | 5 +- client/service/test/src/client/mod.rs | 19 +++- test-utils/client/src/lib.rs | 24 +++-- 10 files changed, 261 insertions(+), 113 deletions(-) diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 59f1fa94c9b20..2151a61a6ca7a 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -397,24 +397,34 @@ impl BenchDb { let task_executor = TaskExecutor::new(); let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); + let executor = NativeElseWasmExecutor::new( + WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, + None, + 8, + 2, + ); + let client_config = sc_service::ClientConfig::default(); + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &keyring.generate_genesis(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .expect("Failed to create genesis block builder"); + let client = sc_service::new_client( backend.clone(), - NativeElseWasmExecutor::new( - WasmExecutionMethod::Compiled { - instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - }, - None, - 8, - 2, - ), - &keyring.generate_genesis(), + executor, + genesis_block_builder, None, None, ExecutionExtensions::new(profile.into_execution_strategies(), None, None), Box::new(task_executor.clone()), None, None, - Default::default(), + client_config, ) .expect("Should not fail"); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 7153672030d6a..1c8f4c7de717d 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -22,7 +22,8 @@ use crate::{ config::{Configuration, KeystoreConfig, PrometheusConfig}, error::Error, metrics::MetricsService, - start_rpc_servers, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, + start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle, + TaskManager, TransactionPoolAdapter, }; use futures::{channel::oneshot, future::ready, FutureExt, StreamExt}; use jsonrpsee::RpcModule; @@ -72,7 +73,6 @@ use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}, - BuildStorage, }; use std::{str::FromStr, sync::Arc, time::SystemTime}; @@ -182,7 +182,7 @@ where new_full_parts(config, telemetry, executor).map(|parts| parts.0) } -/// Create the initial parts of a full node. +/// Create the initial parts of a full node with the default genesis block builder. pub fn new_full_parts( config: &Configuration, telemetry: Option, @@ -191,6 +191,34 @@ pub fn new_full_parts( where TBl: BlockT, TExec: CodeExecutor + RuntimeVersionOf + Clone, +{ + let backend = new_db_backend(config.db_config())?; + + let genesis_block_builder = GenesisBlockBuilder::new( + config.chain_spec.as_storage_builder(), + !config.no_genesis(), + backend.clone(), + executor.clone(), + )?; + + new_full_parts_with_genesis_builder(config, telemetry, executor, backend, genesis_block_builder) +} + +/// Create the initial parts of a full node. +pub fn new_full_parts_with_genesis_builder( + config: &Configuration, + telemetry: Option, + executor: TExec, + backend: Arc>, + genesis_block_builder: TBuildGenesisBlock, +) -> Result, Error> +where + TBl: BlockT, + TExec: CodeExecutor + RuntimeVersionOf + Clone, + TBuildGenesisBlock: BuildGenesisBlock< + TBl, + BlockImportOperation = as sc_client_api::backend::Backend>::BlockImportOperation + >, { let keystore_container = KeystoreContainer::new(&config.keystore)?; @@ -208,16 +236,7 @@ where .cloned() .unwrap_or_default(); - let (client, backend) = { - let db_config = sc_client_db::DatabaseSettings { - trie_cache_maximum_size: config.trie_cache_maximum_size, - state_pruning: config.state_pruning.clone(), - source: config.database.clone(), - blocks_pruning: config.blocks_pruning, - }; - - let backend = new_db_backend(db_config)?; - + let client = { let extensions = sc_client_api::execution_extensions::ExecutionExtensions::new( config.execution_strategies.clone(), Some(keystore_container.sync_keystore()), @@ -244,7 +263,7 @@ where let client = new_client( backend.clone(), executor, - chain_spec.as_storage_builder(), + genesis_block_builder, fork_blocks, bad_blocks, extensions, @@ -263,7 +282,7 @@ where }, )?; - (client, backend) + client }; Ok((client, backend, keystore_container, task_manager)) @@ -282,10 +301,10 @@ where } /// Create an instance of client backed by given backend. -pub fn new_client( +pub fn new_client( backend: Arc>, executor: E, - genesis_storage: &dyn BuildStorage, + genesis_block_builder: G, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, execution_extensions: ExecutionExtensions, @@ -305,6 +324,10 @@ pub fn new_client( where Block: BlockT, E: CodeExecutor + RuntimeVersionOf, + G: BuildGenesisBlock< + Block, + BlockImportOperation = as sc_client_api::backend::Backend>::BlockImportOperation + >, { let executor = crate::client::LocalCallExecutor::new( backend.clone(), @@ -316,7 +339,7 @@ where crate::client::Client::new( backend, executor, - genesis_storage, + genesis_block_builder, fork_blocks, bad_blocks, prometheus_registry, diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index fcece49b5f228..bf7e8b3a6555e 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -377,24 +377,27 @@ mod tests { // LocalCallExecutor directly later on let client_config = ClientConfig::default(); - // client is used for the convenience of creating and inserting the genesis block. - let _client = substrate_test_runtime_client::client::new_with_backend::< - _, - _, - runtime::Block, - _, - runtime::RuntimeApi, - >( + let genesis_block_builder = crate::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, backend.clone(), executor.clone(), - &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), - None, - Box::new(TaskExecutor::new()), - None, - None, - Default::default(), ) - .expect("Creates a client"); + .expect("Creates genesis block builder"); + + // client is used for the convenience of creating and inserting the genesis block. + let _client = + crate::client::new_with_backend::<_, _, runtime::Block, _, runtime::RuntimeApi>( + backend.clone(), + executor.clone(), + genesis_block_builder, + None, + Box::new(TaskExecutor::new()), + None, + None, + Default::default(), + ) + .expect("Creates a client"); let call_executor = LocalCallExecutor { backend: backend.clone(), diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 8ded5ec95c166..f81586d424799 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -20,7 +20,7 @@ use super::{ block_rules::{BlockRules, LookupResult as BlockLookupResult}, - genesis, + genesis::BuildGenesisBlock, }; use log::{info, trace, warn}; use parking_lot::{Mutex, RwLock}; @@ -70,7 +70,7 @@ use sp_runtime::{ Block as BlockT, BlockIdTo, HashFor, Header as HeaderT, NumberFor, One, SaturatedConversion, Zero, }, - BuildStorage, Digest, Justification, Justifications, StateVersion, + Digest, Justification, Justifications, StateVersion, }; use sp_state_machine::{ prove_child_read, prove_range_read_with_child_with_size, prove_read, @@ -155,9 +155,10 @@ enum PrepareStorageChangesResult, Block: BlockT> { /// Create an instance of in-memory client. #[cfg(feature = "test-helpers")] -pub fn new_in_mem( +pub fn new_in_mem( + backend: Arc>, executor: E, - genesis_storage: &S, + genesis_block_builder: G, keystore: Option, prometheus_registry: Option, telemetry: Option, @@ -168,13 +169,16 @@ pub fn new_in_mem( > where E: CodeExecutor + RuntimeVersionOf, - S: BuildStorage, Block: BlockT, + G: BuildGenesisBlock< + Block, + BlockImportOperation = as backend::Backend>::BlockImportOperation, + >, { new_with_backend( - Arc::new(in_mem::Backend::new()), + backend, executor, - genesis_storage, + genesis_block_builder, keystore, spawn_handle, prometheus_registry, @@ -214,10 +218,10 @@ impl Default for ClientConfig { /// Create a client with the explicitly provided backend. /// This is useful for testing backend implementations. #[cfg(feature = "test-helpers")] -pub fn new_with_backend( +pub fn new_with_backend( backend: Arc, executor: E, - build_genesis_storage: &S, + genesis_block_builder: G, keystore: Option, spawn_handle: Box, prometheus_registry: Option, @@ -226,7 +230,10 @@ pub fn new_with_backend( ) -> sp_blockchain::Result, Block, RA>> where E: CodeExecutor + RuntimeVersionOf, - S: BuildStorage, + G: BuildGenesisBlock< + Block, + BlockImportOperation = >::BlockImportOperation, + >, Block: BlockT, B: backend::LocalBackend + 'static, { @@ -247,7 +254,7 @@ where Client::new( backend, call_executor, - build_genesis_storage, + genesis_block_builder, Default::default(), Default::default(), prometheus_registry, @@ -347,26 +354,25 @@ where Block::Header: Clone, { /// Creates new Substrate Client with given blockchain and code executor. - pub fn new( + pub fn new( backend: Arc, executor: E, - build_genesis_storage: &dyn BuildStorage, + genesis_block_builder: G, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, prometheus_registry: Option, telemetry: Option, config: ClientConfig, - ) -> sp_blockchain::Result { + ) -> sp_blockchain::Result + where + G: BuildGenesisBlock< + Block, + BlockImportOperation = >::BlockImportOperation, + >, + { let info = backend.blockchain().info(); if info.finalized_state.is_none() { - let genesis_storage = - build_genesis_storage.build_storage().map_err(sp_blockchain::Error::Storage)?; - let genesis_state_version = - Self::resolve_state_version_from_wasm(&genesis_storage, &executor)?; - let mut op = backend.begin_operation()?; - let state_root = - op.set_genesis_state(genesis_storage, !config.no_genesis, genesis_state_version)?; - let genesis_block = genesis::construct_genesis_block::(state_root); + let (genesis_block, mut op) = genesis_block_builder.build_genesis_block()?; info!( "🔨 Initializing Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), @@ -379,13 +385,8 @@ where } else { NewBlockState::Normal }; - op.set_block_data( - genesis_block.deconstruct().0, - Some(vec![]), - None, - None, - block_state, - )?; + let (header, body) = genesis_block.deconstruct(); + op.set_block_data(header, Some(body), None, None, block_state)?; backend.commit_operation(op)?; } @@ -640,7 +641,7 @@ where // This is use by fast sync for runtime version to be resolvable from // changes. let state_version = - Self::resolve_state_version_from_wasm(&storage, &self.executor)?; + resolve_state_version_from_wasm(&storage, &self.executor)?; let state_root = operation.op.reset_storage(storage, state_version)?; if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in @@ -1104,34 +1105,37 @@ where trace!("Collected {} uncles", uncles.len()); Ok(uncles) } +} - fn resolve_state_version_from_wasm( - storage: &Storage, - executor: &E, - ) -> sp_blockchain::Result { - if let Some(wasm) = storage.top.get(well_known_keys::CODE) { - let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. - - let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); - let runtime_code = sp_core::traits::RuntimeCode { - code_fetcher: &code_fetcher, - heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, - }; - let runtime_version = - RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; - Ok(runtime_version.state_version()) - } else { - Err(sp_blockchain::Error::VersionInvalid( - "Runtime missing from initial storage, could not read state version.".to_string(), - )) - } +/// Return the genesis state version given the genesis storage and executor. +pub fn resolve_state_version_from_wasm( + storage: &Storage, + executor: &E, +) -> sp_blockchain::Result +where + E: RuntimeVersionOf, +{ + if let Some(wasm) = storage.top.get(well_known_keys::CODE) { + let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. + + let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); + let runtime_code = sp_core::traits::RuntimeCode { + code_fetcher: &code_fetcher, + heap_pages: None, + hash: { + use std::hash::{Hash, Hasher}; + let mut state = DefaultHasher::new(); + wasm.hash(&mut state); + state.finish().to_le_bytes().to_vec() + }, + }; + let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; + Ok(runtime_version.state_version()) + } else { + Err(sp_blockchain::Error::VersionInvalid( + "Runtime missing from initial storage, could not read state version.".to_string(), + )) } } diff --git a/client/service/src/client/genesis.rs b/client/service/src/client/genesis.rs index 35fb11f04972a..54ef1182b7e7d 100644 --- a/client/service/src/client/genesis.rs +++ b/client/service/src/client/genesis.rs @@ -18,7 +18,14 @@ //! Tool for creating the genesis block. -use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}; +use sc_client_api::{backend::Backend, BlockImportOperation}; +use sc_executor::RuntimeVersionOf; +use sp_core::storage::Storage; +use sp_runtime::{ + traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + BuildStorage, +}; +use std::{marker::PhantomData, sync::Arc}; /// Create a genesis block, given the initial storage. pub fn construct_genesis_block(state_root: Block::Hash) -> Block { @@ -38,3 +45,61 @@ pub fn construct_genesis_block(state_root: Block::Hash) -> Block Default::default(), ) } + +/// Trait for building the genesis block. +pub trait BuildGenesisBlock { + /// The import operation used to import the genesis block into the backend. + type BlockImportOperation; + + /// Returns the built genesis block along with the block import operation + /// after setting the genesis storage. + fn build_genesis_block(self) -> sp_blockchain::Result<(Block, Self::BlockImportOperation)>; +} + +/// Default genesis block builder in Substrate. +pub struct GenesisBlockBuilder { + genesis_storage: Storage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + _phantom: PhantomData, +} + +impl, E: RuntimeVersionOf> GenesisBlockBuilder { + /// Constructs a new instance of [`GenesisBlockBuilder`]. + pub fn new( + build_genesis_storage: &dyn BuildStorage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + ) -> sp_blockchain::Result { + let genesis_storage = + build_genesis_storage.build_storage().map_err(sp_blockchain::Error::Storage)?; + Ok(Self { + genesis_storage, + commit_genesis_state, + backend, + executor, + _phantom: PhantomData::, + }) + } +} + +impl, E: RuntimeVersionOf> BuildGenesisBlock + for GenesisBlockBuilder +{ + type BlockImportOperation = >::BlockImportOperation; + + fn build_genesis_block(self) -> sp_blockchain::Result<(Block, Self::BlockImportOperation)> { + let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; + + let genesis_state_version = + crate::resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let mut op = backend.begin_operation()?; + let state_root = + op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; + let genesis_block = construct_genesis_block::(state_root); + + Ok((genesis_block, op)) + } +} diff --git a/client/service/src/client/mod.rs b/client/service/src/client/mod.rs index eac744923d501..dafeb68831885 100644 --- a/client/service/src/client/mod.rs +++ b/client/service/src/client/mod.rs @@ -53,7 +53,7 @@ mod wasm_substitutes; pub use self::{ call_executor::LocalCallExecutor, - client::{Client, ClientConfig}, + client::{resolve_state_version_from_wasm, Client, ClientConfig}, }; #[cfg(feature = "test-helpers")] diff --git a/client/service/src/config.rs b/client/service/src/config.rs index e79ff48d6f0ff..efadb8433f63d 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -34,6 +34,7 @@ pub use sc_network_common::{ use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; +use sc_network::config::SyncMode; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -238,6 +239,22 @@ impl Configuration { }; ProtocolId::from(protocol_id_full) } + + /// Returns true if the genesis state writting will be skipped while initializing the genesis + /// block. + pub fn no_genesis(&self) -> bool { + matches!(self.network.sync_mode, SyncMode::Fast { .. } | SyncMode::Warp { .. }) + } + + /// Returns the database config for creating the backend. + pub fn db_config(&self) -> sc_client_db::DatabaseSettings { + sc_client_db::DatabaseSettings { + trie_cache_maximum_size: self.trie_cache_maximum_size, + state_pruning: self.state_pruning.clone(), + source: self.database.clone(), + blocks_pruning: self.blocks_pruning, + } + } } /// Available RPC methods. diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index f0e3f72510c28..8b3a29ba4032a 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -57,7 +57,10 @@ pub use self::{ new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, }, - client::{ClientConfig, LocalCallExecutor}, + client::{ + genesis::{BuildGenesisBlock, GenesisBlockBuilder}, + resolve_state_version_from_wasm, ClientConfig, LocalCallExecutor, + }, error::Error, }; pub use config::{ diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index be9253d8c78e8..e0b07b39cd7e4 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1756,17 +1756,30 @@ fn storage_keys_iter_works() { fn cleans_up_closed_notification_sinks_on_block_import() { use substrate_test_runtime_client::GenesisInit; + let backend = Arc::new(sc_client_api::in_mem::Backend::new()); + let executor = substrate_test_runtime_client::new_native_executor(); + let client_config = sc_service::ClientConfig::default(); + + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .unwrap(); + // NOTE: we need to build the client here instead of using the client // provided by test_runtime_client otherwise we can't access the private // `import_notification_sinks` and `finality_notification_sinks` fields. let mut client = new_in_mem::<_, Block, _, RuntimeApi>( - substrate_test_runtime_client::new_native_executor(), - &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + backend, + executor, + genesis_block_builder, None, None, None, Box::new(TaskExecutor::new()), - Default::default(), + client_config, ) .unwrap(); diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index 8ee652abe2c70..5dc93da13fe74 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -203,7 +203,7 @@ impl ) where ExecutorDispatch: - sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + 'static, + sc_client_api::CallExecutor + sc_executor::RuntimeVersionOf + Clone + 'static, Backend: sc_client_api::backend::Backend, >::OffchainStorage: 'static, { @@ -223,19 +223,29 @@ impl storage }; + let client_config = ClientConfig { + offchain_indexing_api: self.enable_offchain_indexing_api, + no_genesis: self.no_genesis, + ..Default::default() + }; + + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &storage, + !client_config.no_genesis, + self.backend.clone(), + executor.clone(), + ) + .expect("Creates genesis block builder"); + let client = client::Client::new( self.backend.clone(), executor, - &storage, + genesis_block_builder, self.fork_blocks, self.bad_blocks, None, None, - ClientConfig { - offchain_indexing_api: self.enable_offchain_indexing_api, - no_genesis: self.no_genesis, - ..Default::default() - }, + client_config, ) .expect("Creates new client");