Skip to content
This repository was archived by the owner on Feb 21, 2024. It is now read-only.

Commit

Permalink
Introduce code_substitute (paritytech#8898)
Browse files Browse the repository at this point in the history
This introduces a new field `code_substitute` into the chain spec. This
can be used to substitute the on-chain wasm starting from a given block
until there is another wasm on chain (determined through the
`spec_version`). This can be used to fix broken on chain wasm runtimes.
  • Loading branch information
bkchr authored and nazar-pc committed Aug 8, 2021
1 parent d185759 commit 9dd6fdc
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 43 deletions.
4 changes: 2 additions & 2 deletions bin/node/testing/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub type Backend = sc_client_db::Backend<node_primitives::Block>;
/// Test client type.
pub type Client = client::Client<
Backend,
client::LocalCallExecutor<Backend, Executor>,
client::LocalCallExecutor<node_primitives::Block, Backend, Executor>,
node_primitives::Block,
node_runtime::RuntimeApi,
>;
Expand Down Expand Up @@ -63,7 +63,7 @@ pub trait TestClientBuilderExt: Sized {

impl TestClientBuilderExt for substrate_test_client::TestClientBuilder<
node_primitives::Block,
client::LocalCallExecutor<Backend, Executor>,
client::LocalCallExecutor<node_primitives::Block, Backend, Executor>,
Backend,
GenesisParameters,
> {
Expand Down
13 changes: 12 additions & 1 deletion client/chain-spec/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -160,6 +160,12 @@ struct ClientSpec<E> {
#[serde(skip_serializing)]
genesis: serde::de::IgnoredAny,
light_sync_state: Option<SerializableLightSyncState>,
/// 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<String, Bytes>,
}

/// A type denoting empty extensions.
Expand Down Expand Up @@ -249,6 +255,7 @@ impl<G, E> ChainSpec<G, E> {
consensus_engine: (),
genesis: Default::default(),
light_sync_state: None,
code_substitutes: HashMap::new(),
};

ChainSpec {
Expand Down Expand Up @@ -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<String, Vec<u8>> {
self.client_spec.code_substitutes.iter().map(|(h, c)| (h.clone(), c.0.clone())).collect()
}
}

/// Hardcoded infomation that allows light clients to sync quickly.
Expand Down
2 changes: 2 additions & 0 deletions client/chain-spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Vec<u8>>;
}

impl std::fmt::Debug for dyn ChainSpec {
Expand Down
25 changes: 20 additions & 5 deletions client/service/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -150,6 +150,7 @@ pub type TFullBackend<TBl> = sc_client_db::Backend<TBl>;

/// Full client call executor type.
pub type TFullCallExecutor<TBl, TExecDisp> = crate::client::LocalCallExecutor<
TBl,
sc_client_db::Backend<TBl>,
NativeExecutor<TExecDisp>,
>;
Expand All @@ -172,6 +173,7 @@ pub type TLightCallExecutor<TBl, TExecDisp> = sc_light::GenesisCallExecutor<
HashFor<TBl>
>,
crate::client::LocalCallExecutor<
TBl,
sc_light::Backend<
sc_client_db::light::LightStorage<TBl>,
HashFor<TBl>
Expand Down Expand Up @@ -206,7 +208,7 @@ pub type TLightClientWithBackend<TBl, TRtApi, TExecDisp, TBackend> = Client<
TBackend,
sc_light::GenesisCallExecutor<
TBackend,
crate::client::LocalCallExecutor<TBackend, NativeExecutor<TExecDisp>>,
crate::client::LocalCallExecutor<TBl, TBackend, NativeExecutor<TExecDisp>>,
>,
TBl,
TRtApi,
Expand Down Expand Up @@ -295,6 +297,7 @@ pub fn new_full_client<TBl, TRtApi, TExecDisp>(
) -> Result<TFullClient<TBl, TRtApi, TExecDisp>, Error> where
TBl: BlockT,
TExecDisp: NativeExecutionDispatch + 'static,
TBl::Hash: FromStr,
{
new_full_parts(config, telemetry).map(|parts| parts.0)
}
Expand All @@ -303,9 +306,10 @@ pub fn new_full_client<TBl, TRtApi, TExecDisp>(
pub fn new_full_parts<TBl, TRtApi, TExecDisp>(
config: &Configuration,
telemetry: Option<TelemetryHandle>,
) -> Result<TFullParts<TBl, TRtApi, TExecDisp>, Error> where
) -> Result<TFullParts<TBl, TRtApi, TExecDisp>, Error> where
TBl: BlockT,
TExecDisp: NativeExecutionDispatch + 'static,
TBl::Hash: FromStr,
{
let keystore_container = KeystoreContainer::new(&config.keystore)?;

Expand Down Expand Up @@ -349,6 +353,16 @@ pub fn new_full_parts<TBl, TRtApi, TExecDisp>(
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::<Result<std::collections::HashMap<_, _>, Error>>()?;

let client = new_client(
backend.clone(),
executor,
Expand All @@ -363,6 +377,7 @@ pub fn new_full_parts<TBl, TRtApi, TExecDisp>(
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,
},
)?;

Expand Down Expand Up @@ -453,11 +468,11 @@ pub fn new_client<E, Block, RA>(
spawn_handle: Box<dyn SpawnNamed>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
config: ClientConfig,
config: ClientConfig<Block>,
) -> Result<
crate::client::Client<
Backend<Block>,
crate::client::LocalCallExecutor<Backend<Block>, E>,
crate::client::LocalCallExecutor<Block, Backend<Block>, E>,
Block,
RA,
>,
Expand Down
53 changes: 32 additions & 21 deletions client/service/src/client/call_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<B, E> {
pub struct LocalCallExecutor<Block: BlockT, B, E> {
backend: Arc<B>,
executor: E,
wasm_override: Option<WasmOverride<E>>,
wasm_substitutes: WasmSubstitutes<Block, E, B>,
spawn_handle: Box<dyn SpawnNamed>,
client_config: ClientConfig,
client_config: ClientConfig<Block>,
}

impl<B, E> LocalCallExecutor<B, E>
impl<Block: BlockT, B, E> LocalCallExecutor<Block, B, E>
where
E: CodeExecutor + RuntimeInfo + Clone + 'static
E: CodeExecutor + RuntimeInfo + Clone + 'static,
B: backend::Backend<Block>,
{
/// Creates new instance of local call executor.
pub fn new(
backend: Arc<B>,
executor: E,
spawn_handle: Box<dyn SpawnNamed>,
client_config: ClientConfig,
client_config: ClientConfig<Block>,
) -> sp_blockchain::Result<Self> {
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<Block>,
Expand All @@ -81,16 +90,16 @@ where
Block: BlockT,
B: backend::Backend<Block>,
{
let spec = self.runtime_version(id)?.spec_version;
let code = if let Some(d) = self.wasm_override
.as_ref()
.map::<sp_blockchain::Result<Option<RuntimeCode>>, _>(|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",
Expand All @@ -104,19 +113,20 @@ where
}
}

impl<B, E> Clone for LocalCallExecutor<B, E> where E: Clone {
impl<Block: BlockT, B, E> Clone for LocalCallExecutor<Block, B, E> where E: Clone {
fn clone(&self) -> Self {
LocalCallExecutor {
backend: self.backend.clone(),
executor: self.executor.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<B, E, Block> CallExecutor<Block> for LocalCallExecutor<B, E>
impl<B, E, Block> CallExecutor<Block> for LocalCallExecutor<Block, B, E>
where
B: backend::Backend<Block>,
E: CodeExecutor + RuntimeInfo + Clone + 'static,
Expand Down Expand Up @@ -314,7 +324,7 @@ where
}
}

impl<B, E, Block> sp_version::GetRuntimeVersion<Block> for LocalCallExecutor<B, E>
impl<Block, B, E> sp_version::GetRuntimeVersion<Block> for LocalCallExecutor<Block, B, E>
where
B: backend::Backend<Block>,
E: CodeExecutor + RuntimeInfo + Clone + 'static,
Expand Down Expand Up @@ -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::<
Expand All @@ -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()))
Expand Down
30 changes: 22 additions & 8 deletions client/service/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub struct Client<B, E, Block, RA> where Block: BlockT {
importing_block: RwLock<Option<Block::Hash>>,
block_rules: BlockRules<Block>,
execution_extensions: ExecutionExtensions<Block>,
config: ClientConfig,
config: ClientConfig<Block>,
telemetry: Option<TelemetryHandle>,
_phantom: PhantomData<RA>,
}
Expand Down Expand Up @@ -159,10 +159,10 @@ pub fn new_in_mem<E, Block, S, RA>(
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
spawn_handle: Box<dyn SpawnNamed>,
config: ClientConfig,
config: ClientConfig<Block>,
) -> sp_blockchain::Result<Client<
in_mem::Backend<Block>,
LocalCallExecutor<in_mem::Backend<Block>, E>,
LocalCallExecutor<Block, in_mem::Backend<Block>, E>,
Block,
RA
>> where
Expand All @@ -183,14 +183,28 @@ pub fn new_in_mem<E, Block, S, RA>(
}

/// Relevant client configuration items relevant for the client.
#[derive(Debug,Clone,Default)]
pub struct ClientConfig {
#[derive(Debug, Clone)]
pub struct ClientConfig<Block: BlockT> {
/// 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<PathBuf>,
/// 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<Block::Hash, Vec<u8>>,
}

impl<Block: BlockT> Default for ClientConfig<Block> {
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.
Expand All @@ -204,8 +218,8 @@ pub fn new_with_backend<B, E, Block, S, RA>(
spawn_handle: Box<dyn SpawnNamed>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
config: ClientConfig,
) -> sp_blockchain::Result<Client<B, LocalCallExecutor<B, E>, Block, RA>>
config: ClientConfig<Block>,
) -> sp_blockchain::Result<Client<B, LocalCallExecutor<Block, B, E>, Block, RA>>
where
E: CodeExecutor + RuntimeInfo,
S: BuildStorage,
Expand Down Expand Up @@ -308,7 +322,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
execution_extensions: ExecutionExtensions<Block>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
config: ClientConfig,
config: ClientConfig<Block>,
) -> sp_blockchain::Result<Self> {
if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() {
let genesis_storage = build_genesis_storage.build_storage()
Expand Down
2 changes: 1 addition & 1 deletion client/service/src/client/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn new_light<B, S, RA, E>(
Backend<S, HashFor<B>>,
GenesisCallExecutor<
Backend<S, HashFor<B>>,
LocalCallExecutor<Backend<S, HashFor<B>>, E>
LocalCallExecutor<B, Backend<S, HashFor<B>>, E>
>,
B,
RA
Expand Down
1 change: 1 addition & 0 deletions client/service/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ mod call_executor;
mod client;
mod block_rules;
mod wasm_override;
mod wasm_substitutes;

pub use self::{
call_executor::LocalCallExecutor,
Expand Down
Loading

0 comments on commit 9dd6fdc

Please sign in to comment.