Skip to content

Commit

Permalink
sc-chain-spec: add support for custom host functions (#2190)
Browse files Browse the repository at this point in the history
Genesis building in runtime may involve calling some custom host
functions. This PR allows to pass `HostFunctions` into the `ChainSpec`
struct, which in turn are passed to `WasmExecutor`. The `ChainSpec` now
has extended host functions type parameter:
```
pub struct ChainSpec<G, E = NoExtension, EHF = ()>
```
which will be combined with the default set
(`sp_io::SubstrateHostFunctions`) in an instance of `WasmExecutor` used
to build the genesis config.

Fix for #2188

---------

Co-authored-by: Davide Galassi <[email protected]>
Co-authored-by: Bastian Köcher <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2023
1 parent ffa0e30 commit b8acc57
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 37 deletions.
3 changes: 2 additions & 1 deletion substrate/bin/utils/chain-spec-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ pub fn generate_chain_spec_for_runtime(cmd: &RuntimeCmd) -> Result<String, Strin
)?)
},
GenesisBuildAction::Default(DefaultCmd { ref default_config_path }) => {
let caller = GenesisConfigBuilderRuntimeCaller::new(&code[..]);
let caller: GenesisConfigBuilderRuntimeCaller =
GenesisConfigBuilderRuntimeCaller::new(&code[..]);
let default_config = caller
.get_default_config()
.map_err(|e| format!("getting default config from runtime should work: {e}"))?;
Expand Down
86 changes: 57 additions & 29 deletions substrate/client/chain-spec/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
//! Substrate chain configurations.
#![warn(missing_docs)]
use crate::{
extension::GetExtension, ChainType, GenesisConfigBuilderRuntimeCaller as RuntimeCaller,
Properties, RuntimeGenesis,
extension::GetExtension, genesis_config_builder::HostFunctions, ChainType,
GenesisConfigBuilderRuntimeCaller as RuntimeCaller, Properties, RuntimeGenesis,
};
use sc_network::config::MultiaddrWithPeerId;
use sc_telemetry::TelemetryEndpoints;
Expand Down Expand Up @@ -122,7 +122,10 @@ impl<G: RuntimeGenesis> GenesisSource<G> {
}
}

impl<G: RuntimeGenesis, E> BuildStorage for ChainSpec<G, E> {
impl<G: RuntimeGenesis, E, EHF> BuildStorage for ChainSpec<G, E, EHF>
where
EHF: HostFunctions,
{
fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
match self.genesis.resolve()? {
#[allow(deprecated)]
Expand Down Expand Up @@ -158,7 +161,7 @@ impl<G: RuntimeGenesis, E> BuildStorage for ChainSpec<G, E> {
json_blob: RuntimeGenesisConfigJson::Config(config),
code,
}) => {
RuntimeCaller::new(&code[..])
RuntimeCaller::<EHF>::new(&code[..])
.get_storage_for_config(config)?
.assimilate_storage(storage)?;
storage
Expand All @@ -169,7 +172,7 @@ impl<G: RuntimeGenesis, E> BuildStorage for ChainSpec<G, E> {
json_blob: RuntimeGenesisConfigJson::Patch(patch),
code,
}) => {
RuntimeCaller::new(&code[..])
RuntimeCaller::<EHF>::new(&code[..])
.get_storage_for_patch(patch)?
.assimilate_storage(storage)?;
storage
Expand Down Expand Up @@ -322,7 +325,7 @@ struct ClientSpec<E> {
pub type NoExtension = Option<()>;

/// Builder for creating [`ChainSpec`] instances.
pub struct ChainSpecBuilder<G, E = NoExtension> {
pub struct ChainSpecBuilder<G, E = NoExtension, EHF = ()> {
code: Vec<u8>,
extensions: E,
name: String,
Expand All @@ -334,10 +337,10 @@ pub struct ChainSpecBuilder<G, E = NoExtension> {
protocol_id: Option<String>,
fork_id: Option<String>,
properties: Option<Properties>,
_genesis: PhantomData<G>,
_genesis: PhantomData<(G, EHF)>,
}

impl<G, E> ChainSpecBuilder<G, E> {
impl<G, E, EHF> ChainSpecBuilder<G, E, EHF> {
/// Creates a new builder instance with no defaults.
pub fn new(code: &[u8], extensions: E) -> Self {
Self {
Expand Down Expand Up @@ -429,7 +432,7 @@ impl<G, E> ChainSpecBuilder<G, E> {
}

/// Builds a [`ChainSpec`] instance using the provided settings.
pub fn build(self) -> ChainSpec<G, E> {
pub fn build(self) -> ChainSpec<G, E, EHF> {
let client_spec = ClientSpec {
name: self.name,
id: self.id,
Expand All @@ -448,23 +451,33 @@ impl<G, E> ChainSpecBuilder<G, E> {
ChainSpec {
client_spec,
genesis: GenesisSource::GenesisBuilderApi(self.genesis_build_action, self.code.into()),
_host_functions: Default::default(),
}
}
}

/// A configuration of a chain. Can be used to build a genesis block.
pub struct ChainSpec<G, E = NoExtension> {
///
/// The chain spec is generic over the native `RuntimeGenesisConfig` struct (`G`). It is also
/// possible to parametrize chain spec over the extended host functions (EHF). It should be use if
/// runtime is using the non-standard host function during genesis state creation.
pub struct ChainSpec<G, E = NoExtension, EHF = ()> {
client_spec: ClientSpec<E>,
genesis: GenesisSource<G>,
_host_functions: PhantomData<EHF>,
}

impl<G, E: Clone> Clone for ChainSpec<G, E> {
impl<G, E: Clone, EHF> Clone for ChainSpec<G, E, EHF> {
fn clone(&self) -> Self {
ChainSpec { client_spec: self.client_spec.clone(), genesis: self.genesis.clone() }
ChainSpec {
client_spec: self.client_spec.clone(),
genesis: self.genesis.clone(),
_host_functions: self._host_functions,
}
}
}

impl<G, E> ChainSpec<G, E> {
impl<G, E, EHF> ChainSpec<G, E, EHF> {
/// A list of bootnode addresses.
pub fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
&self.client_spec.boot_nodes
Expand Down Expand Up @@ -553,6 +566,7 @@ impl<G, E> ChainSpec<G, E> {
ChainSpec {
client_spec,
genesis: GenesisSource::Factory(Arc::new(constructor), code.into()),
_host_functions: Default::default(),
}
}

Expand All @@ -562,19 +576,23 @@ impl<G, E> ChainSpec<G, E> {
}

/// Provides a `ChainSpec` builder.
pub fn builder(code: &[u8], extensions: E) -> ChainSpecBuilder<G, E> {
pub fn builder(code: &[u8], extensions: E) -> ChainSpecBuilder<G, E, EHF> {
ChainSpecBuilder::new(code, extensions)
}
}

impl<G: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned> ChainSpec<G, E> {
impl<G: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned, EHF> ChainSpec<G, E, EHF> {
/// Parse json content into a `ChainSpec`
pub fn from_json_bytes(json: impl Into<Cow<'static, [u8]>>) -> Result<Self, String> {
let json = json.into();
let client_spec = json::from_slice(json.as_ref())
.map_err(|e| format!("Error parsing spec file: {}", e))?;

Ok(ChainSpec { client_spec, genesis: GenesisSource::Binary(json) })
Ok(ChainSpec {
client_spec,
genesis: GenesisSource::Binary(json),
_host_functions: Default::default(),
})
}

/// Parse json file into a `ChainSpec`
Expand All @@ -593,7 +611,11 @@ impl<G: serde::de::DeserializeOwned, E: serde::de::DeserializeOwned> ChainSpec<G
let client_spec =
json::from_slice(&bytes).map_err(|e| format!("Error parsing spec file: {}", e))?;

Ok(ChainSpec { client_spec, genesis: GenesisSource::File(path) })
Ok(ChainSpec {
client_spec,
genesis: GenesisSource::File(path),
_host_functions: Default::default(),
})
}
}

Expand All @@ -608,7 +630,10 @@ struct ChainSpecJsonContainer<G, E> {
genesis: Genesis<G>,
}

impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static> ChainSpec<G, E> {
impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static, EHF> ChainSpec<G, E, EHF>
where
EHF: HostFunctions,
{
fn json_container(&self, raw: bool) -> Result<ChainSpecJsonContainer<G, E>, String> {
let raw_genesis = match (raw, self.genesis.resolve()?) {
(
Expand All @@ -618,7 +643,8 @@ impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static> ChainSpec<G, E> {
code,
}),
) => {
let mut storage = RuntimeCaller::new(&code[..]).get_storage_for_config(config)?;
let mut storage =
RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_config(config)?;
storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
RawGenesis::from(storage)
},
Expand All @@ -629,7 +655,8 @@ impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static> ChainSpec<G, E> {
code,
}),
) => {
let mut storage = RuntimeCaller::new(&code[..]).get_storage_for_patch(patch)?;
let mut storage =
RuntimeCaller::<EHF>::new(&code[..]).get_storage_for_patch(patch)?;
storage.top.insert(sp_core::storage::well_known_keys::CODE.to_vec(), code);
RawGenesis::from(storage)
},
Expand Down Expand Up @@ -664,10 +691,11 @@ impl<G: RuntimeGenesis, E: serde::Serialize + Clone + 'static> ChainSpec<G, E> {
}
}

impl<G, E> crate::ChainSpec for ChainSpec<G, E>
impl<G, E, EHF> crate::ChainSpec for ChainSpec<G, E, EHF>
where
G: RuntimeGenesis + 'static,
E: GetExtension + serde::Serialize + Clone + Send + Sync + 'static,
EHF: HostFunctions,
{
fn boot_nodes(&self) -> &[MultiaddrWithPeerId] {
ChainSpec::boot_nodes(self)
Expand Down Expand Up @@ -953,7 +981,7 @@ mod tests {
#[docify::export]
#[test]
fn build_chain_spec_with_patch_works() {
let output: ChainSpec<()> = ChainSpec::builder(
let output = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand Down Expand Up @@ -986,7 +1014,7 @@ mod tests {
#[docify::export]
#[test]
fn generate_chain_spec_with_patch_works() {
let output: ChainSpec<()> = ChainSpec::builder(
let output = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand Down Expand Up @@ -1033,7 +1061,7 @@ mod tests {
#[test]
fn generate_chain_spec_with_full_config_works() {
let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
let output: ChainSpec<()> = ChainSpec::builder(
let output = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand Down Expand Up @@ -1065,7 +1093,7 @@ mod tests {
fn chain_spec_as_json_fails_with_invalid_config() {
let j =
include_str!("../../../test-utils/runtime/res/default_genesis_config_invalid_2.json");
let output: ChainSpec<()> = ChainSpec::builder(
let output = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand All @@ -1083,7 +1111,7 @@ mod tests {

#[test]
fn chain_spec_as_json_fails_with_invalid_patch() {
let output: ChainSpec<()> = ChainSpec::builder(
let output = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand Down Expand Up @@ -1139,7 +1167,7 @@ mod tests {
#[test]
fn update_code_works_with_runtime_genesis_config() {
let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
let chain_spec: ChainSpec<()> = ChainSpec::builder(
let chain_spec = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand All @@ -1162,7 +1190,7 @@ mod tests {
#[test]
fn update_code_works_for_raw() {
let j = include_str!("../../../test-utils/runtime/res/default_genesis_config.json");
let chain_spec: ChainSpec<()> = ChainSpec::builder(
let chain_spec = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand All @@ -1184,7 +1212,7 @@ mod tests {

#[test]
fn update_code_works_with_runtime_genesis_patch() {
let chain_spec: ChainSpec<()> = ChainSpec::builder(
let chain_spec = ChainSpec::<()>::builder(
substrate_test_runtime::wasm_binary_unwrap().into(),
Default::default(),
)
Expand Down
27 changes: 20 additions & 7 deletions substrate/client/chain-spec/src/genesis_config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
//! A helper module for calling the GenesisBuilder API from arbitrary runtime wasm blobs.

use codec::{Decode, Encode};
pub use sc_executor::sp_wasm_interface::HostFunctions;
use sc_executor::{error::Result, WasmExecutor};
use serde_json::{from_slice, Value};
use sp_core::{
Expand All @@ -30,27 +31,39 @@ use sp_state_machine::BasicExternalities;
use std::borrow::Cow;

/// A utility that facilitates calling the GenesisBuilder API from the runtime wasm code blob.
pub struct GenesisConfigBuilderRuntimeCaller<'a> {
///
/// `EHF` type allows to specify the extended host function required for building runtime's genesis
/// config. The type will be compbined with default `sp_io::SubstrateHostFunctions`.
pub struct GenesisConfigBuilderRuntimeCaller<'a, EHF = ()>
where
EHF: HostFunctions,
{
code: Cow<'a, [u8]>,
code_hash: Vec<u8>,
executor: WasmExecutor<sp_io::SubstrateHostFunctions>,
executor: WasmExecutor<(sp_io::SubstrateHostFunctions, EHF)>,
}

impl<'a> FetchRuntimeCode for GenesisConfigBuilderRuntimeCaller<'a> {
impl<'a, EHF> FetchRuntimeCode for GenesisConfigBuilderRuntimeCaller<'a, EHF>
where
EHF: HostFunctions,
{
fn fetch_runtime_code(&self) -> Option<Cow<[u8]>> {
Some(self.code.as_ref().into())
}
}

impl<'a> GenesisConfigBuilderRuntimeCaller<'a> {
impl<'a, EHF> GenesisConfigBuilderRuntimeCaller<'a, EHF>
where
EHF: HostFunctions,
{
/// Creates new instance using the provided code blob.
///
/// This code is later referred to as `runtime`.
pub fn new(code: &'a [u8]) -> Self {
GenesisConfigBuilderRuntimeCaller {
code: code.into(),
code_hash: sp_core::blake2_256(code).to_vec(),
executor: WasmExecutor::<sp_io::SubstrateHostFunctions>::builder()
executor: WasmExecutor::<(sp_io::SubstrateHostFunctions, EHF)>::builder()
.with_allow_missing_host_functions(true)
.build(),
}
Expand Down Expand Up @@ -134,7 +147,7 @@ mod tests {
#[test]
fn get_default_config_works() {
let config =
GenesisConfigBuilderRuntimeCaller::new(substrate_test_runtime::wasm_binary_unwrap())
<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
.get_default_config()
.unwrap();
let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":null},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#;
Expand All @@ -156,7 +169,7 @@ mod tests {
});

let storage =
GenesisConfigBuilderRuntimeCaller::new(substrate_test_runtime::wasm_binary_unwrap())
<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
.get_storage_for_patch(patch)
.unwrap();

Expand Down

0 comments on commit b8acc57

Please sign in to comment.