mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 16:51:03 +00:00
Add a new host function for reporting fatal errors; make WASM backtraces readable when printing out errors (#10741)
* Add a new host function for reporting fatal errors * Fix one of the wasmtime executor tests * Have `#[runtime_interface(wasm_only)]` actually mean WASM-only, and not no_std-only * Print out errors through `Display` instead of `Debug` * Switch one more trait to require `Error` for its error instead of only `Debug` * Align to review comments
This commit is contained in:
@@ -297,7 +297,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
|
||||
let state_version = self.call
|
||||
.runtime_version_at(&at)
|
||||
.map(|v| v.state_version())
|
||||
.map_err(|e| format!("Failed to get state version: {:?}", e))?;
|
||||
.map_err(|e| format!("Failed to get state version: {}", e))?;
|
||||
|
||||
self.changes.replace(Default::default()).into_storage_changes(
|
||||
backend,
|
||||
|
||||
@@ -69,7 +69,7 @@ pub enum Error {
|
||||
ExtrinsicRootInvalid { received: String, expected: String },
|
||||
|
||||
// `inner` cannot be made member, since it lacks `std::error::Error` trait bounds.
|
||||
#[error("Execution failed: {0:?}")]
|
||||
#[error("Execution failed: {0}")]
|
||||
Execution(Box<dyn sp_state_machine::Error>),
|
||||
|
||||
#[error("Blockchain")]
|
||||
|
||||
@@ -98,7 +98,7 @@ pub trait Environment<B: BlockT> {
|
||||
+ Unpin
|
||||
+ 'static;
|
||||
/// Error which can occur upon creation.
|
||||
type Error: From<Error> + std::fmt::Debug + 'static;
|
||||
type Error: From<Error> + std::error::Error + 'static;
|
||||
|
||||
/// Initialize the proposal logic on top of a specific header. Provide
|
||||
/// the authorities at that header.
|
||||
@@ -191,7 +191,7 @@ mod private {
|
||||
/// Proposers are generic over bits of "consensus data" which are engine-specific.
|
||||
pub trait Proposer<B: BlockT> {
|
||||
/// Error type which can occur when proposing or evaluating.
|
||||
type Error: From<Error> + std::fmt::Debug + 'static;
|
||||
type Error: From<Error> + std::error::Error + 'static;
|
||||
/// The transaction type used by the backend.
|
||||
type Transaction: Default + Send + 'static;
|
||||
/// Future that resolves to a committed proposal with an optional proof.
|
||||
|
||||
@@ -66,3 +66,22 @@ with-tracing = [
|
||||
disable_panic_handler = []
|
||||
disable_oom = []
|
||||
disable_allocator = []
|
||||
|
||||
# This feature flag controls the runtime's behavior when encountering
|
||||
# a panic or when it runs out of memory, improving the diagnostics.
|
||||
#
|
||||
# When enabled the runtime will marshal the relevant error message
|
||||
# to the host through the `PanicHandler::abort_on_panic` runtime interface.
|
||||
# This gives the caller direct programmatic access to the error message.
|
||||
#
|
||||
# When disabled the error message will only be printed out in the
|
||||
# logs, with the caller receving a generic "wasm `unreachable` instruction executed"
|
||||
# error message.
|
||||
#
|
||||
# This has no effect if both `disable_panic_handler` and `disable_oom`
|
||||
# are enabled.
|
||||
#
|
||||
# WARNING: Enabling this feature flag requires the `PanicHandler::abort_on_panic`
|
||||
# host function to be supported by the host. Do *not* enable it for your
|
||||
# runtime without first upgrading your host client!
|
||||
improved_panic_error_reporting = []
|
||||
|
||||
@@ -1290,6 +1290,17 @@ pub trait Allocator {
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM-only interface which allows for aborting the execution in case
|
||||
/// of an unrecoverable error.
|
||||
#[runtime_interface(wasm_only)]
|
||||
pub trait PanicHandler {
|
||||
/// Aborts the current execution with the given error message.
|
||||
#[trap_on_return]
|
||||
fn abort_on_panic(&mut self, message: &str) {
|
||||
self.register_panic_error_message(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that provides functions for logging from within the runtime.
|
||||
#[runtime_interface]
|
||||
pub trait Logging {
|
||||
@@ -1588,14 +1599,14 @@ pub trait RuntimeTasks {
|
||||
}
|
||||
|
||||
/// Allocator used by Substrate when executing the Wasm runtime.
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
|
||||
struct WasmAllocator;
|
||||
|
||||
#[cfg(all(not(feature = "disable_allocator"), not(feature = "std")))]
|
||||
#[cfg(all(target_arch = "wasm32", not(feature = "disable_allocator"), not(feature = "std")))]
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: WasmAllocator = WasmAllocator;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
|
||||
mod allocator_impl {
|
||||
use super::*;
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
@@ -1617,16 +1628,30 @@ mod allocator_impl {
|
||||
#[no_mangle]
|
||||
pub fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
let message = sp_std::alloc::format!("{}", info);
|
||||
logging::log(LogLevel::Error, "runtime", message.as_bytes());
|
||||
core::arch::wasm32::unreachable();
|
||||
#[cfg(feature = "improved_panic_error_reporting")]
|
||||
{
|
||||
panic_handler::abort_on_panic(&message);
|
||||
}
|
||||
#[cfg(not(feature = "improved_panic_error_reporting"))]
|
||||
{
|
||||
logging::log(LogLevel::Error, "runtime", message.as_bytes());
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/// A default OOM handler for WASM environment.
|
||||
#[cfg(all(not(feature = "disable_oom"), not(feature = "std")))]
|
||||
#[alloc_error_handler]
|
||||
pub fn oom(_: core::alloc::Layout) -> ! {
|
||||
logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting");
|
||||
core::arch::wasm32::unreachable();
|
||||
#[cfg(feature = "improved_panic_error_reporting")]
|
||||
{
|
||||
panic_handler::abort_on_panic("Runtime memory exhausted.");
|
||||
}
|
||||
#[cfg(not(feature = "improved_panic_error_reporting"))]
|
||||
{
|
||||
logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting");
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for Externalities implementation used in tests.
|
||||
@@ -1646,6 +1671,7 @@ pub type SubstrateHostFunctions = (
|
||||
crypto::HostFunctions,
|
||||
hashing::HostFunctions,
|
||||
allocator::HostFunctions,
|
||||
panic_handler::HostFunctions,
|
||||
logging::HostFunctions,
|
||||
sandbox::HostFunctions,
|
||||
crate::trie::HostFunctions,
|
||||
|
||||
+33
-6
@@ -32,11 +32,12 @@
|
||||
use crate::utils::{
|
||||
create_exchangeable_host_function_ident, create_function_ident_with_version,
|
||||
generate_crate_access, get_function_argument_names, get_function_arguments,
|
||||
get_runtime_interface,
|
||||
get_runtime_interface, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, TraitItemMethod,
|
||||
parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token,
|
||||
TraitItemMethod,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
@@ -74,14 +75,14 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool, tracing: bool) -> Res
|
||||
|
||||
/// Generates the bare function implementation for the given method for the host and wasm side.
|
||||
fn function_for_method(
|
||||
method: &TraitItemMethod,
|
||||
method: &RuntimeInterfaceFunction,
|
||||
latest_version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let std_impl =
|
||||
if !is_wasm_only { function_std_latest_impl(method, latest_version)? } else { quote!() };
|
||||
|
||||
let no_std_impl = function_no_std_impl(method)?;
|
||||
let no_std_impl = function_no_std_impl(method, is_wasm_only)?;
|
||||
|
||||
Ok(quote! {
|
||||
#std_impl
|
||||
@@ -91,20 +92,46 @@ fn function_for_method(
|
||||
}
|
||||
|
||||
/// Generates the bare function implementation for `cfg(not(feature = "std"))`.
|
||||
fn function_no_std_impl(method: &TraitItemMethod) -> Result<TokenStream> {
|
||||
fn function_no_std_impl(
|
||||
method: &RuntimeInterfaceFunction,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<TokenStream> {
|
||||
let function_name = &method.sig.ident;
|
||||
let host_function_name = create_exchangeable_host_function_ident(&method.sig.ident);
|
||||
let args = get_function_arguments(&method.sig);
|
||||
let arg_names = get_function_argument_names(&method.sig);
|
||||
let return_value = &method.sig.output;
|
||||
let return_value = if method.should_trap_on_return() {
|
||||
syn::ReturnType::Type(
|
||||
<Token![->]>::default(),
|
||||
Box::new(syn::TypeNever { bang_token: <Token![!]>::default() }.into()),
|
||||
)
|
||||
} else {
|
||||
method.sig.output.clone()
|
||||
};
|
||||
let maybe_unreachable = if method.should_trap_on_return() {
|
||||
quote! {
|
||||
; core::arch::wasm32::unreachable();
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version"));
|
||||
|
||||
let cfg_wasm_only = if is_wasm_only {
|
||||
quote! { #[cfg(target_arch = "wasm32")] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#cfg_wasm_only
|
||||
#[cfg(not(feature = "std"))]
|
||||
#( #attrs )*
|
||||
pub fn #function_name( #( #args, )* ) #return_value {
|
||||
// Call the host function
|
||||
#host_function_name.get()( #( #arg_names, )* )
|
||||
#maybe_unreachable
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ use crate::utils::{
|
||||
create_host_function_ident, generate_crate_access, get_function_argument_names,
|
||||
get_function_argument_names_and_types_without_ref, get_function_argument_types,
|
||||
get_function_argument_types_ref_and_mut, get_function_argument_types_without_ref,
|
||||
get_function_arguments, get_runtime_interface,
|
||||
get_function_arguments, get_runtime_interface, RuntimeInterfaceFunction,
|
||||
};
|
||||
|
||||
use syn::{
|
||||
@@ -205,7 +205,7 @@ fn generate_host_functions_struct(
|
||||
/// implementation of the function.
|
||||
fn generate_host_function_implementation(
|
||||
trait_name: &Ident,
|
||||
method: &TraitItemMethod,
|
||||
method: &RuntimeInterfaceFunction,
|
||||
version: u32,
|
||||
is_wasm_only: bool,
|
||||
) -> Result<(TokenStream, Ident, TokenStream)> {
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Re
|
||||
let crate_ = generate_crate_access();
|
||||
let interface = get_runtime_interface(trait_def)?;
|
||||
let methods = interface.all_versions().map(|(version, method)| {
|
||||
let mut cloned = method.clone();
|
||||
let mut cloned = (*method).clone();
|
||||
cloned.attrs.retain(|a| !a.path.is_ident("version"));
|
||||
cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version);
|
||||
cloned
|
||||
|
||||
@@ -39,18 +39,64 @@ mod attributes {
|
||||
syn::custom_keyword!(register_only);
|
||||
}
|
||||
|
||||
/// Runtime interface function with all associated versions of this function.
|
||||
pub struct RuntimeInterfaceFunction<'a> {
|
||||
latest_version_to_call: Option<u32>,
|
||||
versions: BTreeMap<u32, &'a TraitItemMethod>,
|
||||
/// A concrete, specific version of a runtime interface function.
|
||||
pub struct RuntimeInterfaceFunction {
|
||||
item: TraitItemMethod,
|
||||
should_trap_on_return: bool,
|
||||
}
|
||||
|
||||
impl<'a> RuntimeInterfaceFunction<'a> {
|
||||
fn new(version: VersionAttribute, trait_item: &'a TraitItemMethod) -> Self {
|
||||
Self {
|
||||
latest_version_to_call: version.is_callable().then(|| version.version),
|
||||
versions: BTreeMap::from([(version.version, trait_item)]),
|
||||
impl std::ops::Deref for RuntimeInterfaceFunction {
|
||||
type Target = TraitItemMethod;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunction {
|
||||
fn new(item: &TraitItemMethod) -> Result<Self> {
|
||||
let mut item = item.clone();
|
||||
let mut should_trap_on_return = false;
|
||||
item.attrs.retain(|attr| {
|
||||
if attr.path.is_ident("trap_on_return") {
|
||||
should_trap_on_return = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if should_trap_on_return {
|
||||
if !matches!(item.sig.output, syn::ReturnType::Default) {
|
||||
return Err(Error::new(
|
||||
item.sig.ident.span(),
|
||||
"Methods marked as #[trap_on_return] cannot return anything",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { item, should_trap_on_return })
|
||||
}
|
||||
|
||||
pub fn should_trap_on_return(&self) -> bool {
|
||||
self.should_trap_on_return
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime interface function with all associated versions of this function.
|
||||
struct RuntimeInterfaceFunctionSet {
|
||||
latest_version_to_call: Option<u32>,
|
||||
versions: BTreeMap<u32, RuntimeInterfaceFunction>,
|
||||
}
|
||||
|
||||
impl RuntimeInterfaceFunctionSet {
|
||||
fn new(version: VersionAttribute, trait_item: &TraitItemMethod) -> Result<Self> {
|
||||
Ok(Self {
|
||||
latest_version_to_call: version.is_callable().then(|| version.version),
|
||||
versions: BTreeMap::from([(
|
||||
version.version,
|
||||
RuntimeInterfaceFunction::new(trait_item)?,
|
||||
)]),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the latest version of this runtime interface function plus the actual function
|
||||
@@ -59,11 +105,11 @@ impl<'a> RuntimeInterfaceFunction<'a> {
|
||||
/// This isn't required to be the latest version, because a runtime interface function can be
|
||||
/// annotated with `register_only` to ensure that the host exposes the host function but it
|
||||
/// isn't used when compiling the runtime.
|
||||
pub fn latest_version_to_call(&self) -> Option<(u32, &TraitItemMethod)> {
|
||||
pub fn latest_version_to_call(&self) -> Option<(u32, &RuntimeInterfaceFunction)> {
|
||||
self.latest_version_to_call.map(|v| {
|
||||
(
|
||||
v,
|
||||
*self.versions.get(&v).expect(
|
||||
self.versions.get(&v).expect(
|
||||
"If latest_version_to_call has a value, the key with this value is in the versions; qed",
|
||||
),
|
||||
)
|
||||
@@ -74,7 +120,7 @@ impl<'a> RuntimeInterfaceFunction<'a> {
|
||||
fn add_version(
|
||||
&mut self,
|
||||
version: VersionAttribute,
|
||||
trait_item: &'a TraitItemMethod,
|
||||
trait_item: &TraitItemMethod,
|
||||
) -> Result<()> {
|
||||
if let Some(existing_item) = self.versions.get(&version.version) {
|
||||
let mut err = Error::new(trait_item.span(), "Duplicated version attribute");
|
||||
@@ -86,7 +132,8 @@ impl<'a> RuntimeInterfaceFunction<'a> {
|
||||
return Err(err)
|
||||
}
|
||||
|
||||
self.versions.insert(version.version, trait_item);
|
||||
self.versions
|
||||
.insert(version.version, RuntimeInterfaceFunction::new(trait_item)?);
|
||||
if self.latest_version_to_call.map_or(true, |v| v < version.version) &&
|
||||
version.is_callable()
|
||||
{
|
||||
@@ -98,22 +145,24 @@ impl<'a> RuntimeInterfaceFunction<'a> {
|
||||
}
|
||||
|
||||
/// All functions of a runtime interface grouped by the function names.
|
||||
pub struct RuntimeInterface<'a> {
|
||||
items: BTreeMap<syn::Ident, RuntimeInterfaceFunction<'a>>,
|
||||
pub struct RuntimeInterface {
|
||||
items: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet>,
|
||||
}
|
||||
|
||||
impl<'a> RuntimeInterface<'a> {
|
||||
impl RuntimeInterface {
|
||||
/// Returns an iterator over all runtime interface function
|
||||
/// [`latest_version_to_call`](RuntimeInterfaceFunction::latest_version).
|
||||
pub fn latest_versions_to_call(&self) -> impl Iterator<Item = (u32, &TraitItemMethod)> {
|
||||
/// [`latest_version_to_call`](RuntimeInterfaceFunctionSet::latest_version).
|
||||
pub fn latest_versions_to_call(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items.iter().filter_map(|(_, item)| item.latest_version_to_call())
|
||||
}
|
||||
|
||||
pub fn all_versions(&self) -> impl Iterator<Item = (u32, &TraitItemMethod)> {
|
||||
pub fn all_versions(&self) -> impl Iterator<Item = (u32, &RuntimeInterfaceFunction)> {
|
||||
self.items
|
||||
.iter()
|
||||
.flat_map(|(_, item)| item.versions.iter())
|
||||
.map(|(v, i)| (*v, *i))
|
||||
.map(|(v, i)| (*v, i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +337,8 @@ fn get_item_version(item: &TraitItemMethod) -> Result<Option<VersionAttribute>>
|
||||
}
|
||||
|
||||
/// Returns all runtime interface members, with versions.
|
||||
pub fn get_runtime_interface<'a>(trait_def: &'a ItemTrait) -> Result<RuntimeInterface<'a>> {
|
||||
let mut functions: BTreeMap<syn::Ident, RuntimeInterfaceFunction<'a>> = BTreeMap::new();
|
||||
pub fn get_runtime_interface(trait_def: &ItemTrait) -> Result<RuntimeInterface> {
|
||||
let mut functions: BTreeMap<syn::Ident, RuntimeInterfaceFunctionSet> = BTreeMap::new();
|
||||
|
||||
for item in get_trait_methods(trait_def) {
|
||||
let name = item.sig.ident.clone();
|
||||
@@ -301,7 +350,7 @@ pub fn get_runtime_interface<'a>(trait_def: &'a ItemTrait) -> Result<RuntimeInte
|
||||
|
||||
match functions.entry(name.clone()) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(RuntimeInterfaceFunction::new(version, item));
|
||||
entry.insert(RuntimeInterfaceFunctionSet::new(version, item)?);
|
||||
},
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().add_version(version, item)?;
|
||||
|
||||
@@ -1585,7 +1585,7 @@ impl Printable for Tuple {
|
||||
#[cfg(feature = "std")]
|
||||
pub trait BlockIdTo<Block: self::Block> {
|
||||
/// The error type that will be returned by the functions.
|
||||
type Error: std::fmt::Debug;
|
||||
type Error: std::error::Error;
|
||||
|
||||
/// Convert the given `block_id` to the corresponding block hash.
|
||||
fn to_hash(
|
||||
|
||||
@@ -305,6 +305,29 @@ pub trait FunctionContext {
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> Result<()>;
|
||||
/// Provides access to the sandbox.
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox;
|
||||
|
||||
/// Registers a panic error message within the executor.
|
||||
///
|
||||
/// This is meant to be used in situations where the runtime
|
||||
/// encounters an unrecoverable error and intends to panic.
|
||||
///
|
||||
/// Panicking in WASM is done through the [`unreachable`](https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-instr-control)
|
||||
/// instruction which causes an unconditional trap and immediately aborts
|
||||
/// the execution. It does not however allow for any diagnostics to be
|
||||
/// passed through to the host, so while we do know that *something* went
|
||||
/// wrong we don't have any direct indication of what *exactly* went wrong.
|
||||
///
|
||||
/// As a workaround we use this method right before the execution is
|
||||
/// actually aborted to pass an error message to the host so that it
|
||||
/// can associate it with the next trap, and return that to the caller.
|
||||
///
|
||||
/// A WASM trap should be triggered immediately after calling this method;
|
||||
/// otherwise the error message might be associated with a completely
|
||||
/// unrelated trap.
|
||||
///
|
||||
/// It should only be called once, however calling it more than once
|
||||
/// is harmless and will overwrite the previously set error message.
|
||||
fn register_panic_error_message(&mut self, message: &str);
|
||||
}
|
||||
|
||||
/// Sandbox memory identifier.
|
||||
|
||||
Reference in New Issue
Block a user