Skip to content

Commit

Permalink
handle error in tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
pgherveou committed Dec 27, 2024
1 parent 9c4eb15 commit c18ba8d
Show file tree
Hide file tree
Showing 17 changed files with 156 additions and 58 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion substrate/frame/revive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-revive-fixtures = { workspace = true, optional = true }
pallet-revive-uapi = { workspace = true, features = ["scale"] }
pallet-revive-uapi = { workspace = true, features = ["scale", "serde"] }
pallet-revive-proc-macro = { workspace = true }
pallet-transaction-payment = { workspace = true }
sp-api = { workspace = true }
Expand Down
5 changes: 4 additions & 1 deletion substrate/frame/revive/rpc/examples/js/contracts/Tracing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ contract TracingCaller {

function start(uint256 counter) external {
if (counter == 0) {
return;
uint256 a = 1;
uint256 b = 0;
uint256 c = a / b;
return;
}

TracingCallee(callee).consumeGas();
Expand Down
Binary file modified substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm
Binary file not shown.
Binary file modified substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm
Binary file not shown.
Binary file modified substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm
Binary file not shown.
Binary file modified substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm
Binary file not shown.
44 changes: 30 additions & 14 deletions substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
polkadotSdkPath,
} from './util.ts'
import { afterAll, afterEach, describe, expect, test } from 'bun:test'
import { encodeFunctionData, Hex, parseEther } from 'viem'
import { encodeAbiParameters, encodeFunctionData, Hex, parseEther } from 'viem'
import { ErrorTesterAbi } from '../abi/ErrorTester'
import { TracingCallerAbi } from '../abi/TracingCaller.ts'
import { TracingCalleeAbi } from '../abi/TracingCallee.ts'
Expand Down Expand Up @@ -371,23 +371,39 @@ for (const env of envs) {
})()

console.error('Caller address:', callerAddress)
const txHash = await (async () => {
const { request } = await env.serverWallet.simulateContract({
address: callerAddress,
abi: TracingCallerAbi,
functionName: 'start',
args: [2n],
})
//const txHash = await (async () => {
// const { request } = await env.serverWallet.simulateContract({
// address: callerAddress,
// abi: TracingCallerAbi,
// functionName: 'start',
// args: [2n],
// })
//
// const hash = await env.serverWallet.writeContract(request)
// await env.serverWallet.waitForTransactionReceipt({ hash })
//
//
// return hash
//})()

const hash = await env.serverWallet.writeContract(request)
await env.serverWallet.waitForTransactionReceipt({ hash })
let data = encodeFunctionData({
abi: TracingCallerAbi,
functionName: 'start',
args: [2n],
})

return hash
})()
const res = await env.debugClient.traceCall({
account: env.accountWallet.account,
data,
to: callerAddress,
})

console.error('Transaction hash:', txHash)
const res = await env.debugClient.traceTransaction(txHash)
console.error(res)
Bun.write('/tmp/tracing.json', JSON.stringify(res, null, 2))
//console.error('Transaction hash:', txHash)
//const res = await env.debugClient.traceTransaction(txHash)
//// serialize the result to
//console.error(res)
})
})
}
14 changes: 0 additions & 14 deletions substrate/frame/revive/rpc/examples/js/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,3 @@ export function assert(condition: any, message: string): asserts condition {
throw new Error(message)
}
}

const debugClient = createClient({
chain: mainnet,
transport: http(),
}).extend((client) => ({
// ...
async traceCall(args: CallParameters) {
return client.request({
method: 'debug_traceCall',
params: [formatTransactionRequest(args), 'latest', {}],
})
},
// ...
}))
24 changes: 22 additions & 2 deletions substrate/frame/revive/rpc/examples/js/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { spawnSync } from 'bun'
import { resolve } from 'path'
import { readFileSync } from 'fs'
import { createClient, createWalletClient, defineChain, type Hex, http, publicActions } from 'viem'
import {
CallParameters,
createClient,
createWalletClient,
defineChain,
formatTransactionRequest,
type Hex,
http,
publicActions,
} from 'viem'
import { privateKeyToAccount, nonceManager } from 'viem/accounts'

export function getByteCode(name: string, evm: boolean = false): Hex {
Expand Down Expand Up @@ -85,14 +94,25 @@ export async function createEnv(name: 'geth' | 'kitchensink') {
chain,
}).extend(publicActions)

const tracerConfig = { withLog: true }
const debugClient = createClient({
chain,
transport,
}).extend((client) => ({
async traceTransaction(txHash: Hex) {
return client.request({
method: 'debug_traceTransaction' as any,
params: [txHash, { tracer: 'callTracer' } as any],
params: [txHash, { tracer: 'callTracer', tracerConfig } as any],
})
},
async traceCall(args: CallParameters) {
return client.request({
method: 'debug_traceCall' as any,
params: [
formatTransactionRequest(args),
'latest',
{ tracer: 'callTracer', tracerConfig } as any,
],
})
},
// ...
Expand Down
32 changes: 29 additions & 3 deletions substrate/frame/revive/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use crate::{
primitives::ExecReturnValue,
BalanceOf,
};
use crate::{Config, GasMeter, LOG_TARGET};
use crate::{Config, DispatchError, GasMeter, LOG_TARGET};
use alloc::vec::Vec;
use sp_core::{H160, U256};

Expand Down Expand Up @@ -53,6 +53,9 @@ pub trait Tracing<T: Config>: Default {

/// Called after a contract call is executed
fn exit_child_span(&mut self, output: &ExecReturnValue, gas_meter: &GasMeter<T>);

/// Called when a contract call terminates with an error
fn exit_child_span_with_error(&mut self, error: DispatchError, gas_meter: &GasMeter<T>);
}

impl Tracer {
Expand Down Expand Up @@ -111,7 +114,6 @@ where
}
}

//fn after_call(&mut self, output: &ExecReturnValue);
fn exit_child_span(&mut self, output: &ExecReturnValue, gas_meter: &GasMeter<T>) {
match self {
Tracer::CallTracer(tracer) => {
Expand All @@ -122,6 +124,17 @@ where
},
}
}

fn exit_child_span_with_error(&mut self, error: DispatchError, gas_meter: &GasMeter<T>) {
match self {
Tracer::CallTracer(tracer) => {
<CallTracer as Tracing<T>>::exit_child_span_with_error(tracer, error, gas_meter);
},
Tracer::Disabled => {
log::trace!(target: LOG_TARGET, "call failed {error:?}")
},
}
}
}

#[derive(Default, Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -171,8 +184,21 @@ where
// Set the output of the current trace
let current_index = self.current_stack.pop().unwrap();
let trace = &mut self.traces[current_index];
trace.output = output.data.clone();
trace.output = output.clone();
trace.gas_used = gas_meter.gas_consumed();

// move the current trace into its parent
if let Some(parent_index) = self.current_stack.last() {
let child_trace = self.traces.remove(current_index);
self.traces[*parent_index].calls.push(child_trace);
}
}
fn exit_child_span_with_error(&mut self, error: DispatchError, gas_meter: &GasMeter<T>) {
// Set the output of the current trace
let current_index = self.current_stack.pop().unwrap();
let trace = &mut self.traces[current_index];
trace.gas_used = gas_meter.gas_consumed();
trace.error = Some(format!("{error:?}"));

// move the current trace into its parent
if let Some(parent_index) = self.current_stack.last() {
Expand Down
63 changes: 44 additions & 19 deletions substrate/frame/revive/src/evm/api/debug_rpc_types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![allow(missing_docs)]
use crate::Weight;
use crate::{ExecReturnValue, Weight};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use scale_info::TypeInfo;
Expand Down Expand Up @@ -27,28 +27,43 @@ pub enum CallType {
/// The traces of a transaction.
#[derive(TypeInfo, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(untagged)]
pub enum Traces<Gas = Weight> {
CallTraces(Vec<CallTrace<Gas>>),
pub enum Traces<Gas = Weight, Output = ExecReturnValue>
where
Output: Default + PartialEq,
{
CallTraces(Vec<CallTrace<Gas, Output>>),
}

impl<Gas> Traces<Gas> {
impl<Gas, Output: Default + PartialEq> Traces<Gas, Output> {
/// Return mapped traces with the given gas mapper.
pub fn map<F, T>(self, gas_mapper: F) -> Traces<T>
pub fn map<T, V>(
self,
gas_mapper: impl Fn(Gas) -> T + Copy,
output_mapper: impl Fn(Output) -> V + Copy,
) -> Traces<T, V>
where
F: Fn(Gas) -> T + Copy,
V: Default + PartialEq,
{
match self {
Traces::CallTraces(traces) =>
Traces::CallTraces(traces.into_iter().map(|trace| trace.map(gas_mapper)).collect()),
Traces::CallTraces(traces) => Traces::CallTraces(
traces.into_iter().map(|trace| trace.map(gas_mapper, output_mapper)).collect(),
),
}
}
}

pub fn is_default<T: Default + PartialEq>(value: &T) -> bool {
*value == T::default()
}

/// A smart contract execution call trace.
#[derive(
TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq,
)]
pub struct CallTrace<Gas = Weight> {
pub struct CallTrace<Gas = Weight, Output = ExecReturnValue>
where
Output: Default + PartialEq,
{
/// Address of the sender.
pub from: H160,
/// Address of the receiver.
Expand All @@ -61,36 +76,46 @@ pub struct CallTrace<Gas = Weight> {
/// Type of call.
#[serde(rename = "type")]
pub call_type: CallType,
/// Gas limit.
/// Amount of gas provided for the call.
pub gas: Gas,
/// Amount of gas used.
#[serde(rename = "gasUsed")]
pub gas_used: Gas,
/// Return data.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub output: Vec<u8>,
/// Amount of gas provided for the call.
#[serde(flatten, skip_serializing_if = "is_default")]
pub output: Output,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
// TODO: add revertReason
/// List of sub-calls.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub calls: Vec<CallTrace<Gas>>,
pub calls: Vec<CallTrace<Gas, Output>>,
}

impl<Gas> CallTrace<Gas> {
impl<Gas, Output> CallTrace<Gas, Output>
where
Output: Default + PartialEq,
{
/// Return a new call gas with a mapped gas value.
pub fn map<F, T>(self, gas_mapper: F) -> CallTrace<T>
pub fn map<T, V>(
self,
gas_mapper: impl Fn(Gas) -> T + Copy,
output_mapper: impl Fn(Output) -> V + Copy,
) -> CallTrace<T, V>
where
F: Fn(Gas) -> T + Copy,
V: Default + PartialEq,
{
CallTrace {
from: self.from,
to: self.to,
input: self.input,
value: self.value,
call_type: self.call_type,
error: self.error,
gas: gas_mapper(self.gas),
gas_used: gas_mapper(self.gas_used),
output: self.output,
calls: self.calls.into_iter().map(|call| call.map(gas_mapper)).collect(),
output: output_mapper(self.output),
calls: self.calls.into_iter().map(|call| call.map(gas_mapper, output_mapper)).collect(),
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,14 @@ where

let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data)
.unwrap_or_else(|| executable.execute(self, entry_point, input_data))
.map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
.map_err(|e| {
<Tracer as Tracing<T>>::exit_child_span_with_error(
self.tracer,
e.error,
&top_frame_mut!(self).nested_gas,
);
ExecError { error: e.error, origin: ErrorOrigin::Callee }
})?;

<Tracer as Tracing<T>>::exit_child_span(
self.tracer,
Expand Down
13 changes: 12 additions & 1 deletion substrate/frame/revive/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,18 @@ pub enum ContractAccessError {
}

/// Output of a contract call or instantiation which ran to completion.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Default)]
#[derive(
Clone,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
Encode,
Decode,
RuntimeDebug,
TypeInfo,
Default,
)]
pub struct ExecReturnValue {
/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
pub flags: ReturnFlags,
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4845,7 +4845,7 @@ fn tracing_works() {
let result = builder::bare_call(addr).data((3u32, addr_callee).encode()).build();
println!("{}", serde_json::to_string_pretty(&result.traces).unwrap());

let traces = result.traces.map(|_| Weight::default());
let traces = result.traces.map(|_| Weight::default(), |res| res);

assert_eq!(
traces,
Expand Down
4 changes: 3 additions & 1 deletion substrate/frame/revive/uapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ codec = { features = [
"max-encoded-len",
], optional = true, workspace = true }
pallet-revive-proc-macro = { workspace = true }
serde = { features = [ "alloc", "derive", ], optional = true, workspace = true, default-features = false }

[target.'cfg(target_arch = "riscv64")'.dependencies]
polkavm-derive = { version = "0.18.0" }
Expand All @@ -29,6 +30,7 @@ features = ["unstable-hostfn"]
targets = ["riscv64imac-unknown-none-elf"]

[features]
default = ["scale"]
default = ["scale", "serde"]
scale = ["dep:codec", "scale-info"]
serde = ["dep:serde"]
unstable-hostfn = []
Loading

0 comments on commit c18ba8d

Please sign in to comment.