referrerpolicy=no-referrer-when-downgrade

sc_service/client/
wasm_substitutes.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//! # WASM substitutes
20
21use sc_client_api::backend;
22use sc_executor::RuntimeVersionOf;
23use sp_blockchain::{HeaderBackend, Result};
24use sp_core::traits::{FetchRuntimeCode, RuntimeCode, WrappedRuntimeCode};
25use sp_runtime::traits::{Block as BlockT, NumberFor};
26use sp_state_machine::BasicExternalities;
27use sp_version::RuntimeVersion;
28use std::{
29	collections::{hash_map::DefaultHasher, HashMap},
30	hash::Hasher as _,
31	sync::Arc,
32};
33
34/// A wasm substitute for the on chain wasm.
35#[derive(Debug)]
36struct WasmSubstitute<Block: BlockT> {
37	code: Vec<u8>,
38	hash: Vec<u8>,
39	/// The block number on which we should start using the substitute.
40	block_number: NumberFor<Block>,
41	version: RuntimeVersion,
42}
43
44impl<Block: BlockT> WasmSubstitute<Block> {
45	fn new(code: Vec<u8>, block_number: NumberFor<Block>, version: RuntimeVersion) -> Self {
46		let hash = make_hash(&code);
47		Self { code, hash, block_number, version }
48	}
49
50	fn runtime_code(&self, heap_pages: Option<u64>) -> RuntimeCode {
51		RuntimeCode { code_fetcher: self, hash: self.hash.clone(), heap_pages }
52	}
53
54	/// Returns `true` when the substitute matches for the given `hash`.
55	fn matches(
56		&self,
57		hash: <Block as BlockT>::Hash,
58		backend: &impl backend::Backend<Block>,
59	) -> bool {
60		let requested_block_number = backend.blockchain().number(hash).ok().flatten();
61
62		Some(self.block_number) <= requested_block_number
63	}
64}
65
66/// Make a hash out of a byte string using the default rust hasher
67fn make_hash<K: std::hash::Hash + ?Sized>(val: &K) -> Vec<u8> {
68	let mut state = DefaultHasher::new();
69	val.hash(&mut state);
70	state.finish().to_le_bytes().to_vec()
71}
72
73impl<Block: BlockT> FetchRuntimeCode for WasmSubstitute<Block> {
74	fn fetch_runtime_code(&self) -> Option<std::borrow::Cow<[u8]>> {
75		Some(self.code.as_slice().into())
76	}
77}
78
79#[derive(Debug, thiserror::Error)]
80#[allow(missing_docs)]
81pub enum WasmSubstituteError {
82	#[error("Failed to get runtime version: {0}")]
83	VersionInvalid(String),
84}
85
86impl From<WasmSubstituteError> for sp_blockchain::Error {
87	fn from(err: WasmSubstituteError) -> Self {
88		Self::Application(Box::new(err))
89	}
90}
91
92/// Substitutes the on-chain wasm with some hard coded blobs.
93#[derive(Debug)]
94pub struct WasmSubstitutes<Block: BlockT, Executor, Backend> {
95	/// spec_version -> WasmSubstitute
96	substitutes: Arc<HashMap<u32, WasmSubstitute<Block>>>,
97	executor: Arc<Executor>,
98	backend: Arc<Backend>,
99}
100
101impl<Block: BlockT, Executor: Clone, Backend> Clone for WasmSubstitutes<Block, Executor, Backend> {
102	fn clone(&self) -> Self {
103		Self {
104			substitutes: self.substitutes.clone(),
105			executor: self.executor.clone(),
106			backend: self.backend.clone(),
107		}
108	}
109}
110
111impl<Executor, Backend, Block> WasmSubstitutes<Block, Executor, Backend>
112where
113	Executor: RuntimeVersionOf,
114	Backend: backend::Backend<Block>,
115	Block: BlockT,
116{
117	/// Create a new instance.
118	pub fn new(
119		substitutes: HashMap<NumberFor<Block>, Vec<u8>>,
120		executor: Arc<Executor>,
121		backend: Arc<Backend>,
122	) -> Result<Self> {
123		let substitutes = substitutes
124			.into_iter()
125			.map(|(block_number, code)| {
126				let runtime_code = RuntimeCode {
127					code_fetcher: &WrappedRuntimeCode((&code).into()),
128					heap_pages: None,
129					hash: make_hash(&code),
130				};
131				let version = Self::runtime_version(&executor, &runtime_code)?;
132				let spec_version = version.spec_version;
133
134				let substitute = WasmSubstitute::new(code, block_number, version);
135
136				Ok((spec_version, substitute))
137			})
138			.collect::<Result<HashMap<_, _>>>()?;
139
140		Ok(Self { executor, substitutes: Arc::new(substitutes), backend })
141	}
142
143	/// Get a substitute.
144	///
145	/// Returns `None` if there isn't any substitute required.
146	pub fn get(
147		&self,
148		spec: u32,
149		pages: Option<u64>,
150		hash: Block::Hash,
151	) -> Option<(RuntimeCode<'_>, RuntimeVersion)> {
152		let s = self.substitutes.get(&spec)?;
153		s.matches(hash, &*self.backend)
154			.then(|| (s.runtime_code(pages), s.version.clone()))
155	}
156
157	fn runtime_version(executor: &Executor, code: &RuntimeCode) -> Result<RuntimeVersion> {
158		let mut ext = BasicExternalities::default();
159		executor
160			.runtime_version(&mut ext, code)
161			.map_err(|e| WasmSubstituteError::VersionInvalid(e.to_string()).into())
162	}
163}