Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sc-chain-spec: add support for custom host functions #2190

Merged
merged 14 commits into from
Nov 8, 2023
Merged
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(),
michalkucharczyk marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// 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
michalkucharczyk marked this conversation as resolved.
Show resolved Hide resolved
/// 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())
michalkucharczyk marked this conversation as resolved.
Show resolved Hide resolved
.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
Loading