Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
23feffa
Fix tracing collect
pgherveou Sep 15, 2025
b565491
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Sep 15, 2025
9237ec2
Update substrate/frame/revive/src/evm/tracing/prestate_tracing.rs
pgherveou Sep 15, 2025
babb6be
[pallet-revive] fix GAS_PRICE (#9679)
pgherveou Sep 10, 2025
63903fb
add logger
pgherveou Sep 11, 2025
eea1a2b
wip
pgherveou Sep 12, 2025
5a67cee
wip
pgherveou Sep 12, 2025
52ad1e3
fix
pgherveou Sep 12, 2025
ebe0401
nit
pgherveou Sep 12, 2025
1785e8e
fix
pgherveou Sep 12, 2025
a1f734f
fix
pgherveou Sep 12, 2025
a459aa6
wip
pgherveou Sep 12, 2025
c206d14
wip
pgherveou Sep 13, 2025
ccf3025
fixes
pgherveou Sep 13, 2025
45d0c4c
fixes
pgherveou Sep 13, 2025
3a8d20c
wip
pgherveou Sep 14, 2025
15fa3a3
wip
pgherveou Sep 14, 2025
4df7eae
wip
pgherveou Sep 15, 2025
049c17d
fix
pgherveou Sep 15, 2025
813e2f9
nit
pgherveou Sep 15, 2025
ab31e6f
fix
pgherveou Sep 15, 2025
b557153
wip
pgherveou Sep 16, 2025
1a34ea6
wip
pgherveou Sep 17, 2025
cb9f79e
wip
pgherveou Sep 18, 2025
7972e46
wip
pgherveou Sep 19, 2025
8aca032
wip
pgherveou Sep 22, 2025
b766747
wip
pgherveou Sep 22, 2025
88d93d2
wip
pgherveou Sep 22, 2025
4356b6f
wip
pgherveou Sep 22, 2025
77ae1d2
wip
pgherveou Sep 23, 2025
9d85225
fixes
pgherveou Sep 23, 2025
dc62dd2
wip
pgherveou Sep 23, 2025
ff2884b
wip
pgherveou Sep 23, 2025
1108f51
Merge branch 'pg/revm-refactor' into pg/structlogger
pgherveou Sep 23, 2025
c7c3819
wip
pgherveou Sep 23, 2025
809c7f7
wip
pgherveou Sep 23, 2025
a507aa6
wip
pgherveou Sep 23, 2025
ed825fd
Merge branch 'pg/revm-refactor' into pg/structlogger-with-refactor
pgherveou Sep 23, 2025
5f08ae8
fix tests
pgherveou Sep 24, 2025
b7726d3
Merge branch 'master' into pg/structlogger-with-refactor
pgherveou Dec 12, 2025
57a5c60
fix
pgherveou Dec 12, 2025
cd96cd6
rm comment
pgherveou Dec 12, 2025
b2d8cee
Merge branch 'master' into pg/structlogger
pgherveou Dec 12, 2025
45d876c
fixes
pgherveou Dec 15, 2025
e7febda
fix for evm-test-suite
pgherveou Dec 15, 2025
a950637
wip
pgherveou Dec 15, 2025
3cdb2c4
wip
pgherveou Dec 15, 2025
ed70925
wip
pgherveou Dec 15, 2025
ab335f9
wip
pgherveou Dec 15, 2025
1bc241c
wip
pgherveou Dec 15, 2025
836421c
wip
pgherveou Dec 16, 2025
18bfd5b
Merge branch 'master' into pg/structlogger
pgherveou Dec 16, 2025
0b7972f
wip
pgherveou Dec 16, 2025
53738e6
fix
pgherveou Dec 16, 2025
82f5a85
wip
pgherveou Dec 16, 2025
bdeb769
simplify
pgherveou Dec 16, 2025
ea5401c
smaller diff
pgherveou Dec 16, 2025
5ee255c
fix
pgherveou Dec 16, 2025
4a26e0a
fix silly comments
pgherveou Dec 16, 2025
e02e167
Simplify
pgherveou Dec 16, 2025
3506246
add doc
pgherveou Dec 16, 2025
51bb523
tweak
pgherveou Dec 16, 2025
b1571d6
Update from github-actions[bot] running command 'prdoc --audience run…
github-actions[bot] Dec 16, 2025
3828cd0
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 16, 2025
86f00cf
fix
pgherveou Dec 16, 2025
0428a8a
fix PR doc
pgherveou Dec 16, 2025
3f8c066
rm diff
pgherveou Dec 16, 2025
8dbd983
fix
pgherveou Dec 16, 2025
5193cc8
simplify
pgherveou Dec 16, 2025
e7a7895
fix
pgherveou Dec 16, 2025
e8c0107
fix
pgherveou Dec 16, 2025
2730f59
increase limit in dev
pgherveou Dec 17, 2025
febe87d
Merge branch 'master' into pg/structlogger
pgherveou Dec 18, 2025
2a19058
fix tests
pgherveou Jan 6, 2026
f5b27bf
Merge branch 'master' into pg/structlogger
pgherveou Jan 7, 2026
a775d15
Merge branch 'master' into pg/structlogger
pgherveou Jan 12, 2026
02e8e79
update pr doc
pgherveou Jan 15, 2026
231b7b3
Merge branch 'master' into pg/structlogger
pgherveou Jan 15, 2026
4fe1445
add syscall details
pgherveou Jan 16, 2026
68425ab
fixes
pgherveou Jan 16, 2026
c29362d
formatting
pgherveou Jan 19, 2026
5e784ba
fixes
pgherveou Jan 22, 2026
9060ba5
fix fmt
pgherveou Jan 22, 2026
891b004
Merge branch 'master' into pg/structlogger
pgherveou Jan 23, 2026
7da932c
update rpc test to fix CI?
pgherveou Jan 26, 2026
9ecca66
fmt
pgherveou Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions prdoc/pr_9722.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
title: '[pallet-revive] opcode tracer'
doc:
- audience: Runtime Dev
description: |
This PR introduces a **Geth-compatible execution tracer** ([StructLogger](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger)) for pallet-revive

The tracer can be used to capture both EVM opcode and PVM syscall.
It can be used with the same RPC endpoint as Geth StructLogger.


Since it can be quite resource intensive, It can only be queried from the node when the **DebugSettings** are enabled (This is turned on now by default in the dev-node)

Tested in https://github.com/paritytech/evm-test-suite/pull/138


example:

```sh
❯ cast rpc debug_traceTransaction "<TX_HASH>" | jq

# or with options
# See list of options https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#struct-opcode-logger

❯ cast rpc debug_traceTransaction "<TX_HASH>", { "tracer": { "enableMemory": true } } | jq
```

The response includes additional fields compared to the original Geth debug RPC endpoints:

For the trace:
- `weight_consumed`: same as gas but expressed in Weight
- `base_call_weight`: the base cost of the transaction

For each step:
- `weight_cost`: same as gas_cost but expressed in Weight

For an EVM execution, the output will look like this

```json
{
"gas": 4208049,
"weight_consumed": { "ref_time": 126241470000, "proof_size": 4208 },
"base_call_weight": { "ref_time": 9000000000, "proof_size": 3000 },
"failed": false,
"returnValue": "0x",
"structLogs": [
{
"gas": 4109533,
"gasCost": 3,
"weight_cost": { "ref_time": 90000, "proof_size": 0 },
"depth": 1,
"pc": 0,
"op": "PUSH1",
"stack": []
},
{
"gas": 4109530,
"gasCost": 3,
"weight_cost": { "ref_time": 90000, "proof_size": 0 },
"depth": 1,
"pc": 2,
"op": "PUSH1",
"stack": [
"0x80"
]
},
{
"gas": 4109527,
"gasCost": 3,
"weight_cost": { "ref_time": 90000, "proof_size": 0 },
"depth": 1,
"pc": 4,
"op": "MSTORE",
"stack": [
"0x80",
"0x40"
]
}]
}
```

For PVM execution, each step includes additional fields not present in Geth:

- `args`: Array of syscall arguments (register values a0-a5) as hex strings
- `returned`: The syscall return value

These fields are enabled by default. To disable them, use `disableSyscallDetails: true`.

Example output with syscall details:

```json
{
"gas": 97108,
"gasCost": 131,
"weight_cost": { "ref_time": 3930000, "proof_size": 0 },
"depth": 1,
"op": "call_data_load",
"args": ["0x0", "0x4"],
"returned": "0x2a"
}
```
crates:
- name: pallet-revive
bump: patch
- name: pallet-revive-eth-rpc
bump: patch
- name: pallet-revive-proc-macro
bump: patch
- name: revive-dev-runtime
bump: patch
4 changes: 4 additions & 0 deletions substrate/frame/revive/dev-node/node/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ pub fn run_with_args(args: Vec<String>) -> sc_cli::Result<()> {
// Enforce dev
cli.run.shared_params.dev = true;

// Increase max_response_size for large trace responses
cli.run.rpc_params.rpc_max_response_size =
cli.run.rpc_params.rpc_max_response_size.max(50);

// Pass Default logging settings if none are specified
if std::env::var("RUST_LOG").is_err() && cli.run.shared_params.log.is_empty() {
cli.run.shared_params.log = "error,sc_rpc_server=info,runtime::revive=debug"
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/dev-node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ impl pallet_revive::Config for Runtime {
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
type Time = Timestamp;
type FeeInfo = FeeInfo<Address, Signature, EthExtraImpl>;
type DebugEnabled = ConstBool<false>;
type DebugEnabled = ConstBool<true>;
type GasScale = ConstU32<50000>;
}

Expand Down
41 changes: 40 additions & 1 deletion substrate/frame/revive/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ impl HostFnReturn {
Self::ReturnCode => parse_quote! { -> ReturnErrorCode },
}
}

fn trace_return_value(&self) -> TokenStream2 {
match self {
Self::Unit => quote! { None },
Self::U32 => quote! { result.as_ref().ok().map(|r| *r as u64) },
Self::ReturnCode => quote! { result.as_ref().ok().copied().map(u64::from) },
Self::U64 => quote! { result.as_ref().ok().copied() },
}
}
}

impl EnvDef {
Expand Down Expand Up @@ -317,12 +326,19 @@ fn expand_env(def: &EnvDef) -> TokenStream2 {
let bench_impls = expand_bench_functions(def);
let docs = expand_func_doc(def);
let all_syscalls = expand_func_list(def);
let lookup_syscall = expand_func_lookup(def);

quote! {
/// Returns the list of all syscalls.
pub fn list_syscalls() -> &'static [&'static [u8]] {
#all_syscalls
}

/// Return the index of a syscall in the `all_syscalls()` list.
pub fn lookup_syscall_index(name: &'static str) -> Option<u8> {
#lookup_syscall
}

impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> {
fn handle_ecall(
&mut self,
Expand Down Expand Up @@ -377,10 +393,10 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 {
let syscall_symbol = Literal::byte_string(name.as_bytes());
let body = &f.item.block;
let map_output = f.returns.map_output();
let trace_return = f.returns.trace_return_value();
let output = &f.item.sig.output;

// wrapped host function body call with host function traces
// see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/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,
Expand All @@ -396,11 +412,18 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 {
.collect::<Vec<_>>()
.join(", ");
let trace_fmt_str = format!("{}({}) = {{:?}} weight_consumed: {{:?}}", name, params_fmt_str);
let trace_args_for_tracer: Vec<_> = trace_fmt_args.clone().collect();

quote! {
crate::tracing::if_tracing(|tracer| {
tracer.enter_ecall(#name, &[#( #trace_args_for_tracer as u64 ),*], self)
});

// wrap body in closure to make sure the tracing is always executed
let result = (|| #body)();
::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result, self.ext.frame_meter().weight_consumed());

crate::tracing::if_tracing(|tracer| tracer.exit_step(self, #trace_return));
result
}
};
Expand Down Expand Up @@ -519,3 +542,19 @@ fn expand_func_list(def: &EnvDef) -> TokenStream2 {
}
}
}

fn expand_func_lookup(def: &EnvDef) -> TokenStream2 {
let arms = def.host_funcs.iter().enumerate().map(|(idx, f)| {
let name_str = &f.name;
quote! {
#name_str => Some(#idx as u8)
}
});

quote! {
match name {
#( #arms, )*
_ => None,
}
}
}
4 changes: 2 additions & 2 deletions substrate/frame/revive/rpc/examples/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> {
println!("\n\n=== Deploying contract ===\n\n");

let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?;
let tx = TransactionBuilder::new(&client)
let tx = TransactionBuilder::new(client.clone())
.value(5_000_000_000_000u128.into())
.input(input)
.send()
Expand All @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> {
}

println!("\n\n=== Calling contract ===\n\n");
let tx = TransactionBuilder::new(&client)
let tx = TransactionBuilder::new(client.clone())
.value(U256::from(1_000_000u32))
.to(contract_address)
.send()
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/revive/rpc/examples/eth-rpc-tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async fn test_eth_rpc(rpc_url: &str) -> anyhow::Result<()> {
println!("- balance: {balance:?}");

println!("\n\n=== Deploying dummy contract ===\n\n");
let tx = TransactionBuilder::new(&client).input(input).send().await?;
let tx = TransactionBuilder::new(client.clone()).input(input).send().await?;

println!("Hash: {:?}", tx.hash());
println!("Waiting for receipt...");
Expand All @@ -156,7 +156,7 @@ async fn test_eth_rpc(rpc_url: &str) -> anyhow::Result<()> {
println!("- Address: {contract_address:?}");

println!("\n\n=== Calling dummy contract ===\n\n");
let tx = TransactionBuilder::new(&client).to(contract_address).send().await?;
let tx = TransactionBuilder::new(client.clone()).to(contract_address).send().await?;

println!("Hash: {:?}", tx.hash());
println!("Waiting for receipt...");
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/rpc/examples/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> {
print_balance().await?;
println!("\n\n=== Transferring ===\n\n");

let tx = TransactionBuilder::new(&client)
let tx = TransactionBuilder::new(client.clone())
.signer(alith)
.value(value)
.to(ethan.address())
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/rpc/examples/tx-types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn main() -> anyhow::Result<()> {
] {
println!("\n\n=== TransactionType {tx_type:?} ===\n\n",);

let tx = TransactionBuilder::new(&client)
let tx = TransactionBuilder::new(client.clone())
.signer(alith.clone())
.value(value)
.to(ethan.address())
Expand Down
18 changes: 9 additions & 9 deletions substrate/frame/revive/rpc/src/apis/debug_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub trait DebugRpc {
async fn trace_block_by_number(
&self,
block: BlockNumberOrTag,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Vec<TransactionTrace>>;

/// Returns a transaction's traces by replaying it.
Expand All @@ -41,7 +41,7 @@ pub trait DebugRpc {
async fn trace_transaction(
&self,
transaction_hash: H256,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Trace>;

/// Dry run a call and returns the transaction's traces.
Expand All @@ -54,7 +54,7 @@ pub trait DebugRpc {
&self,
transaction: GenericTransaction,
block: BlockNumberOrTagOrHash,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Trace>;

#[method(name = "debug_getAutomine")]
Expand Down Expand Up @@ -94,28 +94,28 @@ impl DebugRpcServer for DebugRpcServerImpl {
async fn trace_block_by_number(
&self,
block: BlockNumberOrTag,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Vec<TransactionTrace>> {
let TracerConfig { config, timeout } = tracer_config;
let TracerConfig { config, timeout } = tracer_config.unwrap_or_default();
with_timeout(timeout, self.client.trace_block_by_number(block, config)).await
}

async fn trace_transaction(
&self,
transaction_hash: H256,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Trace> {
let TracerConfig { config, timeout } = tracer_config;
let TracerConfig { config, timeout } = tracer_config.unwrap_or_default();
with_timeout(timeout, self.client.trace_transaction(transaction_hash, config)).await
}

async fn trace_call(
&self,
transaction: GenericTransaction,
block: BlockNumberOrTagOrHash,
tracer_config: TracerConfig,
tracer_config: Option<TracerConfig>,
) -> RpcResult<Trace> {
let TracerConfig { config, timeout } = tracer_config;
let TracerConfig { config, timeout } = tracer_config.unwrap_or_default();
with_timeout(timeout, self.client.trace_call(transaction, block, config)).await
}

Expand Down
6 changes: 5 additions & 1 deletion substrate/frame/revive/rpc/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ fn build_client(
earliest_receipt_block: Option<SubstrateBlockNumber>,
node_rpc_url: &str,
database_url: &str,
max_request_size: u32,
max_response_size: u32,
abort_signal: Signals,
) -> anyhow::Result<Client> {
let fut = async {
let (api, rpc_client, rpc) = connect(node_rpc_url).await?;
let (api, rpc_client, rpc) = connect(node_rpc_url, max_request_size, max_response_size).await?;
let block_provider = SubxtBlockInfoProvider::new( api.clone(), rpc.clone()).await?;

let (pool, keep_latest_n_blocks) = if database_url == IN_MEMORY_DB {
Expand Down Expand Up @@ -213,6 +215,8 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> {
earliest_receipt_block,
&node_rpc_url,
&database_url,
rpc_config.max_request_size * 1024 * 1024,
rpc_config.max_response_size * 1024 * 1024,
tokio_runtime.block_on(async { Signals::capture() })?,
)?;

Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/revive/rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,15 @@ async fn get_automine(rpc_client: &RpcClient) -> bool {
/// clients.
pub async fn connect(
node_rpc_url: &str,
max_request_size: u32,
max_response_size: u32,
) -> Result<(OnlineClient<SrcChainConfig>, RpcClient, LegacyRpcMethods<SrcChainConfig>), ClientError>
{
log::info!(target: LOG_TARGET, "🌐 Connecting to node at: {node_rpc_url} ...");
let rpc_client = ReconnectingRpcClient::builder()
.retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10)))
.max_request_size(max_request_size)
.max_response_size(max_response_size)
.build(node_rpc_url.to_string())
.await?;
let rpc_client = RpcClient::new(rpc_client);
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/revive/rpc/src/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
}

impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
pub fn new(client: &Arc<Client>) -> Self {
pub fn new(client: Arc<Client>) -> Self {
Self {
client: Arc::clone(client),
client,
signer: Account::default(),
value: U256::zero(),
input: Bytes::default(),
Expand Down
Loading
Loading