contracts: Add host function tracing (#13648)

This commit is contained in:
PG Herveou
2023-03-27 19:33:59 +02:00
committed by GitHub
parent 39e5548c66
commit 836f544c86
4 changed files with 959 additions and 908 deletions
+12
View File
@@ -135,6 +135,18 @@ to `error` in order to prevent them from spamming the console.
`--dev`: Use a dev chain spec
`--tmp`: Use temporary storage for chain data (the chain state is deleted on exit)
## Host function tracing
For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, and what the result was.
In order to see these messages on the node console, the log level for the `runtime::contracts::strace` target needs to be raised to the `trace` level.
Example:
```bash
cargo run --release -- --dev -lerror,runtime::contracts::strace=trace,runtime::contracts=debug
```
## Unstable Interfaces
Driven by the desire to have an iterative approach in developing new contract interfaces
@@ -596,6 +596,7 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
let impls = def.host_funcs.iter().map(|f| {
// skip the context and memory argument
let params = f.item.sig.inputs.iter().skip(2);
let (module, name, body, wasm_output, output) = (
f.module(),
&f.name,
@@ -606,6 +607,39 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
let is_stable = f.is_stable;
let not_deprecated = f.not_deprecated;
// wrapped host function body call with host function traces
// see https://github.com/paritytech/substrate/tree/master/frame/contracts#host-function-tracing
let wrapped_body_with_trace = {
let trace_fmt_args = params.clone().filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(p) => {
match *p.pat.clone() {
syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()),
_ => None,
}
},
});
let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::<Vec<_>>().join(", ");
let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);
quote! {
if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
let result = #body;
{
use sp_std::fmt::Write;
let mut w = sp_std::Writer::default();
let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result);
let msg = core::str::from_utf8(&w.inner()).unwrap_or_default();
ctx.ext().append_debug_buffer(msg);
}
result
} else {
#body
}
}
};
// If we don't expand blocks (implementing for `()`) we change a few things:
// - We replace any code by unreachable!
// - Allow unused variables as the code that uses is not expanded
@@ -617,7 +651,7 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2)
.memory()
.expect("Memory must be set when setting up host data; qed")
.data_and_store_mut(&mut __caller__);
#body
#wrapped_body_with_trace
} }
} else {
quote! { || -> #wasm_output {
@@ -84,6 +84,7 @@ enum KeyType {
/// will not be changed or removed. This means that any contract **must not** exhaustively
/// match return codes. Instead, contracts should prepare for unknown variants and deal with
/// those errors gracefully in order to be forward compatible.
#[derive(Debug)]
#[repr(u32)]
pub enum ReturnCode {
/// API call successful.
+911 -907
View File
File diff suppressed because it is too large Load Diff