mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 23:57:56 +00:00
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:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user