mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 14:11:09 +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:
Generated
+16
-1
@@ -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]]
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 )*
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user