Skip to content

Commit cf87bd1

Browse files
committed
WiP: Add flag to count opcodes during execution
1 parent c3e20fa commit cf87bd1

File tree

3 files changed

+120
-4
lines changed

3 files changed

+120
-4
lines changed

src/ethereum_spec_tools/evm_tools/t8n/__init__.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import json
88
import os
99
from functools import partial
10-
from typing import Any, TextIO
10+
from typing import Any, Final, TextIO
1111

1212
from ethereum_rlp import rlp
1313
from ethereum_types.numeric import U64, U256, Uint
@@ -25,7 +25,10 @@
2525
parse_hex_or_int,
2626
)
2727
from .env import Env
28-
from .evm_trace.eip3155 import evm_trace
28+
from .evm_trace.count import evm_trace as evm_trace_count
29+
from .evm_trace.count import results as count_results
30+
from .evm_trace.eip3155 import evm_trace as evm_trace_eip3155
31+
from .evm_trace.group import GroupTracer
2932
from .t8n_types import Alloc, Result, Txs
3033

3134

@@ -73,12 +76,16 @@ def t8n_arguments(subparsers: argparse._SubParsersAction) -> None:
7376
t8n_parser.add_argument("--trace.nostack", action="store_true")
7477
t8n_parser.add_argument("--trace.returndata", action="store_true")
7578

79+
t8n_parser.add_argument("--opcode.count", dest="opcode_count", type=str)
80+
7681
t8n_parser.add_argument("--state-test", action="store_true")
7782

7883

7984
class T8N(Load):
8085
"""The class that carries out the transition"""
8186

87+
tracers: Final[GroupTracer | None]
88+
8289
def __init__(
8390
self, options: Any, out_file: TextIO, in_file: TextIO
8491
) -> None:
@@ -101,19 +108,34 @@ def __init__(
101108
)
102109
self.fork = ForkLoad(fork_module)
103110

111+
tracers = GroupTracer()
112+
104113
if self.options.trace:
105114
trace_memory = getattr(self.options, "trace.memory", False)
106115
trace_stack = not getattr(self.options, "trace.nostack", False)
107116
trace_return_data = getattr(self.options, "trace.returndata")
108-
trace.set_evm_trace(
117+
tracers.add(
109118
partial(
110-
evm_trace,
119+
evm_trace_eip3155,
111120
trace_memory=trace_memory,
112121
trace_stack=trace_stack,
113122
trace_return_data=trace_return_data,
114123
output_basedir=self.options.output_basedir,
115124
)
116125
)
126+
127+
if self.options.opcode_count is not None:
128+
tracers.add(evm_trace_count)
129+
130+
maybe_tracers: GroupTracer | None
131+
if tracers.tracers:
132+
trace.set_evm_trace(tracers)
133+
maybe_tracers = tracers
134+
else:
135+
maybe_tracers = None
136+
137+
self.tracers = maybe_tracers
138+
117139
self.logger = get_stream_logger("T8N")
118140

119141
super().__init__(
@@ -349,6 +371,18 @@ def run(self) -> int:
349371
json.dump(json_result, f, indent=4)
350372
self.logger.info(f"Wrote result to {result_output_path}")
351373

374+
opcode_count_results = count_results()
375+
if self.options.opcode_count == "stdout":
376+
json_output["opcodes"] = opcode_count_results
377+
elif self.options.opcode_count is not None:
378+
result_output_path = os.path.join(
379+
self.options.output_basedir,
380+
self.options.opcode_count,
381+
)
382+
with open(result_output_path, "w") as f:
383+
json.dump(opcode_count_results, f, indent=4)
384+
self.logger.info(f"Wrote opcode counts to {result_output_path}")
385+
352386
if json_output:
353387
json.dump(json_output, self.out_file, indent=4)
354388

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
EVM trace implementation that counts how many times each opcode is executed.
3+
"""
4+
from collections import defaultdict
5+
from typing import Any, TypeAlias
6+
7+
from ethereum.trace import OpStart, TraceEvent
8+
9+
from .protocols import Evm
10+
11+
_ActiveTraces: TypeAlias = tuple[object, dict[str, int]]
12+
_active_traces: _ActiveTraces | None = None
13+
14+
15+
def evm_trace(
16+
evm: Any,
17+
event: TraceEvent,
18+
) -> None:
19+
"""
20+
Create a trace of the event.
21+
"""
22+
global _active_traces
23+
24+
if not isinstance(event, OpStart):
25+
return
26+
27+
assert isinstance(evm, Evm)
28+
29+
if _active_traces and _active_traces[0] is evm.message.tx_env:
30+
traces = _active_traces[1]
31+
else:
32+
traces = defaultdict(lambda: 0)
33+
_active_traces = (evm.message.tx_env, traces)
34+
35+
traces[event.op.name] += 1
36+
37+
38+
def results() -> dict[str, int]:
39+
"""
40+
Take and clear the current opcode counts.
41+
"""
42+
global _active_traces
43+
44+
results = _active_traces
45+
_active_traces = None
46+
return {} if results is None else results[1]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
EVM trace implementation that fans out to many concrete trace implementations.
3+
"""
4+
from typing import Final, override
5+
6+
from ethereum.trace import EvmTracer, TraceEvent
7+
8+
9+
class GroupTracer(EvmTracer):
10+
"""
11+
EVM trace implementation that fans out to many concrete trace
12+
implementations.
13+
"""
14+
15+
tracers: Final[set[EvmTracer]]
16+
17+
def __init__(self) -> None:
18+
self.tracers = set()
19+
20+
def add(self, tracer: EvmTracer) -> None:
21+
"""
22+
Insert a new tracer.
23+
"""
24+
self.tracers.add(tracer)
25+
26+
@override
27+
def __call__(
28+
self,
29+
evm: object,
30+
event: TraceEvent,
31+
) -> None:
32+
"""
33+
Record a trace event.
34+
"""
35+
for tracer in self.tracers:
36+
tracer(evm, event)

0 commit comments

Comments
 (0)