diff --git a/Cargo.lock b/Cargo.lock index 71cb808d83d4c..9019acfcbb239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7353,7 +7353,6 @@ dependencies = [ "sp-core", "sp-externalities", "sp-io", - "sp-maybe-compressed-blob", "sp-panic-handler", "sp-runtime", "sp-runtime-interface", @@ -7381,6 +7380,7 @@ dependencies = [ "pwasm-utils 0.14.0", "sp-allocator", "sp-core", + "sp-maybe-compressed-blob", "sp-serializer", "sp-wasm-interface", "thiserror", @@ -8883,6 +8883,7 @@ dependencies = [ "sp-core", "sp-externalities", "sp-keystore", + "sp-maybe-compressed-blob", "sp-runtime-interface", "sp-state-machine", "sp-std", @@ -9063,6 +9064,7 @@ name = "sp-runtime-interface-test" version = "2.0.0" dependencies = [ "sc-executor", + "sc-executor-common", "sp-core", "sp-io", "sp-runtime", @@ -9291,6 +9293,19 @@ dependencies = [ "serde", "sp-runtime", "sp-std", + "sp-version-proc-macro", +] + +[[package]] +name = "sp-version-proc-macro" +version = "3.0.0" +dependencies = [ + "parity-scale-codec", + "proc-macro-crate 1.0.0", + "proc-macro2", + "quote", + "sp-version", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1b35c7181d17d..9d7017be1d0de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,6 +182,7 @@ members = [ "primitives/trie", "primitives/utils", "primitives/version", + "primitives/version/proc-macro", "primitives/wasm-interface", "test-utils/client", "test-utils/derive", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 178918266a7fc..b928f8d3410ef 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -94,6 +94,7 @@ pub mod opaque { // To learn more about runtime versioning and what each of the following value means: // https://substrate.dev/docs/en/knowledgebase/runtime/upgrades#runtime-versioning +#[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node-template"), impl_name: create_runtime_str!("node-template"), diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 52eb5e42bd4da..a20437c25659b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -105,6 +105,7 @@ pub fn wasm_binary_unwrap() -> &'static [u8] { } /// Runtime version. +#[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), impl_name: create_runtime_str!("substrate-node"), diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index e9f0fa14d8e7e..f678029d06743 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -30,7 +30,6 @@ sp-api = { version = "3.0.0", path = "../../primitives/api" } sp-wasm-interface = { version = "3.0.0", path = "../../primitives/wasm-interface" } sp-runtime-interface = { version = "3.0.0", path = "../../primitives/runtime-interface" } sp-externalities = { version = "0.9.0", path = "../../primitives/externalities" } -sp-maybe-compressed-blob = { version = "3.0.0", path = "../../primitives/maybe-compressed-blob" } sc-executor-common = { version = "0.9.0", path = "common" } sc-executor-wasmi = { version = "0.9.0", path = "wasmi" } sc-executor-wasmtime = { version = "0.9.0", path = "wasmtime", optional = true } diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 95c090686e83b..9f9ec989431fb 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -22,6 +22,7 @@ wasmi = "0.6.2" sp-core = { version = "3.0.0", path = "../../../primitives/core" } sp-allocator = { version = "3.0.0", path = "../../../primitives/allocator" } sp-wasm-interface = { version = "3.0.0", path = "../../../primitives/wasm-interface" } +sp-maybe-compressed-blob = { version = "3.0.0", path = "../../../primitives/maybe-compressed-blob" } sp-serializer = { version = "3.0.0", path = "../../../primitives/serializer" } thiserror = "1.0.21" diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index d90a48fde0c81..6541f9f5d966d 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -27,6 +27,17 @@ pub struct RuntimeBlob { } impl RuntimeBlob { + /// Create `RuntimeBlob` from the given wasm code. Will attempt to decompress the code before + /// deserializing it. + /// + /// See [`sp_maybe_compressed_blob`] for details about decompression. + pub fn uncompress_if_needed(wasm_code: &[u8]) -> Result<Self, WasmError> { + use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT; + let wasm_code = sp_maybe_compressed_blob::decompress(wasm_code, CODE_BLOB_BOMB_LIMIT) + .map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?; + Self::new(&wasm_code) + } + /// Create `RuntimeBlob` from the given wasm code. /// /// Returns `Err` if the wasm code cannot be deserialized. @@ -85,9 +96,23 @@ impl RuntimeBlob { }) } + /// Scans the wasm blob for the first section with the name that matches the given. Returns the + /// contents of the custom section if found or `None` otherwise. + pub fn custom_section_contents(&self, section_name: &str) -> Option<&[u8]> { + self.raw_module + .custom_sections() + .find(|cs| cs.name() == section_name) + .map(|cs| cs.payload()) + } + /// Consumes this runtime blob and serializes it. pub fn serialize(self) -> Vec<u8> { serialize(self.raw_module) .expect("serializing into a vec should succeed; qed") } + + /// Destructure this structure into the underlying parity-wasm Module. + pub fn into_inner(self) -> RawModule { + self.raw_module + } } diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index d08f830f40dae..fb39429dfdb24 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -17,18 +17,20 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. mod sandbox; +use std::sync::Arc; use codec::{Encode, Decode}; use hex_literal::hex; use sp_core::{ blake2_128, blake2_256, ed25519, sr25519, map, Pair, offchain::{OffchainWorkerExt, OffchainDbExt, testing}, - traits::{Externalities, CallInWasm}, + traits::Externalities, }; use sc_runtime_test::wasm_binary_unwrap; use sp_state_machine::TestExternalities as CoreTestExternalities; use sp_trie::{TrieConfiguration, trie_types::Layout}; use sp_wasm_interface::HostFunctions as _; use sp_runtime::traits::BlakeTwo256; +use sc_executor_common::{wasm_runtime::WasmModule, runtime_blob::RuntimeBlob}; use tracing_subscriber::layer::SubscriberExt; use crate::WasmExecutionMethod; @@ -77,13 +79,12 @@ fn call_in_wasm<E: Externalities>( 8, None, ); - executor.call_in_wasm( - &wasm_binary_unwrap()[..], - None, + executor.uncached_call( + RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + ext, + true, function, call_data, - ext, - sp_core::traits::MissingHostFunctions::Allow, ) } @@ -541,28 +542,37 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { None, ); - let err = executor.call_in_wasm( - &wasm_binary_unwrap()[..], - None, - "test_exhaust_heap", - &[0], - &mut ext.ext(), - sp_core::traits::MissingHostFunctions::Allow, - ).unwrap_err(); + let err = executor + .uncached_call( + RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + &mut ext.ext(), + true, + "test_exhaust_heap", + &[0], + ) + .unwrap_err(); assert!(err.contains("Allocator ran out of space")); } -test_wasm_execution!(returns_mutable_static); -fn returns_mutable_static(wasm_method: WasmExecutionMethod) { - let runtime = crate::wasm_runtime::create_wasm_runtime_with_code( +fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc<dyn WasmModule> { + let blob = RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]) + .expect("failed to create a runtime blob out of test runtime"); + + crate::wasm_runtime::create_wasm_runtime_with_code( wasm_method, - 1024, - &wasm_binary_unwrap()[..], + pages, + blob, HostFunctions::host_functions(), true, None, - ).expect("Creates runtime"); + ) + .expect("failed to instantiate wasm runtime") +} + +test_wasm_execution!(returns_mutable_static); +fn returns_mutable_static(wasm_method: WasmExecutionMethod) { + let runtime = mk_test_runtime(wasm_method, 1024); let instance = runtime.new_instance().unwrap(); let res = instance.call_export("returns_mutable_static", &[0]).unwrap(); @@ -589,14 +599,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { // to our allocator algorithm there are inefficiencies. const REQUIRED_MEMORY_PAGES: u64 = 32; - let runtime = crate::wasm_runtime::create_wasm_runtime_with_code( - wasm_method, - REQUIRED_MEMORY_PAGES, - &wasm_binary_unwrap()[..], - HostFunctions::host_functions(), - true, - None, - ).expect("Creates runtime"); + let runtime = mk_test_runtime(wasm_method, REQUIRED_MEMORY_PAGES); let instance = runtime.new_instance().unwrap(); // On the first invocation we allocate approx. 768KB (75%) of stack and then trap. @@ -610,14 +613,7 @@ fn restoration_of_globals(wasm_method: WasmExecutionMethod) { test_wasm_execution!(interpreted_only heap_is_reset_between_calls); fn heap_is_reset_between_calls(wasm_method: WasmExecutionMethod) { - let runtime = crate::wasm_runtime::create_wasm_runtime_with_code( - wasm_method, - 1024, - &wasm_binary_unwrap()[..], - HostFunctions::host_functions(), - true, - None, - ).expect("Creates runtime"); + let runtime = mk_test_runtime(wasm_method, 1024); let instance = runtime.new_instance().unwrap(); let heap_base = instance.get_global_const("__heap_base") @@ -642,27 +638,27 @@ fn parallel_execution(wasm_method: WasmExecutionMethod) { 8, None, )); - let code_hash = blake2_256(wasm_binary_unwrap()).to_vec(); - let threads: Vec<_> = (0..8).map(|_| - { + let threads: Vec<_> = (0..8) + .map(|_| { let executor = executor.clone(); - let code_hash = code_hash.clone(); std::thread::spawn(move || { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); assert_eq!( - executor.call_in_wasm( - &wasm_binary_unwrap()[..], - Some(code_hash.clone()), - "test_twox_128", - &[0], - &mut ext, - sp_core::traits::MissingHostFunctions::Allow, - ).unwrap(), + executor + .uncached_call( + RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + &mut ext, + true, + "test_twox_128", + &[0], + ) + .unwrap(), hex!("99e9d85137db46ef4bbea33613baafd5").to_vec().encode(), ); }) - }).collect(); + }) + .collect(); for t in threads.into_iter() { t.join().unwrap(); @@ -671,9 +667,7 @@ fn parallel_execution(wasm_method: WasmExecutionMethod) { test_wasm_execution!(wasm_tracing_should_work); fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) { - - use std::sync::{Arc, Mutex}; - + use std::sync::Mutex; use sc_tracing::{SpanDatum, TraceEvent}; struct TestTraceHandler(Arc<Mutex<Vec<SpanDatum>>>); @@ -779,6 +773,5 @@ fn panic_in_spawned_instance_panics_on_joining_its_result(wasm_method: WasmExecu &mut ext, ).unwrap_err(); - dbg!(&error_result); assert!(format!("{}", error_result).contains("Spawned task")); } diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index c30015a86b20e..c0cbf9c94dafd 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -38,14 +38,17 @@ mod wasm_runtime; mod integration_tests; pub use wasmi; -pub use native_executor::{with_externalities_safe, NativeExecutor, WasmExecutor, NativeExecutionDispatch}; +pub use native_executor::{ + with_externalities_safe, NativeExecutor, WasmExecutor, NativeExecutionDispatch, +}; pub use sp_version::{RuntimeVersion, NativeVersion}; pub use codec::Codec; #[doc(hidden)] -pub use sp_core::traits::{Externalities, CallInWasm}; +pub use sp_core::traits::{Externalities}; #[doc(hidden)] pub use sp_wasm_interface; pub use wasm_runtime::WasmExecutionMethod; +pub use wasm_runtime::read_embedded_version; pub use sc_executor_common::{error, sandbox}; @@ -68,7 +71,7 @@ mod tests { use sc_runtime_test::wasm_binary_unwrap; use sp_io::TestExternalities; use sp_wasm_interface::HostFunctions; - use sp_core::traits::CallInWasm; + use sc_executor_common::runtime_blob::RuntimeBlob; #[test] fn call_in_interpreted_wasm_works() { @@ -82,14 +85,15 @@ mod tests { 8, None, ); - let res = executor.call_in_wasm( - &wasm_binary_unwrap()[..], - None, - "test_empty_return", - &[], - &mut ext, - sp_core::traits::MissingHostFunctions::Allow, - ).unwrap(); + let res = executor + .uncached_call( + RuntimeBlob::uncompress_if_needed(&wasm_binary_unwrap()[..]).unwrap(), + &mut ext, + true, + "test_empty_return", + &[], + ) + .unwrap(); assert_eq!(res, vec![0u8; 0]); } } diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 6df651e1b776c..760e0c066beec 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -33,14 +33,14 @@ use sp_version::{NativeVersion, RuntimeVersion}; use codec::{Decode, Encode}; use sp_core::{ NativeOrEncoded, - traits::{ - CodeExecutor, Externalities, RuntimeCode, MissingHostFunctions, - RuntimeSpawnExt, RuntimeSpawn, - }, + traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawnExt, RuntimeSpawn}, }; use log::trace; use sp_wasm_interface::{HostFunctions, Function}; -use sc_executor_common::wasm_runtime::{WasmInstance, WasmModule, InvokeMethod}; +use sc_executor_common::{ + wasm_runtime::{WasmInstance, WasmModule, InvokeMethod}, + runtime_blob::RuntimeBlob, +}; use sp_externalities::ExternalitiesExt as _; use sp_tasks::new_async_externalities; @@ -188,64 +188,81 @@ impl WasmExecutor { Err(e) => Err(e), } } + + /// Perform a call into the given runtime. + /// + /// The runtime is passed as a [`RuntimeBlob`]. The runtime will be isntantiated with the + /// parameters this `WasmExecutor` was initialized with. + /// + /// In case of problems with during creation of the runtime or instantation, a `Err` is returned. + /// that describes the message. + #[doc(hidden)] // We use this function for tests across multiple crates. + pub fn uncached_call( + &self, + runtime_blob: RuntimeBlob, + ext: &mut dyn Externalities, + allow_missing_host_functions: bool, + export_name: &str, + call_data: &[u8], + ) -> std::result::Result<Vec<u8>, String> { + let module = crate::wasm_runtime::create_wasm_runtime_with_code( + self.method, + self.default_heap_pages, + runtime_blob, + self.host_functions.to_vec(), + allow_missing_host_functions, + self.cache_path.as_deref(), + ) + .map_err(|e| format!("Failed to create module: {:?}", e))?; + + let instance = module + .new_instance() + .map_err(|e| format!("Failed to create instance: {:?}", e))?; + + let instance = AssertUnwindSafe(instance); + let mut ext = AssertUnwindSafe(ext); + let module = AssertUnwindSafe(module); + + with_externalities_safe(&mut **ext, move || { + preregister_builtin_ext(module.clone()); + instance.call_export(export_name, call_data) + }) + .and_then(|r| r) + .map_err(|e| e.to_string()) + } } -impl sp_core::traits::CallInWasm for WasmExecutor { - fn call_in_wasm( +impl sp_core::traits::ReadRuntimeVersion for WasmExecutor { + fn read_runtime_version( &self, wasm_code: &[u8], - code_hash: Option<Vec<u8>>, - method: &str, - call_data: &[u8], ext: &mut dyn Externalities, - missing_host_functions: MissingHostFunctions, ) -> std::result::Result<Vec<u8>, String> { - let allow_missing_host_functions = missing_host_functions.allowed(); - - if let Some(hash) = code_hash { - let code = RuntimeCode { - code_fetcher: &sp_core::traits::WrappedRuntimeCode(wasm_code.into()), - hash, - heap_pages: None, - }; + let runtime_blob = RuntimeBlob::uncompress_if_needed(&wasm_code) + .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?; + + if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob) + .map_err(|e| format!("Failed to read the static section: {:?}", e)) + .map(|v| v.map(|v| v.encode()))? + { + return Ok(version); + } - self.with_instance(&code, ext, allow_missing_host_functions, |module, instance, _, mut ext| { - with_externalities_safe( - &mut **ext, - move || { - RuntimeInstanceSpawn::register_on_externalities(module.clone()); - instance.call_export(method, call_data) - } - ) - }).map_err(|e| e.to_string()) - } else { - let module = crate::wasm_runtime::create_wasm_runtime_with_code( - self.method, - self.default_heap_pages, - &wasm_code, - self.host_functions.to_vec(), - allow_missing_host_functions, - self.cache_path.as_deref(), - ) - .map_err(|e| format!("Failed to create module: {:?}", e))?; - - let instance = module.new_instance() - .map_err(|e| format!("Failed to create instance: {:?}", e))?; - - let instance = AssertUnwindSafe(instance); - let mut ext = AssertUnwindSafe(ext); - let module = AssertUnwindSafe(module); + // If the blob didn't have embedded runtime version section, we fallback to the legacy + // way of fetching the verison: i.e. instantiating the given instance and calling + // `Core_version` on it. - with_externalities_safe( - &mut **ext, - move || { - RuntimeInstanceSpawn::register_on_externalities(module.clone()); - instance.call_export(method, call_data) - } - ) - .and_then(|r| r) - .map_err(|e| e.to_string()) - } + self.uncached_call( + runtime_blob, + ext, + // If a runtime upgrade introduces new host functions that are not provided by + // the node, we should not fail at instantiation. Otherwise nodes that are + // updated could run this successfully and it could lead to a storage root + // mismatch when importing this block. + true, + "Core_version", + &[], + ) } } @@ -436,29 +453,25 @@ impl RuntimeInstanceSpawn { ext.extension::<sp_core::traits::TaskExecutorExt>() .map(move |task_ext| Self::new(module, task_ext.clone())) } +} - /// Register new `RuntimeSpawnExt` on current externalities. - /// - /// This extensions will spawn instances from provided `module`. - pub fn register_on_externalities(module: Arc<dyn WasmModule>) { - sp_externalities::with_externalities( - move |mut ext| { - if let Some(runtime_spawn) = - Self::with_externalities_and_module(module.clone(), ext) - { - if let Err(e) = ext.register_extension( - RuntimeSpawnExt(Box::new(runtime_spawn)) - ) { - trace!( - target: "executor", - "Failed to register `RuntimeSpawnExt` instance on externalities: {:?}", - e, - ) - } - } +/// Pre-registers the built-in extensions to the currently effective externalities. +/// +/// Meant to be called each time before calling into the runtime. +fn preregister_builtin_ext(module: Arc<dyn WasmModule>) { + sp_externalities::with_externalities(move |mut ext| { + if let Some(runtime_spawn) = + RuntimeInstanceSpawn::with_externalities_and_module(module, ext) + { + if let Err(e) = ext.register_extension(RuntimeSpawnExt(Box::new(runtime_spawn))) { + trace!( + target: "executor", + "Failed to register `RuntimeSpawnExt` instance on externalities: {:?}", + e, + ) } - ); - } + } + }); } impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeExecutor<D> { @@ -506,7 +519,7 @@ impl<D: NativeExecutionDispatch + 'static> CodeExecutor for NativeExecutor<D> { with_externalities_safe( &mut **ext, move || { - RuntimeInstanceSpawn::register_on_externalities(module.clone()); + preregister_builtin_ext(module.clone()); instance.call_export(method, data).map(NativeOrEncoded::Encoded) } ) @@ -557,17 +570,13 @@ impl<D: NativeExecutionDispatch> Clone for NativeExecutor<D> { } } -impl<D: NativeExecutionDispatch> sp_core::traits::CallInWasm for NativeExecutor<D> { - fn call_in_wasm( +impl<D: NativeExecutionDispatch> sp_core::traits::ReadRuntimeVersion for NativeExecutor<D> { + fn read_runtime_version( &self, - wasm_blob: &[u8], - code_hash: Option<Vec<u8>>, - method: &str, - call_data: &[u8], + wasm_code: &[u8], ext: &mut dyn Externalities, - missing_host_functions: MissingHostFunctions, ) -> std::result::Result<Vec<u8>, String> { - self.wasm.call_in_wasm(wasm_blob, code_hash, method, call_data, ext, missing_host_functions) + self.wasm.read_runtime_version(wasm_code, ext) } } diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 53968a645c994..23e88f9440907 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -29,7 +29,10 @@ use sp_core::traits::{Externalities, RuntimeCode, FetchRuntimeCode}; use sp_version::RuntimeVersion; use std::panic::AssertUnwindSafe; use std::path::{Path, PathBuf}; -use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance}; +use sc_executor_common::{ + wasm_runtime::{WasmModule, WasmInstance}, + runtime_blob::RuntimeBlob, +}; use sp_wasm_interface::Function; @@ -235,6 +238,9 @@ impl RuntimeCache { None => { let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?; + #[cfg(not(target_os = "unknown"))] + let time = std::time::Instant::now(); + let result = create_versioned_wasm_runtime( &code, code_hash.clone(), @@ -246,9 +252,22 @@ impl RuntimeCache { self.max_runtime_instances, self.cache_path.as_deref(), ); - if let Err(ref err) = result { - log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err); + + match result { + Ok(ref result) => { + #[cfg(not(target_os = "unknown"))] + log::debug!( + target: "wasm-runtime", + "Prepared new runtime version {:?} in {} ms.", + result.version, + time.elapsed().as_millis(), + ); + } + Err(ref err) => { + log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err); + } } + Arc::new(result?) } }; @@ -278,16 +297,11 @@ impl RuntimeCache { pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, heap_pages: u64, - code: &[u8], + blob: RuntimeBlob, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, cache_path: Option<&Path>, ) -> Result<Arc<dyn WasmModule>, WasmError> { - use sp_maybe_compressed_blob::CODE_BLOB_BOMB_LIMIT; - - let code = sp_maybe_compressed_blob::decompress(code, CODE_BLOB_BOMB_LIMIT) - .map_err(|e| WasmError::Other(format!("Decompression error: {:?}", e)))?; - match wasm_method { WasmExecutionMethod::Interpreted => { // Wasmi doesn't have any need in a cache directory. @@ -297,7 +311,7 @@ pub fn create_wasm_runtime_with_code( drop(cache_path); sc_executor_wasmi::create_runtime( - &code, + blob, heap_pages, host_functions, allow_missing_func_imports, @@ -306,7 +320,6 @@ pub fn create_wasm_runtime_with_code( } #[cfg(feature = "wasmtime")] WasmExecutionMethod::Compiled => { - let blob = sc_executor_common::runtime_blob::RuntimeBlob::new(&code)?; sc_executor_wasmtime::create_runtime( sc_executor_wasmtime::CodeSupplyMode::Verbatim { blob }, sc_executor_wasmtime::Config { @@ -343,6 +356,55 @@ fn decode_version(version: &[u8]) -> Result<RuntimeVersion, WasmError> { } } +fn decode_runtime_apis(apis: &[u8]) -> Result<Vec<([u8; 8], u32)>, WasmError> { + use std::convert::TryFrom; + use sp_api::RUNTIME_API_INFO_SIZE; + + apis.chunks(RUNTIME_API_INFO_SIZE) + .map(|chunk| { + // `chunk` can be less than `RUNTIME_API_INFO_SIZE` if the total length of `apis` doesn't + // completely divide by `RUNTIME_API_INFO_SIZE`. + <[u8; RUNTIME_API_INFO_SIZE]>::try_from(chunk) + .map(sp_api::deserialize_runtime_api_info) + .map_err(|_| { + WasmError::Other(format!( + "a clipped runtime api info declaration" + )) + }) + }) + .collect::<Result<Vec<_>, WasmError>>() +} + +/// Take the runtime blob and scan it for the custom wasm sections containing the version information +/// and construct the `RuntimeVersion` from them. +/// +/// If there are no such sections, it returns `None`. If there is an error during decoding those +/// sections, `Err` will be returned. +pub fn read_embedded_version( + blob: &RuntimeBlob, +) -> Result<Option<RuntimeVersion>, WasmError> { + if let Some(version_section) = blob.custom_section_contents("runtime_version") { + // We do not use `decode_version` here because the runtime_version section is not supposed + // to ever contain a legacy version. Apart from that `decode_version` relies on presence + // of a special API in the `apis` field to treat the input as a non-legacy version. However + // the structure found in the `runtime_version` always contain an empty `apis` field. Therefore + // the version read will be mistakingly treated as an legacy one. + let mut decoded_version = sp_api::RuntimeVersion::decode(&mut &version_section[..]) + .map_err(|_| + WasmError::Instantiation("failed to decode verison section".into()) + )?; + + // Don't stop on this and check if there is a special section that encodes all runtime APIs. + if let Some(apis_section) = blob.custom_section_contents("runtime_apis") { + decoded_version.apis = decode_runtime_apis(apis_section)?.into(); + } + + Ok(Some(decoded_version)) + } else { + Ok(None) + } +} + fn create_versioned_wasm_runtime( code: &[u8], code_hash: Vec<u8>, @@ -354,41 +416,44 @@ fn create_versioned_wasm_runtime( max_instances: usize, cache_path: Option<&Path>, ) -> Result<VersionedRuntime, WasmError> { - #[cfg(not(target_os = "unknown"))] - let time = std::time::Instant::now(); + // The incoming code may be actually compressed. We decompress it here and then work with + // the uncompressed code from now on. + let blob = sc_executor_common::runtime_blob::RuntimeBlob::uncompress_if_needed(&code)?; + + // Use the runtime blob to scan if there is any metadata embedded into the wasm binary pertaining + // to runtime version. We do it before consuming the runtime blob for creating the runtime. + let mut version: Option<_> = read_embedded_version(&blob)?; + let runtime = create_wasm_runtime_with_code( wasm_method, heap_pages, - &code, + blob, host_functions, allow_missing_func_imports, cache_path, )?; - // Call to determine runtime version. - let version_result = { - // `ext` is already implicitly handled as unwind safe, as we store it in a global variable. - let mut ext = AssertUnwindSafe(ext); - - // The following unwind safety assertion is OK because if the method call panics, the - // runtime will be dropped. - let runtime = AssertUnwindSafe(runtime.as_ref()); - crate::native_executor::with_externalities_safe( - &mut **ext, - move || runtime.new_instance()?.call("Core_version".into(), &[]) - ).map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))? - }; - let version = match version_result { - Ok(version) => Some(decode_version(&version)?), - Err(_) => None, - }; - #[cfg(not(target_os = "unknown"))] - log::debug!( - target: "wasm-runtime", - "Prepared new runtime version {:?} in {} ms.", - version, - time.elapsed().as_millis(), - ); + // If the runtime blob doesn't embed the runtime version then use the legacy version query + // mechanism: call the runtime. + if version.is_none() { + // Call to determine runtime version. + let version_result = { + // `ext` is already implicitly handled as unwind safe, as we store it in a global variable. + let mut ext = AssertUnwindSafe(ext); + + // The following unwind safety assertion is OK because if the method call panics, the + // runtime will be dropped. + let runtime = AssertUnwindSafe(runtime.as_ref()); + crate::native_executor::with_externalities_safe( + &mut **ext, + move || runtime.new_instance()?.call("Core_version".into(), &[]) + ).map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))? + }; + + if let Ok(version_buf) = version_result { + version = Some(decode_version(&version_buf)?) + } + } let mut instances = Vec::with_capacity(max_instances); instances.resize_with(max_instances, || Mutex::new(None)); diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 0163e07e654bf..953c5e5178a61 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -641,18 +641,18 @@ impl WasmModule for WasmiRuntime { /// Create a new `WasmiRuntime` given the code. This function loads the module and /// stores it in the instance. pub fn create_runtime( - code: &[u8], + blob: RuntimeBlob, heap_pages: u64, host_functions: Vec<&'static dyn Function>, allow_missing_func_imports: bool, ) -> Result<WasmiRuntime, WasmError> { - let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?; + let data_segments_snapshot = DataSegmentsSnapshot::take(&blob) + .map_err(|e| WasmError::Other(e.to_string()))?; - // Extract the data segments from the wasm code. - // - // A return of this error actually indicates that there is a problem in logic, since - // we just loaded and validated the `module` above. - let (data_segments_snapshot, global_vals_snapshot) = { + let module = Module::from_parity_wasm_module(blob.into_inner()) + .map_err(|_| WasmError::InvalidModule)?; + + let global_vals_snapshot = { let (instance, _, _) = instantiate_module( heap_pages as usize, &module, @@ -660,12 +660,7 @@ pub fn create_runtime( allow_missing_func_imports, ) .map_err(|e| WasmError::Instantiation(e.to_string()))?; - - let data_segments_snapshot = DataSegmentsSnapshot::take(&RuntimeBlob::new(code)?) - .map_err(|e| WasmError::Other(e.to_string()))?; - let global_vals_snapshot = GlobalValsSnapshot::take(&instance); - - (data_segments_snapshot, global_vals_snapshot) + GlobalValsSnapshot::take(&instance) }; Ok(WasmiRuntime { diff --git a/frame/system/src/tests.rs b/frame/system/src/tests.rs index 25f67d7a1a499..df28e2c118c21 100644 --- a/frame/system/src/tests.rs +++ b/frame/system/src/tests.rs @@ -420,17 +420,13 @@ fn prunes_block_hash_mappings() { #[test] fn set_code_checks_works() { - struct CallInWasm(Vec<u8>); + struct ReadRuntimeVersion(Vec<u8>); - impl sp_core::traits::CallInWasm for CallInWasm { - fn call_in_wasm( + impl sp_core::traits::ReadRuntimeVersion for ReadRuntimeVersion { + fn read_runtime_version( &self, - _: &[u8], - _: Option<Vec<u8>>, - _: &str, - _: &[u8], - _: &mut dyn sp_externalities::Externalities, - _: sp_core::traits::MissingHostFunctions, + _wasm_code: &[u8], + _ext: &mut dyn sp_externalities::Externalities, ) -> Result<Vec<u8>, String> { Ok(self.0.clone()) } @@ -452,10 +448,10 @@ fn set_code_checks_works() { impl_version, ..Default::default() }; - let call_in_wasm = CallInWasm(version.encode()); + let read_runtime_version = ReadRuntimeVersion(version.encode()); let mut ext = new_test_ext(); - ext.register_extension(sp_core::traits::CallInWasmExt::new(call_in_wasm)); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(read_runtime_version)); ext.execute_with(|| { let res = System::set_code( RawOrigin::Root.into(), @@ -471,7 +467,7 @@ fn set_code_checks_works() { fn set_code_with_real_wasm_blob() { let executor = substrate_test_runtime_client::new_native_executor(); let mut ext = new_test_ext(); - ext.register_extension(sp_core::traits::CallInWasmExt::new(executor)); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor)); ext.execute_with(|| { System::set_block_number(1); System::set_code( @@ -494,7 +490,7 @@ fn set_code_with_real_wasm_blob() { fn runtime_upgraded_with_set_storage() { let executor = substrate_test_runtime_client::new_native_executor(); let mut ext = new_test_ext(); - ext.register_extension(sp_core::traits::CallInWasmExt::new(executor)); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor)); ext.execute_with(|| { System::set_storage( RawOrigin::Root.into(), diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index 85ba0788105d7..e918724c0f5b2 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -633,8 +633,11 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result<TokenStream> /// runtime apis. fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> { let mut result = Vec::with_capacity(impls.len()); + let mut sections = Vec::with_capacity(impls.len()); let mut processed_traits = HashSet::new(); + let c = generate_crate_access(HIDDEN_INCLUDES_ID); + for impl_ in impls { let mut path = extend_with_runtime_decl_path( extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(), @@ -667,12 +670,21 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> { #( #attrs )* (#id, #version) )); - } - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + sections.push(quote!( + #( #attrs )* + const _: () = { + // All sections with the same name are going to be merged by concatenation. + #[link_section = "runtime_apis"] + static SECTION_CONTENTS: [u8; 12] = #c::serialize_runtime_api_info(#id, #version); + }; + )); + } Ok(quote!( const RUNTIME_API_VERSIONS: #c::ApisVec = #c::create_apis_vec!([ #( #result ),* ]); + + #( #sections )* )) } diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 155bb899a2ed5..97342377a76c8 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -613,6 +613,49 @@ pub trait RuntimeApiInfo { const VERSION: u32; } +/// The number of bytes required to encode a [`RuntimeApiInfo`]. +/// +/// 8 bytes for `ID` and 4 bytes for a version. +pub const RUNTIME_API_INFO_SIZE: usize = 12; + +/// Crude and simple way to serialize the `RuntimeApiInfo` into a bunch of bytes. +pub const fn serialize_runtime_api_info(id: [u8; 8], version: u32) -> [u8; RUNTIME_API_INFO_SIZE] { + let version = version.to_le_bytes(); + + let mut r = [0; RUNTIME_API_INFO_SIZE]; + r[0] = id[0]; + r[1] = id[1]; + r[2] = id[2]; + r[3] = id[3]; + r[4] = id[4]; + r[5] = id[5]; + r[6] = id[6]; + r[7] = id[7]; + + r[8] = version[0]; + r[9] = version[1]; + r[10] = version[2]; + r[11] = version[3]; + r +} + +/// Deserialize the runtime API info serialized by [`serialize_runtime_api_info`]. +pub fn deserialize_runtime_api_info(bytes: [u8; RUNTIME_API_INFO_SIZE]) -> ([u8; 8], u32) { + use sp_std::convert::TryInto; + + let id: [u8; 8] = bytes[0..8] + .try_into() + .expect("the source slice size is equal to the dest array length; qed"); + + let version = u32::from_le_bytes( + bytes[8..12] + .try_into() + .expect("the source slice size is equal to the array length; qed"), + ); + + (id, version) +} + #[derive(codec::Encode, codec::Decode)] pub struct OldRuntimeVersion { pub spec_name: RuntimeString, diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index 90f8060f9a565..948830cf5ca68 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -26,7 +26,7 @@ use std::{ pub use sp_externalities::{Externalities, ExternalitiesExt}; /// Code execution engine. -pub trait CodeExecutor: Sized + Send + Sync + CallInWasm + Clone + 'static { +pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'static { /// Externalities error type. type Error: Display + Debug + Send + Sync + 'static; @@ -123,53 +123,42 @@ impl std::fmt::Display for CodeNotFound { } } -/// `Allow` or `Disallow` missing host functions when instantiating a WASM blob. -#[derive(Clone, Copy, Debug)] -pub enum MissingHostFunctions { - /// Any missing host function will be replaced by a stub that returns an error when - /// being called. - Allow, - /// Any missing host function will result in an error while instantiating the WASM blob, - Disallow, -} - -impl MissingHostFunctions { - /// Are missing host functions allowed? - pub fn allowed(self) -> bool { - matches!(self, Self::Allow) - } -} - -/// Something that can call a method in a WASM blob. -pub trait CallInWasm: Send + Sync { - /// Call the given `method` in the given `wasm_blob` using `call_data` (SCALE encoded arguments) - /// to decode the arguments for the method. +/// A trait that allows reading version information from the binary. +pub trait ReadRuntimeVersion: Send + Sync { + /// Reads the runtime version information from the given wasm code. /// - /// Returns the SCALE encoded return value of the method. + /// The version information may be embedded into the wasm binary itself. If it is not present, + /// then this function may fallback to the legacy way of reading the version. /// - /// # Note + /// The legacy mechanism involves instantiating the passed wasm runtime and calling `Core_version` + /// on it. This is a very expensive operation. /// - /// If `code_hash` is `Some(_)` the `wasm_code` module and instance will be cached internally, - /// otherwise it is thrown away after the call. - fn call_in_wasm( + /// `ext` is only needed in case the calling into runtime happens. Otherwise it is ignored. + /// + /// Compressed wasm blobs are supported and will be decompressed if needed. If uncompression fails, + /// the error is returned. + /// + /// # Errors + /// + /// If the version information present in binary, but is corrupted - returns an error. + /// + /// Otherwise, if there is no version information present, and calling into the runtime takes + /// place, then an error would be returned if `Core_version` is not provided. + fn read_runtime_version( &self, wasm_code: &[u8], - code_hash: Option<Vec<u8>>, - method: &str, - call_data: &[u8], ext: &mut dyn Externalities, - missing_host_functions: MissingHostFunctions, ) -> Result<Vec<u8>, String>; } sp_externalities::decl_extension! { - /// The call-in-wasm extension to register/retrieve from the externalities. - pub struct CallInWasmExt(Box<dyn CallInWasm>); + /// An extension that provides functionality to read version information from a given wasm blob. + pub struct ReadRuntimeVersionExt(Box<dyn ReadRuntimeVersion>); } -impl CallInWasmExt { - /// Creates a new instance of `Self`. - pub fn new<T: CallInWasm + 'static>(inner: T) -> Self { +impl ReadRuntimeVersionExt { + /// Creates a new instance of the extension given a version determinator instance. + pub fn new<T: ReadRuntimeVersion + 'static>(inner: T) -> Self { Self(Box::new(inner)) } } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index cbbda1807cc2f..e63fcb909573a 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -24,6 +24,7 @@ libsecp256k1 = { version = "0.3.4", optional = true } sp-state-machine = { version = "0.9.0", optional = true, path = "../state-machine" } sp-wasm-interface = { version = "3.0.0", path = "../wasm-interface", default-features = false } sp-runtime-interface = { version = "3.0.0", default-features = false, path = "../runtime-interface" } +sp-maybe-compressed-blob = { version = "3.0.0", optional = true, path = "../maybe-compressed-blob" } sp-trie = { version = "3.0.0", optional = true, path = "../trie" } sp-externalities = { version = "0.9.0", optional = true, path = "../externalities" } sp-tracing = { version = "3.0.0", default-features = false, path = "../tracing" } @@ -47,6 +48,7 @@ std = [ "sp-runtime-interface/std", "sp-externalities", "sp-wasm-interface/std", + "sp-maybe-compressed-blob", "sp-tracing/std", "tracing/std", "tracing-core/std", diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 35daaa3989907..72695f2156b67 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -38,7 +38,7 @@ use tracing; #[cfg(feature = "std")] use sp_core::{ crypto::Pair, - traits::{CallInWasmExt, TaskExecutorExt, RuntimeSpawnExt}, + traits::{TaskExecutorExt, RuntimeSpawnExt}, offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, hexdisplay::HexDisplay, storage::ChildInfo, @@ -70,6 +70,8 @@ mod batch_verifier; #[cfg(feature = "std")] use batch_verifier::BatchVerifier; +const LOG_TARGET: &str = "runtime::io"; + /// Error verifying ECDSA signature #[derive(Encode, Decode)] pub enum EcdsaVerifyError { @@ -432,6 +434,9 @@ pub trait Trie { /// Interface that provides miscellaneous functions for communicating between the runtime and the node. #[runtime_interface] pub trait Misc { + // NOTE: We use the target 'runtime' for messages produced by general printing functions, instead + // of LOG_TARGET. + /// Print a number. fn print_num(val: u64) { log::debug!(target: "runtime", "{}", val); @@ -456,28 +461,34 @@ pub trait Misc { /// /// # Performance /// - /// Calling this function is very expensive and should only be done very occasionally. - /// For getting the runtime version, it requires instantiating the wasm blob and calling a - /// function in this blob. + /// This function may be very expensive to call depending on the wasm binary. It may be + /// relatively cheap if the wasm binary contains version information. In that case, uncompression + /// of the wasm blob is the dominating factor. + /// + /// If the wasm binary does not have the version information attached, then a legacy mechanism + /// may be involved. This means that a runtime call will be performed to query the version. + /// + /// Calling into the runtime may be incredible expensive and should be approached with care. fn runtime_version(&mut self, wasm: &[u8]) -> Option<Vec<u8>> { - // Create some dummy externalities, `Core_version` should not write data anyway. + use sp_core::traits::ReadRuntimeVersionExt; + let mut ext = sp_state_machine::BasicExternalities::default(); - self.extension::<CallInWasmExt>() - .expect("No `CallInWasmExt` associated for the current context!") - .call_in_wasm( - wasm, - None, - "Core_version", - &[], - &mut ext, - // If a runtime upgrade introduces new host functions that are not provided by - // the node, we should not fail at instantiation. Otherwise nodes that are - // updated could run this successfully and it could lead to a storage root - // mismatch when importing this block. - sp_core::traits::MissingHostFunctions::Allow, - ) - .ok() + match self + .extension::<ReadRuntimeVersionExt>() + .expect("No `ReadRuntimeVersionExt` associated for the current context!") + .read_runtime_version(wasm, &mut ext) + { + Ok(v) => Some(v), + Err(err) => { + log::debug!( + target: LOG_TARGET, + "cannot read version from the given runtime: {}", + err, + ); + None + } + } } } diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 8b6c9cbe5df05..fb9b3c4b71edf 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-runtime-interface = { version = "3.0.0", path = "../" } sc-executor = { version = "0.9.0", path = "../../../client/executor" } +sc-executor-common = { version = "0.9.0", path = "../../../client/executor/common" } sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" } sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0", path = "../test-wasm-deprecated" } sp-state-machine = { version = "0.9.0", path = "../../state-machine" } diff --git a/primitives/runtime-interface/test/src/lib.rs b/primitives/runtime-interface/test/src/lib.rs index 4426997663485..a021a93939a10 100644 --- a/primitives/runtime-interface/test/src/lib.rs +++ b/primitives/runtime-interface/test/src/lib.rs @@ -24,7 +24,7 @@ use sp_runtime_interface_test_wasm::{wasm_binary_unwrap, test_api::HostFunctions use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap; use sp_wasm_interface::HostFunctions as HostFunctionsT; -use sc_executor::CallInWasm; +use sc_executor_common::runtime_blob::RuntimeBlob; use std::{collections::HashSet, sync::{Arc, Mutex}}; @@ -46,14 +46,15 @@ fn call_wasm_method_with_result<HF: HostFunctionsT>( 8, None, ); - executor.call_in_wasm( - binary, - None, - method, - &[], - &mut ext_ext, - sp_core::traits::MissingHostFunctions::Disallow, - ).map_err(|e| format!("Failed to execute `{}`: {}", method, e))?; + executor + .uncached_call( + RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), + &mut ext_ext, + false, + method, + &[], + ) + .map_err(|e| format!("Failed to execute `{}`: {}", method, e))?; Ok(ext) } diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index a6f1fb1f0e788..479184b4b9905 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -178,7 +178,7 @@ mod execution { use codec::{Decode, Encode, Codec}; use sp_core::{ storage::ChildInfo, NativeOrEncoded, NeverNativeValue, hexdisplay::HexDisplay, - traits::{CodeExecutor, CallInWasmExt, RuntimeCode, SpawnNamed}, + traits::{CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed}, }; use sp_externalities::Extensions; @@ -339,7 +339,7 @@ mod execution { runtime_code: &'a RuntimeCode, spawn_handle: impl SpawnNamed + Send + 'static, ) -> Self { - extensions.register(CallInWasmExt::new(exec.clone())); + extensions.register(ReadRuntimeVersionExt::new(exec.clone())); extensions.register(sp_core::traits::TaskExecutorExt::new(spawn_handle)); Self { @@ -943,15 +943,11 @@ mod tests { } } - impl sp_core::traits::CallInWasm for DummyCodeExecutor { - fn call_in_wasm( + impl sp_core::traits::ReadRuntimeVersion for DummyCodeExecutor { + fn read_runtime_version( &self, _: &[u8], - _: Option<Vec<u8>>, - _: &str, - _: &[u8], _: &mut dyn Externalities, - _: sp_core::traits::MissingHostFunctions, ) -> std::result::Result<Vec<u8>, String> { unimplemented!("Not required in tests.") } diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index bfb9a742c8689..b50da9e9eacf9 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -20,6 +20,7 @@ serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } sp-std = { version = "3.0.0", default-features = false, path = "../std" } sp-runtime = { version = "3.0.0", default-features = false, path = "../runtime" } +sp-version-proc-macro = { version = "3.0.0", default-features = false, path = "proc-macro" } [features] default = ["std"] diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml new file mode 100644 index 0000000000000..ea3144090c70e --- /dev/null +++ b/primitives/version/proc-macro/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sp-version-proc-macro" +version = "3.0.0" +authors = ["Parity Technologies <admin@parity.io>"] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Macro for defining a runtime version." +documentation = "https://docs.rs/sp-api-proc-macro" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.3" +syn = { version = "1.0.58", features = ["full", "fold", "extra-traits", "visit"] } +proc-macro2 = "1.0.6" +proc-macro-crate = "1.0.0" +codec = { package = "parity-scale-codec", version = "2.0.0", features = [ "derive" ] } + +[dev-dependencies] +sp-version = { version = "3.0.0", path = ".." } diff --git a/primitives/version/proc-macro/src/decl_runtime_version.rs b/primitives/version/proc-macro/src/decl_runtime_version.rs new file mode 100644 index 0000000000000..6df0b71b202c4 --- /dev/null +++ b/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -0,0 +1,279 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::Encode; +use syn::{ + Expr, ExprLit, FieldValue, ItemConst, Lit, + parse::{Result, Error}, + parse_macro_input, + spanned::Spanned as _, +}; +use quote::quote; +use proc_macro2::{TokenStream, Span}; + +/// This macro accepts a `const` item that has a struct initializer expression of `RuntimeVersion`-like type. +/// The macro will pass through this declaration and append an item declaration that will +/// lead to emitting a wasm custom section with the contents of `RuntimeVersion`. +pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = parse_macro_input!(input as ItemConst); + decl_runtime_version_impl_inner(item) + .unwrap_or_else(|e| e.to_compile_error()) + .into() +} + +fn decl_runtime_version_impl_inner(item: ItemConst) -> Result<TokenStream> { + let runtime_version = ParseRuntimeVersion::parse_expr(&*item.expr)?.build(item.expr.span())?; + let link_section = + generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version"); + + Ok(quote! { + #item + #link_section + }) +} + +/// This is a duplicate of `sp_version::RuntimeVersion`. We cannot unfortunately use the original +/// declaration, because if we directly depend on `sp_version` from this proc-macro cargo will +/// enable `std` feature even for `no_std` wasm runtime builds. +/// +/// One difference from the original definition is the `apis` field. Since we don't actually parse +/// `apis` from this macro it will always be emitteed as empty. An empty vector can be encoded as +/// a zero-byte, thus `u8` is sufficient here. +#[derive(Encode)] +struct RuntimeVersion { + spec_name: String, + impl_name: String, + authoring_version: u32, + spec_version: u32, + impl_version: u32, + apis: u8, + transaction_version: u32, +} + +#[derive(Default, Debug)] +struct ParseRuntimeVersion { + spec_name: Option<String>, + impl_name: Option<String>, + authoring_version: Option<u32>, + spec_version: Option<u32>, + impl_version: Option<u32>, + transaction_version: Option<u32>, +} + +impl ParseRuntimeVersion { + fn parse_expr(init_expr: &Expr) -> Result<ParseRuntimeVersion> { + let init_expr = match init_expr { + Expr::Struct(ref e) => e, + _ => { + return Err(Error::new( + init_expr.span(), + "expected a struct initializer expression", + )); + } + }; + + let mut parsed = ParseRuntimeVersion::default(); + for field_value in init_expr.fields.iter() { + parsed.parse_field_value(field_value)?; + } + Ok(parsed) + } + + fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<()> { + let field_name = match field_value.member { + syn::Member::Named(ref ident) => ident, + syn::Member::Unnamed(_) => { + return Err(Error::new( + field_value.span(), + "only named members must be used", + )); + } + }; + + fn parse_once<T>( + value: &mut Option<T>, + field: &FieldValue, + parser: impl FnOnce(&Expr) -> Result<T>, + ) -> Result<()> { + if value.is_some() { + return Err(Error::new( + field.span(), + "field is already initialized before", + )); + } else { + *value = Some(parser(&field.expr)?); + Ok(()) + } + } + + if field_name == "spec_name" { + parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?; + } else if field_name == "impl_name" { + parse_once(&mut self.impl_name, field_value, Self::parse_str_literal)?; + } else if field_name == "authoring_version" { + parse_once( + &mut self.authoring_version, + field_value, + Self::parse_num_literal, + )?; + } else if field_name == "spec_version" { + parse_once(&mut self.spec_version, field_value, Self::parse_num_literal)?; + } else if field_name == "impl_version" { + parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?; + } else if field_name == "transaction_version" { + parse_once( + &mut self.transaction_version, + field_value, + Self::parse_num_literal, + )?; + } else if field_name == "apis" { + // Intentionally ignored + // + // The definition will pass through for the declaration, however, it won't get into + // the "runtime_version" custom section. `impl_runtime_apis` is responsible for generating + // a custom section with the supported runtime apis descriptor. + } else { + return Err(Error::new(field_name.span(), "unknown field")); + } + + Ok(()) + } + + fn parse_num_literal(expr: &Expr) -> Result<u32> { + let lit = match *expr { + Expr::Lit(ExprLit { + lit: Lit::Int(ref lit), + .. + }) => lit, + _ => { + return Err(Error::new( + expr.span(), + "only numeric literals (e.g. `10`) are supported here", + )); + } + }; + lit.base10_parse::<u32>() + } + + fn parse_str_literal(expr: &Expr) -> Result<String> { + let mac = match *expr { + Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac, + _ => { + return Err(Error::new( + expr.span(), + "a macro expression is expected here", + )); + } + }; + + let lit: ExprLit = mac.parse_body().map_err(|e| { + Error::new( + e.span(), + format!( + "a single literal argument is expected, but parsing is failed: {}", + e + ), + ) + })?; + + match lit.lit { + Lit::Str(ref lit) => Ok(lit.value()), + _ => Err(Error::new( + lit.span(), + "only string literals are supported here", + )), + } + } + + fn build(self, span: Span) -> Result<RuntimeVersion> { + macro_rules! required { + ($e:expr) => { + $e.ok_or_else(|| + { + Error::new( + span, + format!("required field '{}' is missing", stringify!($e)), + ) + } + )? + }; + } + + let Self { + spec_name, + impl_name, + authoring_version, + spec_version, + impl_version, + transaction_version, + } = self; + + Ok(RuntimeVersion { + spec_name: required!(spec_name), + impl_name: required!(impl_name), + authoring_version: required!(authoring_version), + spec_version: required!(spec_version), + impl_version: required!(impl_version), + transaction_version: required!(transaction_version), + apis: 0, + }) + } +} + +fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream { + let len = contents.len(); + quote! { + const _: () = { + #[link_section = #section_name] + static SECTION_CONTENTS: [u8; #len] = [#(#contents),*]; + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::DecodeAll; + use std::borrow::Cow; + + #[test] + fn version_can_be_deserialized() { + let version_bytes = RuntimeVersion { + spec_name: "hello".to_string(), + impl_name: "world".to_string(), + authoring_version: 10, + spec_version: 265, + impl_version: 1, + apis: 0, + transaction_version: 2, + } + .encode(); + + assert_eq!( + sp_version::RuntimeVersion::decode_all(&mut &version_bytes[..]).unwrap(), + sp_version::RuntimeVersion { + spec_name: "hello".into(), + impl_name: "world".into(), + authoring_version: 10, + spec_version: 265, + impl_version: 1, + apis: Cow::Owned(vec![]), + transaction_version: 2, + }, + ); + } +} diff --git a/primitives/version/proc-macro/src/lib.rs b/primitives/version/proc-macro/src/lib.rs new file mode 100644 index 0000000000000..9a6d4d60bbf9f --- /dev/null +++ b/primitives/version/proc-macro/src/lib.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A proc-macro that generates a custom wasm section from a given RuntimeVersion declaration +//! +//! This macro is re-exported from the `sp_version::runtime_version` and intended to be used from +//! there. Documentation can also be found there. + +#![recursion_limit = "512"] + +use proc_macro::TokenStream; + +mod decl_runtime_version; + +#[proc_macro_attribute] +pub fn runtime_version(_: TokenStream, input: TokenStream) -> TokenStream { + decl_runtime_version::decl_runtime_version_impl(input) +} diff --git a/primitives/version/src/lib.rs b/primitives/version/src/lib.rs index 24a1b85ed0c38..603989f5d2f61 100644 --- a/primitives/version/src/lib.rs +++ b/primitives/version/src/lib.rs @@ -35,6 +35,51 @@ pub use sp_std; #[cfg(feature = "std")] use sp_runtime::{traits::Block as BlockT, generic::BlockId}; +/// An attribute that accepts a version declaration of a runtime and generates a custom wasm section +/// with the equivalent contents. +/// +/// The custom section allows to read the version of the runtime without having to execute any code. +/// Instead, the generated custom section can be relatively easily parsed from the wasm binary. The +/// identifier of the custom section is "runtime_version". +/// +/// A shortcoming of this macro is that it is unable to embed information regarding supported APIs. +/// This is supported by the `construct_runtime!` macro. +/// +/// This macro accepts a const item like the following: +/// +/// ```rust +/// use sp_version::{create_runtime_str, RuntimeVersion}; +/// +/// #[sp_version::runtime_version] +/// pub const VERSION: RuntimeVersion = RuntimeVersion { +/// spec_name: create_runtime_str!("test"), +/// impl_name: create_runtime_str!("test"), +/// authoring_version: 10, +/// spec_version: 265, +/// impl_version: 1, +/// apis: RUNTIME_API_VERSIONS, +/// transaction_version: 2, +/// }; +/// +/// # const RUNTIME_API_VERSIONS: sp_version::ApisVec = sp_version::create_apis_vec!([]); +/// ``` +/// +/// It will pass it through and add code required for emitting a custom section. The information that +/// will go into the custom section is parsed from the item declaration. Due to that, the macro is +/// somewhat rigid in terms of the code it accepts. There are the following considerations: +/// +/// - The `spec_name` and `impl_name` must be set by a macro-like expression. The name of the macro +/// doesn't matter though. +/// +/// - `authoring_version`, `spec_version`, `impl_version` and `transaction_version` must be set +/// by a literal. Literal must be an integer. No other expressions are allowed there. In particular, +/// you can't supply a constant variable. +/// +/// - `apis` doesn't have any specific constraints. This is because this information doesn't get into +/// the custom section and is not parsed. +/// +pub use sp_version_proc_macro::runtime_version; + /// The identity of a particular API interface that the runtime might provide. pub type ApiId = [u8; 8]; diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 4afb313eef35d..7ee1072a7b83e 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -92,6 +92,7 @@ pub fn wasm_binary_logging_disabled_unwrap() -> &'static [u8] { } /// Test runtime version. +#[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("test"), impl_name: create_runtime_str!("parity-test"),