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`
This commit is contained in:
Liu-Cheng Xu
2022-12-19 22:26:31 +08:00
committed by GitHub
parent 1be51ccb5c
commit b92aa3dbdc
10 changed files with 260 additions and 112 deletions
+20 -10
View File
@@ -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");
+41 -18
View File
@@ -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<TBl, TRtApi, TExec>(
config: &Configuration,
telemetry: Option<TelemetryHandle>,
@@ -191,6 +191,34 @@ pub fn new_full_parts<TBl, TRtApi, TExec>(
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<TBl, TRtApi, TExec, TBuildGenesisBlock>(
config: &Configuration,
telemetry: Option<TelemetryHandle>,
executor: TExec,
backend: Arc<TFullBackend<TBl>>,
genesis_block_builder: TBuildGenesisBlock,
) -> Result<TFullParts<TBl, TRtApi, TExec>, Error>
where
TBl: BlockT,
TExec: CodeExecutor + RuntimeVersionOf + Clone,
TBuildGenesisBlock: BuildGenesisBlock<
TBl,
BlockImportOperation = <Backend<TBl> as sc_client_api::backend::Backend<TBl>>::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<E, Block, RA>(
pub fn new_client<E, Block, RA, G>(
backend: Arc<Backend<Block>>,
executor: E,
genesis_storage: &dyn BuildStorage,
genesis_block_builder: G,
fork_blocks: ForkBlocks<Block>,
bad_blocks: BadBlocks<Block>,
execution_extensions: ExecutionExtensions<Block>,
@@ -305,6 +324,10 @@ pub fn new_client<E, Block, RA>(
where
Block: BlockT,
E: CodeExecutor + RuntimeVersionOf,
G: BuildGenesisBlock<
Block,
BlockImportOperation = <Backend<Block> as sc_client_api::backend::Backend<Block>>::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,
@@ -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(),
+60 -56
View File
@@ -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<B: backend::Backend<Block>, Block: BlockT> {
/// Create an instance of in-memory client.
#[cfg(feature = "test-helpers")]
pub fn new_in_mem<E, Block, S, RA>(
pub fn new_in_mem<E, Block, G, RA>(
backend: Arc<in_mem::Backend<Block>>,
executor: E,
genesis_storage: &S,
genesis_block_builder: G,
keystore: Option<SyncCryptoStorePtr>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
@@ -168,13 +169,16 @@ pub fn new_in_mem<E, Block, S, RA>(
>
where
E: CodeExecutor + RuntimeVersionOf,
S: BuildStorage,
Block: BlockT,
G: BuildGenesisBlock<
Block,
BlockImportOperation = <in_mem::Backend<Block> as backend::Backend<Block>>::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<Block: BlockT> Default for ClientConfig<Block> {
/// Create a client with the explicitly provided backend.
/// This is useful for testing backend implementations.
#[cfg(feature = "test-helpers")]
pub fn new_with_backend<B, E, Block, S, RA>(
pub fn new_with_backend<B, E, Block, G, RA>(
backend: Arc<B>,
executor: E,
build_genesis_storage: &S,
genesis_block_builder: G,
keystore: Option<SyncCryptoStorePtr>,
spawn_handle: Box<dyn SpawnNamed>,
prometheus_registry: Option<Registry>,
@@ -226,7 +230,10 @@ pub fn new_with_backend<B, E, Block, S, RA>(
) -> sp_blockchain::Result<Client<B, LocalCallExecutor<Block, B, E>, Block, RA>>
where
E: CodeExecutor + RuntimeVersionOf,
S: BuildStorage,
G: BuildGenesisBlock<
Block,
BlockImportOperation = <B as backend::Backend<Block>>::BlockImportOperation,
>,
Block: BlockT,
B: backend::LocalBackend<Block> + '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<G>(
backend: Arc<B>,
executor: E,
build_genesis_storage: &dyn BuildStorage,
genesis_block_builder: G,
fork_blocks: ForkBlocks<Block>,
bad_blocks: BadBlocks<Block>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
config: ClientConfig<Block>,
) -> sp_blockchain::Result<Self> {
) -> sp_blockchain::Result<Self>
where
G: BuildGenesisBlock<
Block,
BlockImportOperation = <B as backend::Backend<Block>>::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::<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<StateVersion> {
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.
/// Return the genesis state version given the genesis storage and executor.
pub fn resolve_state_version_from_wasm<E>(
storage: &Storage,
executor: &E,
) -> sp_blockchain::Result<StateVersion>
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(),
))
}
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(),
))
}
}
+66 -1
View File
@@ -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<Block: BlockT>(state_root: Block::Hash) -> Block {
@@ -38,3 +45,61 @@ pub fn construct_genesis_block<Block: BlockT>(state_root: Block::Hash) -> Block
Default::default(),
)
}
/// Trait for building the genesis block.
pub trait BuildGenesisBlock<Block: BlockT> {
/// 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<Block: BlockT, B, E> {
genesis_storage: Storage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
_phantom: PhantomData<Block>,
}
impl<Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf> GenesisBlockBuilder<Block, B, E> {
/// Constructs a new instance of [`GenesisBlockBuilder`].
pub fn new(
build_genesis_storage: &dyn BuildStorage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
) -> sp_blockchain::Result<Self> {
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::<Block>,
})
}
}
impl<Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf> BuildGenesisBlock<Block>
for GenesisBlockBuilder<Block, B, E>
{
type BlockImportOperation = <B as Backend<Block>>::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::<Block>(state_root);
Ok((genesis_block, op))
}
}
+1 -1
View File
@@ -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")]
+17
View File
@@ -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.
+4 -1
View File
@@ -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::{
@@ -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();
+17 -7
View File
@@ -203,7 +203,7 @@ impl<Block: BlockT, ExecutorDispatch, Backend, G: GenesisInit>
)
where
ExecutorDispatch:
sc_client_api::CallExecutor<Block> + sc_executor::RuntimeVersionOf + 'static,
sc_client_api::CallExecutor<Block> + sc_executor::RuntimeVersionOf + Clone + 'static,
Backend: sc_client_api::backend::Backend<Block>,
<Backend as sc_client_api::backend::Backend<Block>>::OffchainStorage: 'static,
{
@@ -223,19 +223,29 @@ impl<Block: BlockT, ExecutorDispatch, Backend, G: GenesisInit>
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");