From cd464f9cfdc76811fae75a9487efe3f7546eaa26 Mon Sep 17 00:00:00 2001 From: PG Herveou <pgherveou@gmail.com> Date: Thu, 24 Aug 2023 09:56:28 +0200 Subject: [PATCH] Contracts: Update Config::Debug (#14789) * Update Debug trait * Rename * tweak * fmt * Better namings * rm unsafe-debug * rework doc * nit * fix comment * clippy * update naming * Rename file * fmt fixes * rename * Move tracing behind umbrella Debugging trait * fix * fix comment * reorder imports * comment * update doc * add missing doc * add missing doc * Update Debugging -> Debugger * Update bin/node/runtime/Cargo.toml --- substrate/bin/node/runtime/Cargo.toml | 1 - substrate/bin/node/runtime/src/lib.rs | 1 - substrate/frame/contracts/Cargo.toml | 1 - substrate/frame/contracts/src/debug.rs | 55 +++++++++++++++++++ substrate/frame/contracts/src/exec.rs | 14 ++--- substrate/frame/contracts/src/lib.rs | 16 +++--- substrate/frame/contracts/src/tests.rs | 10 ++-- .../tests/{unsafe_debug.rs => test_debug.rs} | 39 +++++++------ substrate/frame/contracts/src/unsafe_debug.rs | 47 ---------------- substrate/scripts/ci/gitlab/pipeline/test.yml | 2 +- 10 files changed, 97 insertions(+), 89 deletions(-) create mode 100644 substrate/frame/contracts/src/debug.rs rename substrate/frame/contracts/src/tests/{unsafe_debug.rs => test_debug.rs} (83%) delete mode 100644 substrate/frame/contracts/src/unsafe_debug.rs diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index f8ecf656c65..0c151437548 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -373,4 +373,3 @@ try-runtime = [ "pallet-whitelist/try-runtime", "sp-runtime/try-runtime", ] -unsafe-debug = [ "pallet-contracts/unsafe-debug" ] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index b536b9547b7..9b943d2f66c 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1264,7 +1264,6 @@ impl pallet_contracts::Config for Runtime { type Migrations = pallet_contracts::migration::codegen::BenchMigrations; type MaxDelegateDependencies = ConstU32<32>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - #[cfg(feature = "unsafe-debug")] type Debug = (); type Environment = (); } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 3ad5367678d..75a6093dffa 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -114,4 +114,3 @@ try-runtime = [ "pallet-utility/try-runtime", "sp-runtime/try-runtime", ] -unsafe-debug = [] diff --git a/substrate/frame/contracts/src/debug.rs b/substrate/frame/contracts/src/debug.rs new file mode 100644 index 00000000000..a92f428c8f8 --- /dev/null +++ b/substrate/frame/contracts/src/debug.rs @@ -0,0 +1,55 @@ +pub use crate::exec::ExportedFunction; +use crate::{CodeHash, Config, LOG_TARGET}; +use pallet_contracts_primitives::ExecReturnValue; + +/// Umbrella trait for all interfaces that serves for debugging. +pub trait Debugger<T: Config>: Tracing<T> {} + +impl<T: Config, V> Debugger<T> for V where V: Tracing<T> {} + +/// Defines methods to capture contract calls, enabling external observers to +/// measure, trace, and react to contract interactions. +pub trait Tracing<T: Config> { + /// The type of [`CallSpan`] that is created by this trait. + type CallSpan: CallSpan; + + /// Creates a new call span to encompass the upcoming contract execution. + /// + /// This method should be invoked just before the execution of a contract and + /// marks the beginning of a traceable span of execution. + /// + /// # Arguments + /// + /// * `code_hash` - The code hash of the contract being called. + /// * `entry_point` - Describes whether the call is the constructor or a regular call. + /// * `input_data` - The raw input data of the call. + fn new_call_span( + code_hash: &CodeHash<T>, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> Self::CallSpan; +} + +/// Defines a span of execution for a contract call. +pub trait CallSpan { + /// Called just after the execution of a contract. + /// + /// # Arguments + /// + /// * `output` - The raw output of the call. + fn after_call(self, output: &ExecReturnValue); +} + +impl<T: Config> Tracing<T> for () { + type CallSpan = (); + + fn new_call_span(code_hash: &CodeHash<T>, entry_point: ExportedFunction, input_data: &[u8]) { + log::trace!(target: LOG_TARGET, "call {entry_point:?} hash: {code_hash:?}, input_data: {input_data:?}") + } +} + +impl CallSpan for () { + fn after_call(self, output: &ExecReturnValue) { + log::trace!(target: LOG_TARGET, "call result {output:?}") + } +} diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index a4d0760e6c0..1ba44220ff8 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -15,9 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "unsafe-debug")] -use crate::unsafe_debug::ExecutionObserver; use crate::{ + debug::{CallSpan, Tracing}, gas::GasMeter, storage::{self, meter::Diff, WriteOutcome}, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, @@ -908,20 +907,15 @@ where // Every non delegate call or instantiate also optionally transfers the balance. self.initial_transfer()?; - #[cfg(feature = "unsafe-debug")] - let (code_hash, input_clone) = { - let code_hash = *executable.code_hash(); - T::Debug::before_call(&code_hash, entry_point, &input_data); - (code_hash, input_data.clone()) - }; + let call_span = + T::Debug::new_call_span(executable.code_hash(), entry_point, &input_data); // Call into the Wasm blob. let output = executable .execute(self, &entry_point, input_data) .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; - #[cfg(feature = "unsafe-debug")] - T::Debug::after_call(&code_hash, entry_point, input_clone, &output); + call_span.after_call(&output); // Avoid useless work that would be reverted anyways. if output.did_revert() { diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index f67e4d7035e..2b9dd07b3f6 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -96,8 +96,8 @@ mod storage; mod wasm; pub mod chain_extension; +pub mod debug; pub mod migration; -pub mod unsafe_debug; pub mod weights; #[cfg(test)] @@ -144,6 +144,7 @@ use sp_std::{fmt::Debug, prelude::*}; pub use crate::{ address::{AddressGenerator, DefaultAddressGenerator}, + debug::Tracing, exec::Frame, migration::{MigrateSequence, Migration, NoopMigration}, pallet::*, @@ -219,6 +220,7 @@ pub struct Environment<T: Config> { #[frame_support::pallet] pub mod pallet { use super::*; + use crate::debug::Debugger; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_runtime::Perbill; @@ -390,13 +392,11 @@ pub mod pallet { /// ``` type Migrations: MigrateSequence; - /// Type that provides debug handling for the contract execution process. - /// - /// # Warning - /// - /// Do **not** use it in a production environment or for benchmarking purposes. - #[cfg(feature = "unsafe-debug")] - type Debug: unsafe_debug::UnsafeDebug<Self>; + /// # Note + /// For most production chains, it's recommended to use the `()` implementation of this + /// trait. This implementation offers additional logging when the log target + /// "runtime::contracts" is set to trace. + type Debug: Debugger<Self>; /// Type that bundles together all the runtime configurable interface types. /// diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index b421bbf3e30..8cc6d00b3d4 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -16,9 +16,12 @@ // limitations under the License. mod pallet_dummy; -mod unsafe_debug; +mod test_debug; -use self::test_utils::{ensure_stored, expected_deposit, hash}; +use self::{ + test_debug::TestDebug, + test_utils::{ensure_stored, expected_deposit, hash}, +}; use crate::{ self as pallet_contracts, chain_extension::{ @@ -479,8 +482,7 @@ impl Config for Test { type Migrations = crate::migration::codegen::BenchMigrations; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type MaxDelegateDependencies = MaxDelegateDependencies; - #[cfg(feature = "unsafe-debug")] - type Debug = unsafe_debug::TestDebugger; + type Debug = TestDebug; type Environment = (); } diff --git a/substrate/frame/contracts/src/tests/unsafe_debug.rs b/substrate/frame/contracts/src/tests/test_debug.rs similarity index 83% rename from substrate/frame/contracts/src/tests/unsafe_debug.rs rename to substrate/frame/contracts/src/tests/test_debug.rs index 160a6ed6dc5..ba936a4588d 100644 --- a/substrate/frame/contracts/src/tests/unsafe_debug.rs +++ b/substrate/frame/contracts/src/tests/test_debug.rs @@ -1,7 +1,5 @@ -#![cfg(feature = "unsafe-debug")] - use super::*; -use crate::unsafe_debug::{ExecutionObserver, ExportedFunction}; +use crate::debug::{CallSpan, ExportedFunction, Tracing}; use frame_support::traits::Currency; use pallet_contracts_primitives::ExecReturnValue; use pretty_assertions::assert_eq; @@ -19,31 +17,40 @@ thread_local! { static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new()); } -pub struct TestDebugger; +pub struct TestDebug; +pub struct TestCallSpan { + code_hash: CodeHash<Test>, + call: ExportedFunction, + input: Vec<u8>, +} + +impl Tracing<Test> for TestDebug { + type CallSpan = TestCallSpan; -impl ExecutionObserver<CodeHash<Test>> for TestDebugger { - fn before_call(code_hash: &CodeHash<Test>, entry_point: ExportedFunction, input_data: &[u8]) { + fn new_call_span( + code_hash: &CodeHash<Test>, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> TestCallSpan { DEBUG_EXECUTION_TRACE.with(|d| { d.borrow_mut().push(DebugFrame { - code_hash: code_hash.clone(), + code_hash: *code_hash, call: entry_point, input: input_data.to_vec(), result: None, }) }); + TestCallSpan { code_hash: *code_hash, call: entry_point, input: input_data.to_vec() } } +} - fn after_call( - code_hash: &CodeHash<Test>, - entry_point: ExportedFunction, - input_data: Vec<u8>, - output: &ExecReturnValue, - ) { +impl CallSpan for TestCallSpan { + fn after_call(self, output: &ExecReturnValue) { DEBUG_EXECUTION_TRACE.with(|d| { d.borrow_mut().push(DebugFrame { - code_hash: code_hash.clone(), - call: entry_point, - input: input_data, + code_hash: self.code_hash, + call: self.call, + input: self.input, result: Some(output.data.clone()), }) }); diff --git a/substrate/frame/contracts/src/unsafe_debug.rs b/substrate/frame/contracts/src/unsafe_debug.rs deleted file mode 100644 index 418af5e605d..00000000000 --- a/substrate/frame/contracts/src/unsafe_debug.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![cfg(feature = "unsafe-debug")] - -pub use crate::exec::ExportedFunction; -use crate::{CodeHash, Vec}; -use pallet_contracts_primitives::ExecReturnValue; - -/// Umbrella trait for all interfaces that serves for debugging, but are not suitable for any -/// production or benchmarking use. -pub trait UnsafeDebug<T: frame_system::Config>: ExecutionObserver<CodeHash<T>> {} - -impl<T: frame_system::Config, D> UnsafeDebug<T> for D where D: ExecutionObserver<CodeHash<T>> {} - -/// Defines the interface between pallet contracts and the outside observer. -/// -/// The intended use is the environment, where the observer holds directly the whole runtime -/// (externalities) and thus can react to the execution breakpoints synchronously. -/// -/// This definitely *should not* be used in any production or benchmarking setting, since handling -/// callbacks might be arbitrarily expensive and thus significantly influence performance. -pub trait ExecutionObserver<CodeHash> { - /// Called just before the execution of a contract. - /// - /// # Arguments - /// - /// * `code_hash` - The code hash of the contract being called. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - fn before_call(_code_hash: &CodeHash, _entry_point: ExportedFunction, _input_data: &[u8]) {} - - /// Called just after the execution of a contract. - /// - /// # Arguments - /// - /// * `code_hash` - The code hash of the contract being called. - /// * `entry_point` - Describes whether the call was the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - /// * `output` - The raw output of the call. - fn after_call( - _code_hash: &CodeHash, - _entry_point: ExportedFunction, - _input_data: Vec<u8>, - _output: &ExecReturnValue, - ) { - } -} - -impl<CodeHash> ExecutionObserver<CodeHash> for () {} diff --git a/substrate/scripts/ci/gitlab/pipeline/test.yml b/substrate/scripts/ci/gitlab/pipeline/test.yml index 71ae5105a61..d986fa34022 100644 --- a/substrate/scripts/ci/gitlab/pipeline/test.yml +++ b/substrate/scripts/ci/gitlab/pipeline/test.yml @@ -234,7 +234,7 @@ test-linux-stable: --locked --release --verbose - --features runtime-benchmarks,try-runtime,experimental,unsafe-debug + --features runtime-benchmarks,try-runtime,experimental --manifest-path ./bin/node/cli/Cargo.toml --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} # we need to update cache only from one job -- GitLab