use sc_client_api::backend;
use sc_executor::RuntimeVersionOf;
use sp_blockchain::{HeaderBackend, Result};
use sp_core::traits::{FetchRuntimeCode, RuntimeCode, WrappedRuntimeCode};
use sp_runtime::traits::{Block as BlockT, NumberFor};
use sp_state_machine::BasicExternalities;
use sp_version::RuntimeVersion;
use std::{
collections::{hash_map::DefaultHasher, HashMap},
hash::Hasher as _,
sync::Arc,
};
#[derive(Debug)]
struct WasmSubstitute<Block: BlockT> {
code: Vec<u8>,
hash: Vec<u8>,
block_number: NumberFor<Block>,
version: RuntimeVersion,
}
impl<Block: BlockT> WasmSubstitute<Block> {
fn new(code: Vec<u8>, block_number: NumberFor<Block>, version: RuntimeVersion) -> Self {
let hash = make_hash(&code);
Self { code, hash, block_number, version }
}
fn runtime_code(&self, heap_pages: Option<u64>) -> RuntimeCode {
RuntimeCode { code_fetcher: self, hash: self.hash.clone(), heap_pages }
}
fn matches(
&self,
hash: <Block as BlockT>::Hash,
backend: &impl backend::Backend<Block>,
) -> bool {
let requested_block_number = backend.blockchain().number(hash).ok().flatten();
Some(self.block_number) <= requested_block_number
}
}
fn make_hash<K: std::hash::Hash + ?Sized>(val: &K) -> Vec<u8> {
let mut state = DefaultHasher::new();
val.hash(&mut state);
state.finish().to_le_bytes().to_vec()
}
impl<Block: BlockT> FetchRuntimeCode for WasmSubstitute<Block> {
fn fetch_runtime_code(&self) -> Option<std::borrow::Cow<[u8]>> {
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<WasmSubstituteError> for sp_blockchain::Error {
fn from(err: WasmSubstituteError) -> Self {
Self::Application(Box::new(err))
}
}
#[derive(Debug)]
pub struct WasmSubstitutes<Block: BlockT, Executor, Backend> {
substitutes: Arc<HashMap<u32, WasmSubstitute<Block>>>,
executor: Arc<Executor>,
backend: Arc<Backend>,
}
impl<Block: BlockT, Executor: Clone, Backend> Clone for WasmSubstitutes<Block, Executor, Backend> {
fn clone(&self) -> Self {
Self {
substitutes: self.substitutes.clone(),
executor: self.executor.clone(),
backend: self.backend.clone(),
}
}
}
impl<Executor, Backend, Block> WasmSubstitutes<Block, Executor, Backend>
where
Executor: RuntimeVersionOf,
Backend: backend::Backend<Block>,
Block: BlockT,
{
pub fn new(
substitutes: HashMap<NumberFor<Block>, Vec<u8>>,
executor: Arc<Executor>,
backend: Arc<Backend>,
) -> Result<Self> {
let substitutes = substitutes
.into_iter()
.map(|(block_number, code)| {
let runtime_code = RuntimeCode {
code_fetcher: &WrappedRuntimeCode((&code).into()),
heap_pages: None,
hash: make_hash(&code),
};
let version = Self::runtime_version(&executor, &runtime_code)?;
let spec_version = version.spec_version;
let substitute = WasmSubstitute::new(code, block_number, version);
Ok((spec_version, substitute))
})
.collect::<Result<HashMap<_, _>>>()?;
Ok(Self { executor, substitutes: Arc::new(substitutes), backend })
}
pub fn get(
&self,
spec: u32,
pages: Option<u64>,
hash: Block::Hash,
) -> Option<(RuntimeCode<'_>, RuntimeVersion)> {
let s = self.substitutes.get(&spec)?;
s.matches(hash, &*self.backend)
.then(|| (s.runtime_code(pages), s.version.clone()))
}
fn runtime_version(executor: &Executor, code: &RuntimeCode) -> Result<RuntimeVersion> {
let mut ext = BasicExternalities::default();
executor
.runtime_version(&mut ext, code)
.map_err(|e| WasmSubstituteError::VersionInvalid(e.to_string()).into())
}
}