sc_chain_spec/
genesis_config_builder.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! A helper module for calling the GenesisBuilder API from arbitrary runtime wasm blobs.
20
21use codec::{Decode, Encode};
22pub use sc_executor::sp_wasm_interface::HostFunctions;
23use sc_executor::{error::Result, WasmExecutor};
24use serde_json::{from_slice, Value};
25use sp_core::{
26	storage::Storage,
27	traits::{CallContext, CodeExecutor, Externalities, FetchRuntimeCode, RuntimeCode},
28};
29use sp_genesis_builder::{PresetId, Result as BuildResult};
30pub use sp_genesis_builder::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET};
31use sp_state_machine::BasicExternalities;
32use std::borrow::Cow;
33
34/// A utility that facilitates calling the GenesisBuilder API from the runtime wasm code blob.
35///
36/// `EHF` type allows to specify the extended host function required for building runtime's genesis
37/// config. The type will be combined with default `sp_io::SubstrateHostFunctions`.
38pub struct GenesisConfigBuilderRuntimeCaller<'a, EHF = ()>
39where
40	EHF: HostFunctions,
41{
42	code: Cow<'a, [u8]>,
43	code_hash: Vec<u8>,
44	executor: WasmExecutor<(sp_io::SubstrateHostFunctions, EHF)>,
45}
46
47impl<'a, EHF> FetchRuntimeCode for GenesisConfigBuilderRuntimeCaller<'a, EHF>
48where
49	EHF: HostFunctions,
50{
51	fn fetch_runtime_code(&self) -> Option<Cow<[u8]>> {
52		Some(self.code.as_ref().into())
53	}
54}
55
56impl<'a, EHF> GenesisConfigBuilderRuntimeCaller<'a, EHF>
57where
58	EHF: HostFunctions,
59{
60	/// Creates new instance using the provided code blob.
61	///
62	/// This code is later referred to as `runtime`.
63	pub fn new(code: &'a [u8]) -> Self {
64		GenesisConfigBuilderRuntimeCaller {
65			code: code.into(),
66			code_hash: sp_crypto_hashing::blake2_256(code).to_vec(),
67			executor: WasmExecutor::<(sp_io::SubstrateHostFunctions, EHF)>::builder()
68				.with_allow_missing_host_functions(true)
69				.build(),
70		}
71	}
72
73	fn call(&self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>> {
74		self.executor
75			.call(
76				ext,
77				&RuntimeCode { heap_pages: None, code_fetcher: self, hash: self.code_hash.clone() },
78				method,
79				data,
80				CallContext::Offchain,
81			)
82			.0
83	}
84
85	/// Returns a json representation of the default `RuntimeGenesisConfig` provided by the
86	/// `runtime`.
87	///
88	/// Calls [`GenesisBuilder::get_preset`](sp_genesis_builder::GenesisBuilder::get_preset) in the
89	/// `runtime` with `None` argument.
90	pub fn get_default_config(&self) -> core::result::Result<Value, String> {
91		self.get_named_preset(None)
92	}
93
94	/// Returns a JSON blob representation of the builtin `GenesisConfig` identified by `id`.
95	///
96	/// Calls [`GenesisBuilder::get_preset`](sp_genesis_builder::GenesisBuilder::get_preset)
97	/// provided by the `runtime`.
98	pub fn get_named_preset(&self, id: Option<&String>) -> core::result::Result<Value, String> {
99		let mut t = BasicExternalities::new_empty();
100		let call_result = self
101			.call(&mut t, "GenesisBuilder_get_preset", &id.encode())
102			.map_err(|e| format!("wasm call error {e}"))?;
103
104		let named_preset = Option::<Vec<u8>>::decode(&mut &call_result[..])
105			.map_err(|e| format!("scale codec error: {e}"))?;
106
107		if let Some(named_preset) = named_preset {
108			Ok(from_slice(&named_preset[..]).expect("returned value is json. qed."))
109		} else {
110			Err(format!("The preset with name {id:?} is not available."))
111		}
112	}
113
114	/// Calls [`sp_genesis_builder::GenesisBuilder::build_state`] provided by runtime.
115	pub fn get_storage_for_config(&self, config: Value) -> core::result::Result<Storage, String> {
116		let mut ext = BasicExternalities::new_empty();
117
118		let json_pretty_str = serde_json::to_string_pretty(&config)
119			.map_err(|e| format!("json to string failed: {e}"))?;
120
121		let call_result = self
122			.call(&mut ext, "GenesisBuilder_build_state", &json_pretty_str.encode())
123			.map_err(|e| format!("wasm call error {e}"))?;
124
125		BuildResult::decode(&mut &call_result[..])
126			.map_err(|e| format!("scale codec error: {e}"))?
127			.map_err(|e| format!("{e} for blob:\n{}", json_pretty_str))?;
128
129		Ok(ext.into_storages())
130	}
131
132	/// Creates the genesis state by patching the default `RuntimeGenesisConfig`.
133	///
134	/// This function generates the `RuntimeGenesisConfig` for the runtime by applying a provided
135	/// JSON patch. The patch modifies the default `RuntimeGenesisConfig` allowing customization of
136	/// the specific keys. The resulting `RuntimeGenesisConfig` is then deserialized from the
137	/// patched JSON representation and stored in the storage.
138	///
139	/// If the provided JSON patch is incorrect or the deserialization fails the error will be
140	/// returned.
141	///
142	/// The patching process modifies the default `RuntimeGenesisConfig` according to the following
143	/// rules:
144	/// 1. Existing keys in the default configuration will be overridden by the corresponding values
145	///    in the patch.
146	/// 2. If a key exists in the patch but not in the default configuration, it will be added to
147	///    the resulting `RuntimeGenesisConfig`.
148	/// 3. Keys in the default configuration that have null values in the patch will be removed from
149	///    the resulting `RuntimeGenesisConfig`. This is helpful for changing enum variant value.
150	///
151	/// Please note that the patch may contain full `RuntimeGenesisConfig`.
152	pub fn get_storage_for_patch(&self, patch: Value) -> core::result::Result<Storage, String> {
153		let mut config = self.get_default_config()?;
154		crate::json_patch::merge(&mut config, patch);
155		self.get_storage_for_config(config)
156	}
157
158	pub fn get_storage_for_named_preset(
159		&self,
160		name: Option<&String>,
161	) -> core::result::Result<Storage, String> {
162		self.get_storage_for_patch(self.get_named_preset(name)?)
163	}
164
165	pub fn preset_names(&self) -> core::result::Result<Vec<PresetId>, String> {
166		let mut t = BasicExternalities::new_empty();
167		let call_result = self
168			.call(&mut t, "GenesisBuilder_preset_names", &vec![])
169			.map_err(|e| format!("wasm call error {e}"))?;
170
171		let preset_names = Vec::<PresetId>::decode(&mut &call_result[..])
172			.map_err(|e| format!("scale codec error: {e}"))?;
173
174		Ok(preset_names)
175	}
176}
177
178#[cfg(test)]
179mod tests {
180	use super::*;
181	use serde_json::{from_str, json};
182	pub use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration};
183	pub use sp_genesis_builder::PresetId;
184
185	#[test]
186	fn list_presets_works() {
187		sp_tracing::try_init_simple();
188		let presets =
189			<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
190				.preset_names()
191				.unwrap();
192		assert_eq!(presets, vec![PresetId::from("foobar"), PresetId::from("staging"),]);
193	}
194
195	#[test]
196	fn get_default_config_works() {
197		let config =
198			<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
199				.get_default_config()
200				.unwrap();
201		let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#;
202		assert_eq!(from_str::<Value>(expected).unwrap(), config);
203	}
204
205	#[test]
206	fn get_named_preset_works() {
207		sp_tracing::try_init_simple();
208		let config =
209			<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
210				.get_named_preset(Some(&"foobar".to_string()))
211				.unwrap();
212		let expected = r#"{"foo":"bar"}"#;
213		assert_eq!(from_str::<Value>(expected).unwrap(), config);
214	}
215
216	#[test]
217	fn get_storage_for_patch_works() {
218		let patch = json!({
219			"babe": {
220				"epochConfig": {
221					"c": [
222						69,
223						696
224					],
225					"allowed_slots": "PrimaryAndSecondaryPlainSlots"
226				}
227			},
228		});
229
230		let storage =
231			<GenesisConfigBuilderRuntimeCaller>::new(substrate_test_runtime::wasm_binary_unwrap())
232				.get_storage_for_patch(patch)
233				.unwrap();
234
235		//Babe|Authorities
236		let value: Vec<u8> = storage
237			.top
238			.get(
239				&array_bytes::hex2bytes(
240					"1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef",
241				)
242				.unwrap(),
243			)
244			.unwrap()
245			.clone();
246
247		assert_eq!(
248			BabeEpochConfiguration::decode(&mut &value[..]).unwrap(),
249			BabeEpochConfiguration {
250				c: (69, 696),
251				allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots
252			}
253		);
254	}
255}