Embed runtime version as a custom section (#8688)

* emit a custom section from impl_runtime_apis!

This change emits a custom section from the impl_runtime_apis! proc macro.

Each implemented API will result to emitting a link section `runtime_apis`.
During linking all sections with this name will be concatenated and
placed into the final wasm binary under the same name.

* Introduce `runtime_version` proc macro

This macro takes an existing `RuntimeVersion` const declaration, parses
it and emits the version information in form of a linking section.
Ultimately such a linking section will result into a custom wasm
section.

* Parse custom wasm section for runtime version

* Apply suggestions from code review

Co-authored-by: David <dvdplm@gmail.com>

* Fix sc-executor integration tests

* Nits

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Refactor apis section deserialization

* Fix version decoding

* Reuse uncompressed value for CallInWasm

* Log on decompression error

* Simplify if

* Reexport proc-macro from sp_version

* Merge ReadRuntimeVersionExt

* Export `read_embedded_version`

* Fix test

* Simplify searching for custom section

Co-authored-by: David <dvdplm@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Sergei Shulepov
2021-05-12 15:39:08 +02:00
committed by GitHub
parent 4f7c1df31e
commit 0849bcce0e
27 changed files with 833 additions and 289 deletions
+16 -1
View File
@@ -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]]
+1
View File
@@ -182,6 +182,7 @@ members = [
"primitives/trie",
"primitives/utils",
"primitives/version",
"primitives/version/proc-macro",
"primitives/wasm-interface",
"test-utils/client",
"test-utils/derive",
@@ -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"),
+1
View File
@@ -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"),
-1
View File
@@ -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 }
@@ -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"
@@ -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
}
}
@@ -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"));
}
+15 -11
View File
@@ -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]);
}
}
@@ -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();
let runtime_blob = RuntimeBlob::uncompress_if_needed(&wasm_code)
.map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
if let Some(hash) = code_hash {
let code = RuntimeCode {
code_fetcher: &sp_core::traits::WrappedRuntimeCode(wasm_code.into()),
hash,
heap_pages: None,
};
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);
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())
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);
}
// 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.
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)
}
}
+102 -37
View File
@@ -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);
// 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()))?
};
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(),
);
// 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));
+8 -13
View File
@@ -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 {
+9 -13
View File
@@ -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(),
@@ -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 )*
))
}
+43
View File
@@ -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,
+25 -36
View File
@@ -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))
}
}
+2
View File
@@ -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",
+31 -20
View File
@@ -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
}
}
}
}
@@ -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" }
@@ -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)
}
@@ -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.")
}
+1
View File
@@ -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"]
@@ -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 = ".." }
@@ -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,
},
);
}
}
@@ -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)
}
+45
View File
@@ -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];
+1
View File
@@ -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"),