1
// Copyright 2024 Moonbeam foundation
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # WASM substitutes
18

            
19
use sc_client_api::backend;
20
use sc_executor::RuntimeVersionOf;
21
use sp_blockchain::{HeaderBackend, Result};
22
use sp_core::traits::{FetchRuntimeCode, RuntimeCode, WrappedRuntimeCode};
23
use sp_runtime::traits::{Block as BlockT, NumberFor};
24
use sp_state_machine::BasicExternalities;
25
use sp_version::RuntimeVersion;
26
use std::{
27
	collections::{hash_map::DefaultHasher, HashMap},
28
	hash::Hasher as _,
29
	sync::Arc,
30
};
31

            
32
/// A wasm substitute for the on chain wasm.
33
#[derive(Debug)]
34
struct WasmSubstitute<Block: BlockT> {
35
	code: Vec<u8>,
36
	hash: Vec<u8>,
37
	/// The block number on which we should start using the substitute.
38
	block_number: NumberFor<Block>,
39
	version: RuntimeVersion,
40
}
41

            
42
impl<Block: BlockT> WasmSubstitute<Block> {
43
	fn new(code: Vec<u8>, block_number: NumberFor<Block>, version: RuntimeVersion) -> Self {
44
		let hash = make_hash(&code);
45
		Self {
46
			code,
47
			hash,
48
			block_number,
49
			version,
50
		}
51
	}
52

            
53
	fn runtime_code(&self, heap_pages: Option<u64>) -> RuntimeCode {
54
		RuntimeCode {
55
			code_fetcher: self,
56
			hash: self.hash.clone(),
57
			heap_pages,
58
		}
59
	}
60

            
61
	/// Returns `true` when the substitute matches for the given `hash`.
62
	fn matches(
63
		&self,
64
		hash: <Block as BlockT>::Hash,
65
		backend: &impl backend::Backend<Block>,
66
	) -> bool {
67
		let requested_block_number = backend.blockchain().number(hash).ok().flatten();
68

            
69
		Some(self.block_number) <= requested_block_number
70
	}
71
}
72

            
73
/// Make a hash out of a byte string using the default rust hasher
74
fn make_hash<K: std::hash::Hash + ?Sized>(val: &K) -> Vec<u8> {
75
	let mut state = DefaultHasher::new();
76
	val.hash(&mut state);
77
	state.finish().to_le_bytes().to_vec()
78
}
79

            
80
impl<Block: BlockT> FetchRuntimeCode for WasmSubstitute<Block> {
81
	fn fetch_runtime_code(&self) -> Option<std::borrow::Cow<[u8]>> {
82
		Some(self.code.as_slice().into())
83
	}
84
}
85

            
86
#[derive(Debug, thiserror::Error)]
87
#[allow(missing_docs)]
88
pub enum WasmSubstituteError {
89
	#[error("Failed to get runtime version: {0}")]
90
	VersionInvalid(String),
91
}
92

            
93
impl From<WasmSubstituteError> for sp_blockchain::Error {
94
	fn from(err: WasmSubstituteError) -> Self {
95
		Self::Application(Box::new(err))
96
	}
97
}
98

            
99
/// Substitutes the on-chain wasm with some hard coded blobs.
100
#[derive(Debug)]
101
pub struct WasmSubstitutes<Block: BlockT, Executor, Backend> {
102
	/// spec_version -> WasmSubstitute
103
	substitutes: Arc<HashMap<u32, WasmSubstitute<Block>>>,
104
	executor: Executor,
105
	backend: Arc<Backend>,
106
}
107

            
108
impl<Block: BlockT, Executor: Clone, Backend> Clone for WasmSubstitutes<Block, Executor, Backend> {
109
	fn clone(&self) -> Self {
110
		Self {
111
			substitutes: self.substitutes.clone(),
112
			executor: self.executor.clone(),
113
			backend: self.backend.clone(),
114
		}
115
	}
116
}
117

            
118
impl<Executor, Backend, Block> WasmSubstitutes<Block, Executor, Backend>
119
where
120
	Executor: RuntimeVersionOf + Clone + 'static,
121
	Backend: backend::Backend<Block>,
122
	Block: BlockT,
123
{
124
	/// Create a new instance.
125
	pub fn new(
126
		substitutes: HashMap<NumberFor<Block>, Vec<u8>>,
127
		executor: Executor,
128
		backend: Arc<Backend>,
129
	) -> Result<Self> {
130
		let substitutes = substitutes
131
			.into_iter()
132
			.map(|(block_number, code)| {
133
				let runtime_code = RuntimeCode {
134
					code_fetcher: &WrappedRuntimeCode((&code).into()),
135
					heap_pages: None,
136
					hash: make_hash(&code),
137
				};
138
				let version = Self::runtime_version(&executor, &runtime_code)?;
139
				let spec_version = version.spec_version;
140

            
141
				let substitute = WasmSubstitute::new(code, block_number, version);
142

            
143
				Ok((spec_version, substitute))
144
			})
145
			.collect::<Result<HashMap<_, _>>>()?;
146

            
147
		Ok(Self {
148
			executor,
149
			substitutes: Arc::new(substitutes),
150
			backend,
151
		})
152
	}
153

            
154
	/// Get a substitute.
155
	///
156
	/// Returns `None` if there isn't any substitute required.
157
	pub fn get(
158
		&self,
159
		spec: u32,
160
		pages: Option<u64>,
161
		hash: Block::Hash,
162
	) -> Option<(RuntimeCode<'_>, RuntimeVersion)> {
163
		let s = self.substitutes.get(&spec)?;
164
		s.matches(hash, &*self.backend)
165
			.then(|| (s.runtime_code(pages), s.version.clone()))
166
	}
167

            
168
	fn runtime_version(executor: &Executor, code: &RuntimeCode) -> Result<RuntimeVersion> {
169
		let mut ext = BasicExternalities::default();
170
		executor
171
			.runtime_version(&mut ext, code)
172
			.map_err(|e| WasmSubstituteError::VersionInvalid(e.to_string()).into())
173
	}
174
}