chainql_core/
wasm.rs

1use std::borrow::Cow;
2use std::path::{Path, PathBuf};
3
4use jrsonnet_evaluator::{
5	error::ErrorKind::RuntimeError, function::builtin, typed::Typed, val::ThunkValue, ObjValue,
6	Result, Val,
7};
8use jrsonnet_gcmodule::{Cc, Trace};
9use parity_scale_codec::Decode;
10use sc_executor::{RuntimeVersionOf, WasmExecutor};
11use sp_core::blake2_256;
12use sp_core::traits::{CodeExecutor, RuntimeCode, WrappedRuntimeCode};
13
14type HostFunctions = (
15	sp_io::SubstrateHostFunctions,
16	cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
17);
18
19use crate::Hex;
20
21#[derive(Trace)]
22pub struct RuntimeContainer {
23	#[trace(skip)]
24	code: WrappedRuntimeCode<'static>,
25	#[trace(skip)]
26	hash: [u8; 32],
27	#[trace(skip)]
28	executor: WasmExecutor<HostFunctions>,
29}
30
31#[derive(Typed)]
32pub struct RuntimeVersion {
33	spec_name: String,
34	spec_version: u32,
35	state_version: u8,
36	transaction_version: u32,
37}
38
39impl RuntimeContainer {
40	pub fn new(code: Vec<u8>, cache_path: Option<&Path>) -> Self {
41		let mut executor = <WasmExecutor<HostFunctions>>::builder()
42			// chainql is single-threaded
43			.with_max_runtime_instances(1)
44			// allow to work with runtimes with custom set of host functions
45			.with_allow_missing_host_functions(true);
46		if let Some(cache_path) = cache_path {
47			executor = executor.with_cache_path(cache_path);
48		};
49		let executor = executor.build();
50		Self {
51			hash: blake2_256(&code),
52			code: WrappedRuntimeCode(Cow::Owned(code)),
53			executor,
54		}
55	}
56	fn runtime_code(&self) -> RuntimeCode<'_> {
57		RuntimeCode {
58			code_fetcher: &self.code,
59			heap_pages: Some(100),
60			hash: self.hash.to_vec(),
61		}
62	}
63	pub fn version(&self) -> Result<RuntimeVersion> {
64		let mut ext = sp_state_machine::BasicExternalities::new_empty();
65		let version =
66			RuntimeVersionOf::runtime_version(&self.executor, &mut ext, &self.runtime_code())
67				.map_err(|e| RuntimeError(format!("{e}").into()))?;
68
69		Ok(RuntimeVersion {
70			spec_name: version.spec_name.to_string(),
71			spec_version: version.spec_version,
72			state_version: match version.state_version() {
73				sp_runtime::StateVersion::V0 => 0,
74				sp_runtime::StateVersion::V1 => 1,
75			},
76			transaction_version: version.transaction_version,
77		})
78	}
79	pub fn metadata(&self) -> Result<Vec<u8>> {
80		let mut ext = sp_state_machine::BasicExternalities::new_empty();
81		let (result, _native_used) = self.executor.call(
82			&mut ext,
83			&self.runtime_code(),
84			"Metadata_metadata",
85			&[],
86			sp_core::traits::CallContext::Onchain,
87		);
88		let result = result.expect("metadata is implemented for substrate chains");
89		let result = <Vec<u8>>::decode(&mut result.as_slice()).expect("valid output");
90		Ok(result)
91	}
92}
93
94#[builtin(fields(
95	cache_path: Option<PathBuf>,
96))]
97pub fn builtin_runtime_wasm(this: &builtin_runtime_wasm, data: Hex) -> Result<ObjValue> {
98	let runtime = Cc::new(RuntimeContainer::new(data.0, this.cache_path.as_deref()));
99
100	#[derive(Trace)]
101	struct RuntimeVersionThunk {
102		runtime: Cc<RuntimeContainer>,
103	}
104	impl ThunkValue for RuntimeVersionThunk {
105		type Output = Val;
106		fn get(self: Box<Self>) -> Result<Val> {
107			RuntimeVersion::into_untyped(self.runtime.version()?)
108		}
109	}
110	#[derive(Trace)]
111	struct MetadataThunk {
112		runtime: Cc<RuntimeContainer>,
113	}
114	impl ThunkValue for MetadataThunk {
115		type Output = Val;
116		fn get(self: Box<Self>) -> Result<Val> {
117			self.runtime.metadata().map(Hex).and_then(Hex::into_untyped)
118		}
119	}
120
121	let mut out = ObjValue::builder();
122
123	out.field("version").try_thunk(RuntimeVersionThunk {
124		runtime: runtime.clone(),
125	})?;
126	out.field("metadata").try_thunk(MetadataThunk {
127		runtime: runtime.clone(),
128	})?;
129
130	Ok(out.build())
131}