diff --git a/bin/node/testing/src/client.rs b/bin/node/testing/src/client.rs index c4ace4ced9b42..d53519950dc1e 100644 --- a/bin/node/testing/src/client.rs +++ b/bin/node/testing/src/client.rs @@ -32,7 +32,7 @@ pub type Backend = sc_client_db::Backend; /// Test client type. pub type Client = client::Client< Backend, - client::LocalCallExecutor, + client::LocalCallExecutor, node_primitives::Block, node_runtime::RuntimeApi, >; @@ -63,7 +63,7 @@ pub trait TestClientBuilderExt: Sized { impl TestClientBuilderExt for substrate_test_client::TestClientBuilder< node_primitives::Block, - client::LocalCallExecutor, + client::LocalCallExecutor, Backend, GenesisParameters, > { diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index 2faf95568290e..59b55707e182b 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -21,7 +21,7 @@ use std::{borrow::Cow, fs::File, path::PathBuf, sync::Arc, collections::HashMap}; use serde::{Serialize, Deserialize}; -use sp_core::storage::{StorageKey, StorageData, ChildInfo, Storage, StorageChild}; +use sp_core::{storage::{StorageKey, StorageData, ChildInfo, Storage, StorageChild}, Bytes}; use sp_runtime::BuildStorage; use serde_json as json; use crate::{RuntimeGenesis, ChainType, extension::GetExtension, Properties}; @@ -160,6 +160,12 @@ struct ClientSpec { #[serde(skip_serializing)] genesis: serde::de::IgnoredAny, light_sync_state: Option, + /// Mapping from `block_hash` to `wasm_code`. + /// + /// The given `wasm_code` will be used to substitute the on-chain wasm code from the given + /// block hash onwards. + #[serde(default)] + code_substitutes: HashMap, } /// A type denoting empty extensions. @@ -249,6 +255,7 @@ impl ChainSpec { consensus_engine: (), genesis: Default::default(), light_sync_state: None, + code_substitutes: HashMap::new(), }; ChainSpec { @@ -395,6 +402,10 @@ where fn set_light_sync_state(&mut self, light_sync_state: SerializableLightSyncState) { ChainSpec::set_light_sync_state(self, light_sync_state) } + + fn code_substitutes(&self) -> std::collections::HashMap> { + self.client_spec.code_substitutes.iter().map(|(h, c)| (h.clone(), c.0.clone())).collect() + } } /// Hardcoded infomation that allows light clients to sync quickly. diff --git a/client/chain-spec/src/lib.rs b/client/chain-spec/src/lib.rs index ee4f757f8cf0e..e75dafcfe0255 100644 --- a/client/chain-spec/src/lib.rs +++ b/client/chain-spec/src/lib.rs @@ -161,6 +161,8 @@ pub trait ChainSpec: BuildStorage + Send + Sync { fn set_storage(&mut self, storage: Storage); /// Hardcode infomation to allow light clients to sync quickly into the chain spec. fn set_light_sync_state(&mut self, light_sync_state: SerializableLightSyncState); + /// Returns code substitutes that should be used for the on chain wasm. + fn code_substitutes(&self) -> std::collections::HashMap>; } impl std::fmt::Debug for dyn ChainSpec { diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 2c8557a5456e6..45652524d4323 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -50,7 +50,7 @@ use sp_runtime::traits::{ }; use sp_api::{ProvideRuntimeApi, CallApiAt}; use sc_executor::{NativeExecutor, NativeExecutionDispatch, RuntimeInfo}; -use std::sync::Arc; +use std::{sync::Arc, str::FromStr}; use wasm_timer::SystemTime; use sc_telemetry::{ telemetry, @@ -150,6 +150,7 @@ pub type TFullBackend = sc_client_db::Backend; /// Full client call executor type. pub type TFullCallExecutor = crate::client::LocalCallExecutor< + TBl, sc_client_db::Backend, NativeExecutor, >; @@ -172,6 +173,7 @@ pub type TLightCallExecutor = sc_light::GenesisCallExecutor< HashFor >, crate::client::LocalCallExecutor< + TBl, sc_light::Backend< sc_client_db::light::LightStorage, HashFor @@ -206,7 +208,7 @@ pub type TLightClientWithBackend = Client< TBackend, sc_light::GenesisCallExecutor< TBackend, - crate::client::LocalCallExecutor>, + crate::client::LocalCallExecutor>, >, TBl, TRtApi, @@ -295,6 +297,7 @@ pub fn new_full_client( ) -> Result, Error> where TBl: BlockT, TExecDisp: NativeExecutionDispatch + 'static, + TBl::Hash: FromStr, { new_full_parts(config, telemetry).map(|parts| parts.0) } @@ -303,9 +306,10 @@ pub fn new_full_client( pub fn new_full_parts( config: &Configuration, telemetry: Option, -) -> Result, Error> where +) -> Result, Error> where TBl: BlockT, TExecDisp: NativeExecutionDispatch + 'static, + TBl::Hash: FromStr, { let keystore_container = KeystoreContainer::new(&config.keystore)?; @@ -349,6 +353,16 @@ pub fn new_full_parts( sc_offchain::OffchainDb::factory_from_backend(&*backend), ); + let wasm_runtime_substitutes = config.chain_spec.code_substitutes().into_iter().map(|(h, c)| { + let hash = TBl::Hash::from_str(&h) + .map_err(|_| + Error::Application(Box::from( + format!("Failed to parse `{}` as block hash for code substitutes.", h) + )) + )?; + Ok((hash, c)) + }).collect::, Error>>()?; + let client = new_client( backend.clone(), executor, @@ -363,6 +377,7 @@ pub fn new_full_parts( offchain_worker_enabled : config.offchain_worker.enabled, offchain_indexing_api: config.offchain_worker.indexing_enabled, wasm_runtime_overrides: config.wasm_runtime_overrides.clone(), + wasm_runtime_substitutes, }, )?; @@ -453,11 +468,11 @@ pub fn new_client( spawn_handle: Box, prometheus_registry: Option, telemetry: Option, - config: ClientConfig, + config: ClientConfig, ) -> Result< crate::client::Client< Backend, - crate::client::LocalCallExecutor, E>, + crate::client::LocalCallExecutor, E>, Block, RA, >, diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index b48ff028cda0e..e4ef76b1ab081 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -32,47 +32,56 @@ use sp_core::{ }; use sp_api::{ProofRecorder, InitializeBlock, StorageTransactionCache}; use sc_client_api::{backend, call_executor::CallExecutor}; -use super::{client::ClientConfig, wasm_override::WasmOverride}; +use super::{client::ClientConfig, wasm_override::WasmOverride, wasm_substitutes::WasmSubstitutes}; /// Call executor that executes methods locally, querying all required /// data from local backend. -pub struct LocalCallExecutor { +pub struct LocalCallExecutor { backend: Arc, executor: E, wasm_override: Option>, + wasm_substitutes: WasmSubstitutes, spawn_handle: Box, - client_config: ClientConfig, + client_config: ClientConfig, } -impl LocalCallExecutor +impl LocalCallExecutor where - E: CodeExecutor + RuntimeInfo + Clone + 'static + E: CodeExecutor + RuntimeInfo + Clone + 'static, + B: backend::Backend, { /// Creates new instance of local call executor. pub fn new( backend: Arc, executor: E, spawn_handle: Box, - client_config: ClientConfig, + client_config: ClientConfig, ) -> sp_blockchain::Result { let wasm_override = client_config.wasm_runtime_overrides .as_ref() .map(|p| WasmOverride::new(p.clone(), executor.clone())) .transpose()?; + let wasm_substitutes = WasmSubstitutes::new( + client_config.wasm_runtime_substitutes.clone(), + executor.clone(), + backend.clone(), + )?; + Ok(LocalCallExecutor { backend, executor, wasm_override, spawn_handle, client_config, + wasm_substitutes, }) } /// Check if local runtime code overrides are enabled and one is available /// for the given `BlockId`. If yes, return it; otherwise return the same /// `RuntimeCode` instance that was passed. - fn check_override<'a, Block>( + fn check_override<'a>( &'a self, onchain_code: RuntimeCode<'a>, id: &BlockId, @@ -81,16 +90,16 @@ where Block: BlockT, B: backend::Backend, { + let spec = self.runtime_version(id)?.spec_version; let code = if let Some(d) = self.wasm_override .as_ref() - .map::>, _>(|o| { - let spec = self.runtime_version(id)?.spec_version; - Ok(o.get(&spec, onchain_code.heap_pages)) - }) - .transpose()? + .map(|o| o.get(&spec, onchain_code.heap_pages)) .flatten() { log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); d + } else if let Some(s) = self.wasm_substitutes.get(spec, onchain_code.heap_pages, id) { + log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); + s } else { log::debug!( target: "wasm_overrides", @@ -104,7 +113,7 @@ where } } -impl Clone for LocalCallExecutor where E: Clone { +impl Clone for LocalCallExecutor where E: Clone { fn clone(&self) -> Self { LocalCallExecutor { backend: self.backend.clone(), @@ -112,11 +121,12 @@ impl Clone for LocalCallExecutor where E: Clone { wasm_override: self.wasm_override.clone(), spawn_handle: self.spawn_handle.clone(), client_config: self.client_config.clone(), + wasm_substitutes: self.wasm_substitutes.clone(), } } } -impl CallExecutor for LocalCallExecutor +impl CallExecutor for LocalCallExecutor where B: backend::Backend, E: CodeExecutor + RuntimeInfo + Clone + 'static, @@ -314,7 +324,7 @@ where } } -impl sp_version::GetRuntimeVersion for LocalCallExecutor +impl sp_version::GetRuntimeVersion for LocalCallExecutor where B: backend::Backend, E: CodeExecutor + RuntimeInfo + Clone + 'static, @@ -357,11 +367,7 @@ mod tests { // wasm_runtime_overrides is `None` here because we construct the // LocalCallExecutor directly later on - let client_config = ClientConfig { - offchain_worker_enabled: false, - offchain_indexing_api: false, - wasm_runtime_overrides: None, - }; + 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::< @@ -383,10 +389,15 @@ mod tests { let call_executor = LocalCallExecutor { backend: backend.clone(), - executor, + executor: executor.clone(), wasm_override: Some(overrides), spawn_handle: Box::new(TaskExecutor::new()), client_config, + wasm_substitutes: WasmSubstitutes::new( + Default::default(), + executor.clone(), + backend.clone(), + ).unwrap(), }; let check = call_executor.check_override(onchain_code, &BlockId::Number(Default::default())) diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index a958cb6865c79..b294be2268997 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -118,7 +118,7 @@ pub struct Client where Block: BlockT { importing_block: RwLock>, block_rules: BlockRules, execution_extensions: ExecutionExtensions, - config: ClientConfig, + config: ClientConfig, telemetry: Option, _phantom: PhantomData, } @@ -159,10 +159,10 @@ pub fn new_in_mem( prometheus_registry: Option, telemetry: Option, spawn_handle: Box, - config: ClientConfig, + config: ClientConfig, ) -> sp_blockchain::Result, - LocalCallExecutor, E>, + LocalCallExecutor, E>, Block, RA >> where @@ -183,14 +183,28 @@ pub fn new_in_mem( } /// Relevant client configuration items relevant for the client. -#[derive(Debug,Clone,Default)] -pub struct ClientConfig { +#[derive(Debug, Clone)] +pub struct ClientConfig { /// Enable the offchain worker db. pub offchain_worker_enabled: bool, /// If true, allows access from the runtime to write into offchain worker db. pub offchain_indexing_api: bool, /// Path where WASM files exist to override the on-chain WASM. pub wasm_runtime_overrides: Option, + /// Map of WASM runtime substitute starting at the child of the given block until the runtime + /// version doesn't match anymore. + pub wasm_runtime_substitutes: HashMap>, +} + +impl Default for ClientConfig { + fn default() -> Self { + Self { + offchain_worker_enabled: false, + offchain_indexing_api: false, + wasm_runtime_overrides: None, + wasm_runtime_substitutes: HashMap::new(), + } + } } /// Create a client with the explicitly provided backend. @@ -204,8 +218,8 @@ pub fn new_with_backend( spawn_handle: Box, prometheus_registry: Option, telemetry: Option, - config: ClientConfig, -) -> sp_blockchain::Result, Block, RA>> + config: ClientConfig, +) -> sp_blockchain::Result, Block, RA>> where E: CodeExecutor + RuntimeInfo, S: BuildStorage, @@ -308,7 +322,7 @@ impl Client where execution_extensions: ExecutionExtensions, prometheus_registry: Option, telemetry: Option, - config: ClientConfig, + config: ClientConfig, ) -> sp_blockchain::Result { if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() { let genesis_storage = build_genesis_storage.build_storage() diff --git a/client/service/src/client/light.rs b/client/service/src/client/light.rs index 3b29a0e1a92ca..3a09bcbd78de5 100644 --- a/client/service/src/client/light.rs +++ b/client/service/src/client/light.rs @@ -45,7 +45,7 @@ pub fn new_light( Backend>, GenesisCallExecutor< Backend>, - LocalCallExecutor>, E> + LocalCallExecutor>, E> >, B, RA diff --git a/client/service/src/client/mod.rs b/client/service/src/client/mod.rs index 06f48048f8f2b..dd0b70b551bf4 100644 --- a/client/service/src/client/mod.rs +++ b/client/service/src/client/mod.rs @@ -51,6 +51,7 @@ mod call_executor; mod client; mod block_rules; mod wasm_override; +mod wasm_substitutes; pub use self::{ call_executor::LocalCallExecutor, diff --git a/client/service/src/client/wasm_substitutes.rs b/client/service/src/client/wasm_substitutes.rs new file mode 100644 index 0000000000000..e947e4566f332 --- /dev/null +++ b/client/service/src/client/wasm_substitutes.rs @@ -0,0 +1,179 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! # WASM substitutes + +use std::{collections::{HashMap, hash_map::DefaultHasher}, hash::Hasher as _, sync::Arc}; +use sp_core::traits::{FetchRuntimeCode, RuntimeCode}; +use sp_state_machine::BasicExternalities; +use sp_blockchain::{Result, HeaderBackend}; +use sc_executor::RuntimeInfo; +use sp_version::RuntimeVersion; +use sc_client_api::backend; +use sp_runtime::{traits::{NumberFor, Block as BlockT}, generic::BlockId}; +use parking_lot::RwLock; + +/// A wasm substitute for the on chain wasm. +#[derive(Debug)] +struct WasmSubstitute { + code: Vec, + hash: Vec, + /// The hash of the block from that on we should use the substitute. + block_hash: Block::Hash, + /// The block number of `block_hash`. If `None`, the block is still unknown. + block_number: RwLock>>, +} + +impl WasmSubstitute { + fn new( + code: Vec, + block_hash: Block::Hash, + backend: &impl backend::Backend, + ) -> Result { + let block_number = RwLock::new(backend.blockchain().number(block_hash)?); + let hash = make_hash(&code); + Ok(Self { code, hash, block_hash, block_number }) + } + + fn runtime_code(&self, heap_pages: Option) -> RuntimeCode { + RuntimeCode { + code_fetcher: self, + hash: self.hash.clone(), + heap_pages, + } + } + + /// Returns `true` when the substitute matches for the given `block_id`. + fn matches(&self, block_id: &BlockId, backend: &impl backend::Backend) -> bool { + let block_number = *self.block_number.read(); + let block_number = if let Some(block_number) = block_number { + block_number + } else { + let block_number = match backend.blockchain().number(self.block_hash) { + Ok(Some(n)) => n, + // still unknown + Ok(None) => return false, + Err(e) => { + log::debug!( + target: "wasm_substitutes", + "Failed to get block number for block hash {:?}: {:?}", + self.block_hash, + e, + ); + return false + }, + }; + *self.block_number.write() = Some(block_number); + block_number + }; + + let requested_block_number = backend.blockchain().block_number_from_id(&block_id).ok().flatten(); + + Some(block_number) <= requested_block_number + } +} + +/// Make a hash out of a byte string using the default rust hasher +fn make_hash(val: &K) -> Vec { + let mut state = DefaultHasher::new(); + val.hash(&mut state); + state.finish().to_le_bytes().to_vec() +} + +impl FetchRuntimeCode for WasmSubstitute { + fn fetch_runtime_code<'a>(&'a self) -> Option> { + Some(self.code.as_slice().into()) + } +} + +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum WasmSubstituteError { + #[error("Failed to get runtime version: {0}")] + VersionInvalid(String), +} + +impl From for sp_blockchain::Error { + fn from(err: WasmSubstituteError) -> Self { + Self::Application(Box::new(err)) + } +} + +/// Substitutes the on-chain wasm with some hard coded blobs. +#[derive(Debug)] +pub struct WasmSubstitutes { + /// spec_version -> WasmSubstitute + substitutes: Arc>>, + executor: Executor, + backend: Arc, +} + +impl Clone for WasmSubstitutes { + fn clone(&self) -> Self { + Self { + substitutes: self.substitutes.clone(), + executor: self.executor.clone(), + backend: self.backend.clone(), + } + } +} + +impl WasmSubstitutes +where + Executor: RuntimeInfo + Clone + 'static, + Backend: backend::Backend, + Block: BlockT, +{ + /// Create a new instance. + pub fn new( + substitutes: HashMap>, + executor: Executor, + backend: Arc, + ) -> Result { + let substitutes = substitutes.into_iter().map(|(parent_block_hash, code)| { + let substitute = WasmSubstitute::new(code, parent_block_hash, &*backend)?; + let version = Self::runtime_version(&executor, &substitute)?; + Ok((version.spec_version, substitute)) + }).collect::>>()?; + + Ok(Self { executor, substitutes: Arc::new(substitutes), backend }) + } + + /// Get a substitute. + /// + /// Returns `None` if there isn't any substitute required. + pub fn get( + &self, + spec: u32, + pages: Option, + block_id: &BlockId, + ) -> Option> { + let s = self.substitutes.get(&spec)?; + s.matches(block_id, &*self.backend).then(|| s.runtime_code(pages)) + } + + fn runtime_version( + executor: &Executor, + code: &WasmSubstitute, + ) -> Result { + let mut ext = BasicExternalities::default(); + executor.runtime_version(&mut ext, &code.runtime_code(None)) + .map_err(|e| WasmSubstituteError::VersionInvalid(format!("{:?}", e)).into()) + } +} + diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 0234f43513d56..55ff989bb93c0 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1822,7 +1822,7 @@ fn cleans_up_closed_notification_sinks_on_block_import() { type TestClient = Client< in_mem::Backend, - LocalCallExecutor, sc_executor::NativeExecutor>, + LocalCallExecutor, sc_executor::NativeExecutor>, substrate_test_runtime_client::runtime::Block, substrate_test_runtime_client::runtime::RuntimeApi, >; diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index d8cc40d5561c1..e343181505c98 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -244,7 +244,7 @@ impl TestClientBuilder TestClientBuilder< Block, - client::LocalCallExecutor>, + client::LocalCallExecutor>, Backend, G, > { @@ -255,7 +255,7 @@ impl TestClientBuilder< ) -> ( client::Client< Backend, - client::LocalCallExecutor>, + client::LocalCallExecutor>, Block, RuntimeApi >, diff --git a/test-utils/runtime/client/src/lib.rs b/test-utils/runtime/client/src/lib.rs index 5a66cde62e566..a9ff26a5adf8d 100644 --- a/test-utils/runtime/client/src/lib.rs +++ b/test-utils/runtime/client/src/lib.rs @@ -67,6 +67,7 @@ pub type Backend = substrate_test_client::Backend /// Test client executor. pub type Executor = client::LocalCallExecutor< + substrate_test_runtime::Block, Backend, NativeExecutor, >; @@ -78,6 +79,7 @@ pub type LightBackend = substrate_test_client::LightBackend, HashFor @@ -159,7 +161,11 @@ pub type TestClientBuilder = substrate_test_client::TestClientBuilder< /// Test client type with `LocalExecutor` and generic Backend. pub type Client = client::Client< B, - client::LocalCallExecutor>, + client::LocalCallExecutor< + substrate_test_runtime::Block, + B, + sc_executor::NativeExecutor + >, substrate_test_runtime::Block, substrate_test_runtime::RuntimeApi, >; @@ -245,7 +251,11 @@ pub trait TestClientBuilderExt: Sized { } impl TestClientBuilderExt for TestClientBuilder< - client::LocalCallExecutor>, + client::LocalCallExecutor< + substrate_test_runtime::Block, + B, + sc_executor::NativeExecutor + >, B > where B: sc_client_api::backend::Backend + 'static,