Skip to content

feat: Add Gas dimension tracers #457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c1da45b
Gas Dimension tracing initial work.
relyt29 Apr 4, 2025
0af42c1
gas dimension support for EXTCODECOPY. Tested by hand:
relyt29 Apr 4, 2025
74e19e2
Stop tracking previous opcode state - we don't need it
relyt29 Apr 4, 2025
99dc3ab
gas dimensions for SLOAD. Tested by hand with:
relyt29 Apr 4, 2025
f82c8f6
gas emission for LOG0-4, tested by hand:
relyt29 Apr 4, 2025
8da7135
gas dimensions for SELFDESTRUCT opcode, needs testing
relyt29 Apr 4, 2025
5abd69f
gas emission for selfdestruct, tested by hand:
relyt29 Apr 7, 2025
e5c941e
gas emission for staticcall, tested by hand
relyt29 Apr 9, 2025
deed6c8
squash bugs, gas emission for staticcall, tested by hand
relyt29 Apr 9, 2025
547c7d2
kill memExpansion bug where size is zero, DelegatCall tests
relyt29 Apr 9, 2025
b0ef715
gas dimensions for CALL opcode (untested: CALLCODE also)
relyt29 Apr 9, 2025
cdeb045
gas dimensions for CREATE and CREATE2
relyt29 Apr 9, 2025
765818c
gas dimensions for SSTORE tested, and with refunds
relyt29 Apr 10, 2025
2a1ee9e
live tracer leverage native tracer and write txs to folder
relyt29 Apr 10, 2025
fe87615
wrap OnTxStart and OnOpcode for live tracer
relyt29 Apr 10, 2025
1776dc0
upstream updates from offchainlabs/go-ethereum
relyt29 Apr 10, 2025
d5d2ad2
Rename files, add tx gas dimension by opcode tracer
relyt29 Apr 10, 2025
5c2a2f8
Gas dimensions by Block live tracer
relyt29 Apr 10, 2025
3d2a3ab
Add live tracer: gas dimension by opcode for tx
relyt29 Apr 12, 2025
57ca660
save tx data to protobuf serialized format
relyt29 Apr 12, 2025
95d81b0
gas dimensions: handle out of gas errors correctly
relyt29 Apr 16, 2025
a1aeae6
gas dimension protobuf opcodes as uint32 instead of string
relyt29 Apr 16, 2025
668f0b3
error handling on calls with out of gas
relyt29 Apr 16, 2025
9720b9d
gas dimension tracing: refactor shared duplicate code
relyt29 Apr 21, 2025
6ff9569
gas dimensions - small tweaks while debugging
relyt29 Apr 21, 2025
427601c
Directly expose accessList to tracers. Test it works with BALANCE opcode
relyt29 Apr 22, 2025
4bf0635
gas dimension tracers directly calc access list gas for:
relyt29 Apr 23, 2025
aa80bb1
gas dimension logger debug string
relyt29 Apr 30, 2025
76b20db
gas dimension tracing: simplify gas dimensions for SSTORE
relyt29 Apr 30, 2025
2ea6957
gas dimension tracing bugfix: sign error on subtraction of gas refund
relyt29 Apr 30, 2025
f0e39db
gas dimension tracing bugfix: create opcode missing pass-through values
relyt29 May 5, 2025
e09f142
gas dimension tracing: Expose L1GasUsed, CallExecutionGas explicitly
relyt29 May 6, 2025
4bfd741
gas dimension tracing: handle gas refund cap adjustment
relyt29 May 7, 2025
9be9106
gas dimension tracing: linter issues, exhaustruct on gas dimension work
relyt29 May 8, 2025
329f31d
gas dimension tracing: use descriptive names for tracer output
relyt29 May 9, 2025
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
9 changes: 9 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1548,6 +1548,15 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
return s.accessList.Contains(addr, slot)
}

// GetAccessList returns the data of the access list directly for tracers to consume
// this is necessary because the accessList is not exported from the state package
func (s *StateDB) GetAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{}) {
accessListCopy := s.accessList.Copy()
addresses = accessListCopy.addresses
slots = accessListCopy.slots
return
}

// markDelete is invoked when an account is deleted but the deletion is
// not yet committed. The pending mutation is cached and will be applied
// all together
Expand Down
4 changes: 4 additions & 0 deletions core/state/statedb_hooked.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,7 @@ func (s *hookedStateDB) GetSelfDestructs() []common.Address {
func (s *hookedStateDB) GetCurrentTxLogs() []*types.Log {
return s.inner.GetCurrentTxLogs()
}

func (s *hookedStateDB) GetAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{}) {
return s.inner.GetAccessList()
}
1 change: 1 addition & 0 deletions core/tracing/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type StateDB interface {
GetTransientState(common.Address, common.Hash) common.Hash
Exist(common.Address) bool
GetRefund() uint64
GetAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{})
}

// VMContext provides the context for the EVM execution.
Expand Down
2 changes: 2 additions & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ type StateDB interface {
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
AddSlotToAccessList(addr common.Address, slot common.Hash)
// GetAccessList returns the access list
GetAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{})

// PointCache returns the point cache used in computations
PointCache() *utils.PointCache
Expand Down
146 changes: 146 additions & 0 deletions eth/tracers/live/tx_gas_dimension_by_opcode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package live

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/params"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
_vm "github.com/ethereum/go-ethereum/core/vm"
)

// initializer for the tracer
func init() {
tracers.LiveDirectory.Register("txGasDimensionByOpcode", NewTxGasDimensionByOpcodeLogger)
}

type txGasDimensionByOpcodeLiveTraceConfig struct {
Path string `json:"path"` // Path to directory for output
ChainConfig *params.ChainConfig `json:"chainConfig"`
}

// gasDimensionTracer struct
type TxGasDimensionByOpcodeLiveTracer struct {
Path string `json:"path"` // Path to directory for output
ChainConfig *params.ChainConfig
GasDimensionTracer *native.TxGasDimensionByOpcodeTracer
}

// gasDimensionTracer returns a new tracer that traces gas
// usage for each opcode against the dimension of that opcode
// takes a context, and json input for configuration parameters
func NewTxGasDimensionByOpcodeLogger(
cfg json.RawMessage,
) (*tracing.Hooks, error) {
var config txGasDimensionByOpcodeLiveTraceConfig
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}

if config.Path == "" {
return nil, fmt.Errorf("tx gas dimension live tracer path for output is required: %v", config)
}

// if you get stuck here, look at
// cmd/chaininfo/arbitrum_chain_info.json
// for a sample chain config
if config.ChainConfig == nil {
return nil, fmt.Errorf("tx gas dimension live tracer chain config is required: %v", config)
}

t := &TxGasDimensionByOpcodeLiveTracer{
Path: config.Path,
ChainConfig: config.ChainConfig,
GasDimensionTracer: nil,
}

return &tracing.Hooks{
OnOpcode: t.OnOpcode,
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnBlockStart: t.OnBlockStart,
OnBlockEnd: t.OnBlockEnd,
}, nil
}

func (t *TxGasDimensionByOpcodeLiveTracer) OnTxStart(
vm *tracing.VMContext,
tx *types.Transaction,
from common.Address,
) {
if t.GasDimensionTracer != nil {
fmt.Println("Error seen in the gas dimension live tracer lifecycle!")
}

t.GasDimensionTracer = &native.TxGasDimensionByOpcodeTracer{
BaseGasDimensionTracer: native.NewBaseGasDimensionTracer(t.ChainConfig),
OpcodeToDimensions: make(map[_vm.OpCode]native.GasesByDimension),
}
t.GasDimensionTracer.OnTxStart(vm, tx, from)
}

func (t *TxGasDimensionByOpcodeLiveTracer) OnOpcode(
pc uint64,
op byte,
gas, cost uint64,
scope tracing.OpContext,
rData []byte,
depth int,
err error,
) {
t.GasDimensionTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
}

func (t *TxGasDimensionByOpcodeLiveTracer) OnTxEnd(
receipt *types.Receipt,
err error,
) {
// first call the native tracer's OnTxEnd
t.GasDimensionTracer.OnTxEnd(receipt, err)

// system transactions don't use any gas
// they can be skipped
if receipt.GasUsed != 0 {
executionResultBytes, err := t.GasDimensionTracer.GetProtobufResult()
if err != nil {
fmt.Printf("Failed to get protobuf result: %v\n", err)
return
}

blockNumber := receipt.BlockNumber.String()
txHashString := receipt.TxHash.Hex()

// Create the file path
filename := fmt.Sprintf("%s.pb", txHashString)
dirPath := filepath.Join(t.Path, blockNumber)
filepath := filepath.Join(dirPath, filename)

// Ensure the directory exists (including block number subdirectory)
if err := os.MkdirAll(dirPath, 0755); err != nil {
fmt.Printf("Failed to create directory %s: %v\n", dirPath, err)
return
}

// Write the file
if err := os.WriteFile(filepath, executionResultBytes, 0644); err != nil {
fmt.Printf("Failed to write file %s: %v\n", filepath, err)
return
}
}

// reset the tracer
t.GasDimensionTracer = nil
}

func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockStart(ev tracing.BlockEvent) {
}

func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEnd(err error) {
}
117 changes: 117 additions & 0 deletions eth/tracers/live/tx_gas_dimension_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package live

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"

"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/native"
)

type txGasDimensionLiveTraceLogger struct {
Path string `json:"path"`
GasDimensionTracer *tracers.Tracer
}

func init() {
tracers.LiveDirectory.Register("txGasDimensionLogger", newTxGasDimensionLiveTraceLogger)
}

type txGasDimensionLiveTraceLoggerConfig struct {
Path string `json:"path"` // Path to directory for output
}

func newTxGasDimensionLiveTraceLogger(cfg json.RawMessage) (*tracing.Hooks, error) {
var config txGasDimensionLiveTraceLoggerConfig
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err
}

if config.Path == "" {
return nil, fmt.Errorf("gas dimension live tracer path for output is required: %v", config)
}

gasDimensionTracer, err := native.NewTxGasDimensionLogger(nil, nil, nil)
if err != nil {
return nil, err
}

t := &txGasDimensionLiveTraceLogger{
Path: config.Path,
GasDimensionTracer: gasDimensionTracer,
}

return &tracing.Hooks{
OnOpcode: t.OnOpcode,
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnBlockStart: t.OnBlockStart,
OnBlockEnd: t.OnBlockEnd,
}, nil
}

func (t *txGasDimensionLiveTraceLogger) OnTxStart(
vm *tracing.VMContext,
tx *types.Transaction,
from common.Address,
) {
t.GasDimensionTracer.OnTxStart(vm, tx, from)
}

func (t *txGasDimensionLiveTraceLogger) OnOpcode(
pc uint64,
op byte,
gas, cost uint64,
scope tracing.OpContext,
rData []byte,
depth int,
err error,
) {
t.GasDimensionTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
}
func (t *txGasDimensionLiveTraceLogger) OnTxEnd(
receipt *types.Receipt,
err error,
) {
// first call the native tracer's OnTxEnd
t.GasDimensionTracer.OnTxEnd(receipt, err)

// then get the json from the native tracer
executionResultJsonBytes, errGettingResult := t.GasDimensionTracer.GetResult()
if errGettingResult != nil {
errorJsonString := fmt.Sprintf("{\"errorGettingResult\": \"%s\"}", errGettingResult.Error())
fmt.Println(errorJsonString)
return
}

blockNumber := receipt.BlockNumber.String()
txHashString := receipt.TxHash.Hex()

// Create the filename
filename := fmt.Sprintf("%s_%s.json", blockNumber, txHashString)
filepath := filepath.Join(t.Path, filename)

// Ensure the directory exists
if err := os.MkdirAll(t.Path, 0755); err != nil {
fmt.Printf("Failed to create directory %s: %v\n", t.Path, err)
return
}

// Write the file
if err := os.WriteFile(filepath, executionResultJsonBytes, 0644); err != nil {
fmt.Printf("Failed to write file %s: %v\n", filepath, err)
return
}
}

func (t *txGasDimensionLiveTraceLogger) OnBlockStart(ev tracing.BlockEvent) {
}

func (t *txGasDimensionLiveTraceLogger) OnBlockEnd(err error) {
}
Loading
Loading