diff --git a/eth/tracers/live/tx_gas_dimension.go b/eth/tracers/live/tx_gas_dimension.go new file mode 100644 index 0000000000..1e648ae767 --- /dev/null +++ b/eth/tracers/live/tx_gas_dimension.go @@ -0,0 +1,472 @@ +package live + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + "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/eth/tracers/native/proto" + "github.com/ethereum/go-ethereum/params" + protobuf "google.golang.org/protobuf/proto" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// initializer for the tracer +func init() { + tracers.LiveDirectory.Register("txGasDimensionLive", NewTxGasDimensionLiveTracer) +} + +type txGasDimensionLiveTraceConfig struct { + Path string `json:"path"` // Path to directory for output + ChainConfig *params.ChainConfig `json:"chainConfig"` +} + +const fileWriteQueueMaxSize = 1000 + +// gasDimensionTracer struct +// The tracer uses asynchronous file writing to avoid blocking on I/O operations. +// File writes are queued and processed by worker goroutines, ensuring the tracer +// doesn't block the main execution thread. +type TxGasDimensionLiveTracer struct { + Path string `json:"path"` // Path to directory for output + ChainConfig *params.ChainConfig // chain config, needed for the tracer + skip bool // skip hooking system transactions + nativeGasTracer *native.TxGasDimensionTracer // the native tracer that does all the actual work + blockTimestamp uint64 // the timestamp of the currently processing block + blockBaseFee uint64 // the base fee of the currently processing block + txWriteQueue [fileWriteQueueMaxSize]*proto.TxGasDimensionResult // the queue of transactions to write to file + txWriteQueueSize int // the size of the file write queue + blockInfoWriteQueue [fileWriteQueueMaxSize]*proto.BlockInfo // the queue of block info to write to file + blockInfoWriteQueueSize int // the size of the block info write queue +} + +// an struct to capture information about errors in tracer execution +type TxGasDimensionLiveTraceErrorInfo struct { + TxHash string `json:"txHash"` + BlockNumber string `json:"blockNumber"` + Error string `json:"error"` + TracerError string `json:"tracerError,omitempty"` + Status uint64 `json:"status"` + GasUsed uint64 `json:"gasUsed"` + GasUsedForL1 uint64 `json:"gasUsedForL1"` + GasUsedForL2 uint64 `json:"gasUsedForL2"` + IntrinsicGas uint64 `json:"intrinsicGas"` + Dimensions native.GasesByDimension `json:"dimensions,omitempty"` +} + +// 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 NewTxGasDimensionLiveTracer( + cfg json.RawMessage, +) (*tracing.Hooks, error) { + var config txGasDimensionLiveTraceConfig + 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) + } + // be sure path exists + os.MkdirAll(config.Path, 0755) + + // 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 := &TxGasDimensionLiveTracer{ + Path: config.Path, + ChainConfig: config.ChainConfig, + skip: false, + nativeGasTracer: nil, + } + + return &tracing.Hooks{ + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnBlockStart: t.OnBlockStart, + OnBlockEnd: t.OnBlockEnd, + OnBlockEndMetrics: t.OnBlockEndMetrics, + OnClose: t.Close, + }, nil +} + +func (t *TxGasDimensionLiveTracer) OnTxStart( + vm *tracing.VMContext, + tx *types.Transaction, + from common.Address, +) { + if t.nativeGasTracer != nil { + log.Error("Error seen in the gas dimension live tracer lifecycle!") + } + + // we skip internal / system transactions + if tx.Type() == types.ArbitrumInternalTxType { + t.skip = true + return + } + baseGasDimensionTracer, err := native.NewBaseGasDimensionTracer(nil, t.ChainConfig) + if err != nil { + log.Error("Failed to create base gas dimension tracer", "error", err) + return + } + + t.nativeGasTracer = &native.TxGasDimensionTracer{ + BaseGasDimensionTracer: baseGasDimensionTracer, + Dimensions: native.ZeroGasesByDimension(), + } + t.nativeGasTracer.OnTxStart(vm, tx, from) +} + +func (t *TxGasDimensionLiveTracer) OnFault( + pc uint64, + op byte, + gas, cost uint64, + scope tracing.OpContext, + depth int, + err error, +) { + if t.skip { + return + } + t.nativeGasTracer.OnFault(pc, op, gas, cost, scope, depth, err) +} + +func (t *TxGasDimensionLiveTracer) OnOpcode( + pc uint64, + op byte, + gas, cost uint64, + scope tracing.OpContext, + rData []byte, + depth int, + err error, +) { + if t.skip { + return + } + t.nativeGasTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) +} + +func (t *TxGasDimensionLiveTracer) OnTxEnd( + receipt *types.Receipt, + err error, +) { + // If we skipped this transaction, just reset and return + if t.skip { + t.nativeGasTracer = nil + t.skip = false + return + } + + // first call the native tracer's OnTxEnd + t.nativeGasTracer.OnTxEnd(receipt, err) + + tracerErr := t.nativeGasTracer.Reason() + + if tracerErr != nil || err != nil || receipt == nil { + writeTxErrorToFile(t, receipt, err, tracerErr) + } else { // tx did not have any errors + writeTxSuccessToFile(t, receipt) + } + + // reset the tracer + t.nativeGasTracer = nil + t.skip = false +} + +func (t *TxGasDimensionLiveTracer) OnBlockStart(ev tracing.BlockEvent) { + t.blockTimestamp = 0 + t.blockBaseFee = 0 + if ev.Block != nil { + t.blockTimestamp = ev.Block.Time() + t.blockBaseFee = ev.Block.BaseFee().Uint64() + } +} + +func (t *TxGasDimensionLiveTracer) OnBlockEnd(err error) { +} + +func (t *TxGasDimensionLiveTracer) OnBlockEndMetrics(blockNumber uint64, blockInsertDuration time.Duration) { + // Calculate block group number (every 1000 blocks) + blockGroup := (blockNumber / 1000) * 1000 + + // Create BlockInfo protobuf message + blockInfo := &proto.BlockInfo{ + Timestamp: t.blockTimestamp, + InsertDurationNanoseconds: uint64(blockInsertDuration.Nanoseconds()), + BaseFee: t.blockBaseFee, + BlockNumber: blockNumber, + } + + // Add to block info write queue + t.blockInfoWriteQueue[t.blockInfoWriteQueueSize] = blockInfo + t.blockInfoWriteQueueSize++ + + // If queue is full, write the batch + if t.blockInfoWriteQueueSize >= fileWriteQueueMaxSize { + t.writeBlockBatchToFile(fmt.Sprintf("%d", blockGroup)) + } +} + +// writeBlockBatchToFile converts the blockInfoWriteQueue to a BlockInfoBatch, +// marshals it to bytes, and writes it to a file +func (t *TxGasDimensionLiveTracer) writeBlockBatchToFile(blockGroup string) { + if t.blockInfoWriteQueueSize == 0 { + return + } + + // Create the batch from the queue + batch := &proto.BlockInfoBatch{ + Blocks: make([]*proto.BlockInfo, t.blockInfoWriteQueueSize), + } + + // Copy block infos from queue to batch + var i int = 0 + for i < t.blockInfoWriteQueueSize { + batch.Blocks[i] = t.blockInfoWriteQueue[i] + i++ + } + + // Marshal the batch to bytes + batchBytes, err := protobuf.Marshal(batch) + if err != nil { + log.Error("Failed to marshal block batch", "error", err) + return + } + + // Create directory path + dirPath := filepath.Join(t.Path, "blocks", blockGroup) + if err := os.MkdirAll(dirPath, 0755); err != nil { + log.Error("Failed to create directory", "path", dirPath, "error", err) + return + } + + // Create filename with timestamp to avoid conflicts + timestamp := time.Now().UnixNano() + filename := fmt.Sprintf("block_batch_%d.pb", timestamp) + filepath := filepath.Join(dirPath, filename) + + // Write the batch file + if err := os.WriteFile(filepath, batchBytes, 0644); err != nil { + log.Error("Failed to write block batch file", "path", filepath, "error", err) + return + } + + // Reset the queue size to zero so next writes overwrite previous data + t.blockInfoWriteQueueSize = 0 +} + +// Close implements the Close method for the tracer hooks +func (t *TxGasDimensionLiveTracer) Close() { + // Flush any remaining items in the queue before closing + if t.txWriteQueueSize > 0 { + t.flushQueue() + } + if t.blockInfoWriteQueueSize > 0 { + t.flushBlockQueue() + } +} + +// writeTxBatchToFile converts the fileWriteQueue to a TxGasDimensionResultBatch, +// marshals it to bytes, and writes it to a file +func (t *TxGasDimensionLiveTracer) writeTxBatchToFile(blockGroup string) { + if t.txWriteQueueSize == 0 { + return + } + + // Create the batch from the queue + batch := &proto.TxGasDimensionResultBatch{ + Results: make([]*proto.TxGasDimensionResult, t.txWriteQueueSize), + } + + // Copy results from queue to batch + var i int = 0 + for i < t.txWriteQueueSize { + batch.Results[i] = t.txWriteQueue[i] + i++ + } + + // Marshal the batch to bytes + batchBytes, err := protobuf.Marshal(batch) + if err != nil { + log.Error("Failed to marshal batch", "error", err) + return + } + + // Create directory path + dirPath := filepath.Join(t.Path, blockGroup) + if err := os.MkdirAll(dirPath, 0755); err != nil { + log.Error("Failed to create directory", "path", dirPath, "error", err) + return + } + + // Create filename with timestamp to avoid conflicts + timestamp := time.Now().UnixNano() + filename := fmt.Sprintf("batch_%d.pb", timestamp) + filepath := filepath.Join(dirPath, filename) + + // Write the batch file + if err := os.WriteFile(filepath, batchBytes, 0644); err != nil { + log.Error("Failed to write batch file", "path", filepath, "error", err) + return + } + + // Reset the queue size to zero so next writes overwrite previous data + t.txWriteQueueSize = 0 +} + +// flushQueue is a helper method to flush the current queue contents +func (t *TxGasDimensionLiveTracer) flushQueue() { + if t.txWriteQueueSize > 0 { + // Use a default block group for flushing + t.writeTxBatchToFile("flush") + } +} + +// flushBlockQueue is a helper method to flush the current block queue contents +func (t *TxGasDimensionLiveTracer) flushBlockQueue() { + if t.blockInfoWriteQueueSize > 0 { + // Use a default block group for flushing + t.writeBlockBatchToFile("flush") + } +} + +// if the transaction has any kind of error, try to get as much information +// as you can out of it, and then write that out to a file underneath +// Path/errors/block_group/blocknumber_txhash.json +func writeTxErrorToFile(t *TxGasDimensionLiveTracer, receipt *types.Receipt, err error, tracerError error) { + var txHashStr string = "no-tx-hash" + var blockNumberStr string = "no-block-number" + var errorInfo TxGasDimensionLiveTraceErrorInfo + + var errStr string = "" + var tracerErrStr string = "" + if err != nil { + errStr = err.Error() + } + if tracerError != nil { + tracerErrStr = tracerError.Error() + } + if receipt == nil { + // we need something to use as a name for the error file, + // and we have no tx to hash + txHashStr += time.Now().String() + outErrString := fmt.Sprintf("receipt is nil, err: %s", errStr) + errorInfo = TxGasDimensionLiveTraceErrorInfo{ + Error: outErrString, + TracerError: tracerErrStr, + Dimensions: t.nativeGasTracer.Dimensions, + } + } else { + // if we errored in the tracer because we had an unexpected gas cost mismatch + blockNumberStr = receipt.BlockNumber.String() + txHashStr = receipt.TxHash.Hex() + + var intrinsicGas uint64 = 0 + baseExecutionResult, err := t.nativeGasTracer.GetBaseExecutionResult() + if err == nil { + intrinsicGas = baseExecutionResult.IntrinsicGas + } + + errorInfo = TxGasDimensionLiveTraceErrorInfo{ + TxHash: txHashStr, + BlockNumber: blockNumberStr, + Error: errStr, + TracerError: tracerErrStr, + Status: receipt.Status, + GasUsed: receipt.GasUsed, + GasUsedForL1: receipt.GasUsedForL1, + GasUsedForL2: receipt.GasUsedForL2(), + IntrinsicGas: intrinsicGas, + Dimensions: t.nativeGasTracer.Dimensions, + } + } + + // Create error directory path grouped by 1000 blocks + var errorDirPath string + if receipt == nil { + // For nil receipts, use a special directory + errorDirPath = filepath.Join(t.Path, "errors", "nil-receipts") + } else { + // Group by 1000 blocks like successful transactions + blockNumber := receipt.BlockNumber.Uint64() + blockGroup := (blockNumber / 1000) * 1000 + errorDirPath = filepath.Join(t.Path, "errors", fmt.Sprintf("%d", blockGroup)) + } + + if err := os.MkdirAll(errorDirPath, 0755); err != nil { + log.Error("Failed to create error directory", "path", errorDirPath, "error", err) + return + } + // Marshal error info to JSON + errorData, err := json.MarshalIndent(errorInfo, "", " ") + if err != nil { + log.Error("Failed to marshal error info", "error", err) + return + } + + // Write error file + errorFilepath := filepath.Join(errorDirPath, fmt.Sprintf("%s_%s.json", blockNumberStr, txHashStr)) + if err := os.WriteFile(errorFilepath, errorData, 0644); err != nil { + log.Error("Failed to write error file", "path", errorFilepath, "error", err) + return + } +} + +// if the transaction is a non-erroring transaction, write it out to +// the path specified when the tracer was created, under a folder organized by +// every 1000 blocks (this avoids making a huge number of directories, +// which makes analysis iteration over the entire dataset faster) +// the individual filenames are where the filename is blocknumber_txhash.pb +// so you have Path/block_group/blocknumber_txhash.pb +// e.g. Path/1000/1890_0x123abc.pb +func writeTxSuccessToFile(t *TxGasDimensionLiveTracer, receipt *types.Receipt) { + // system transactions don't use any gas + // they can be skipped + if receipt.GasUsed != 0 { + blockNumber := receipt.BlockNumber + executionResult, err := t.nativeGasTracer.GetProtobufResult() + if err != nil { + log.Error("Failed to get protobuf result", "error", err) + return + } + + blockGroup := (blockNumber.Uint64() / 1000) * 1000 + /* + dirPath := filepath.Join(t.Path, fmt.Sprintf("%d", blockGroup)) + filename := fmt.Sprintf("%s_%s.pb", blockNumber.String(), txHashString) + filepath := filepath.Join(dirPath, filename) + + // Ensure the directory exists (including block number subdirectory) + if err := os.MkdirAll(dirPath, 0755); err != nil { + log.Error("Failed to create directory", "path", dirPath, "error", err) + return + } + + // Write the file + if err := os.WriteFile(filepath, executionResultBytes, 0644); err != nil { + log.Error("Failed to write file", "path", filepath, "error", err) + return + } + */ + t.txWriteQueue[t.txWriteQueueSize] = executionResult + t.txWriteQueueSize++ + if t.txWriteQueueSize >= fileWriteQueueMaxSize { + t.writeTxBatchToFile(fmt.Sprintf("%d", blockGroup)) + } + } +} diff --git a/eth/tracers/live/tx_gas_dimension_by_opcode.go b/eth/tracers/live/tx_gas_dimension_by_opcode.go deleted file mode 100644 index ac2a41a9e9..0000000000 --- a/eth/tracers/live/tx_gas_dimension_by_opcode.go +++ /dev/null @@ -1,326 +0,0 @@ -package live - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "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" - "github.com/ethereum/go-ethereum/log" -) - -// 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 // chain config, needed for the tracer - skip bool // skip hooking system transactions - nativeGasByOpcodeTracer *native.TxGasDimensionByOpcodeTracer // the native tracer that does all the actual work -} - -// an struct to capture information about errors in tracer execution -type TxGasDimensionByOpcodeLiveTraceErrorInfo struct { - TxHash string `json:"txHash"` - BlockNumber string `json:"blockNumber"` - Error string `json:"error"` - TracerError string `json:"tracerError,omitempty"` - Status uint64 `json:"status"` - GasUsed uint64 `json:"gasUsed"` - GasUsedForL1 uint64 `json:"gasUsedForL1"` - GasUsedForL2 uint64 `json:"gasUsedForL2"` - IntrinsicGas uint64 `json:"intrinsicGas"` - Dimensions interface{} `json:"dimensions,omitempty"` -} - -// 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) - } - // be sure path exists - os.MkdirAll(config.Path, 0755) - - // 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, - skip: false, - nativeGasByOpcodeTracer: nil, - } - - return &tracing.Hooks{ - OnOpcode: t.OnOpcode, - OnFault: t.OnFault, - OnTxStart: t.OnTxStart, - OnTxEnd: t.OnTxEnd, - OnBlockStart: t.OnBlockStart, - OnBlockEnd: t.OnBlockEnd, - OnBlockEndMetrics: t.OnBlockEndMetrics, - }, nil -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnTxStart( - vm *tracing.VMContext, - tx *types.Transaction, - from common.Address, -) { - if t.nativeGasByOpcodeTracer != nil { - log.Error("Error seen in the gas dimension live tracer lifecycle!") - } - - // we skip internal / system transactions - if tx.Type() == types.ArbitrumInternalTxType { - t.skip = true - return - } - baseGasDimensionTracer, err := native.NewBaseGasDimensionTracer(nil, t.ChainConfig) - if err != nil { - log.Error("Failed to create base gas dimension tracer", "error", err) - return - } - - t.nativeGasByOpcodeTracer = &native.TxGasDimensionByOpcodeTracer{ - BaseGasDimensionTracer: baseGasDimensionTracer, - OpcodeToDimensions: make(map[_vm.OpCode]native.GasesByDimension), - } - t.nativeGasByOpcodeTracer.OnTxStart(vm, tx, from) -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnFault( - pc uint64, - op byte, - gas, cost uint64, - scope tracing.OpContext, - depth int, - err error, -) { - if t.skip { - return - } - t.nativeGasByOpcodeTracer.OnFault(pc, op, gas, cost, scope, depth, err) -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnOpcode( - pc uint64, - op byte, - gas, cost uint64, - scope tracing.OpContext, - rData []byte, - depth int, - err error, -) { - if t.skip { - return - } - t.nativeGasByOpcodeTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnTxEnd( - receipt *types.Receipt, - err error, -) { - // If we skipped this transaction, just reset and return - if t.skip { - t.nativeGasByOpcodeTracer = nil - t.skip = false - return - } - - // first call the native tracer's OnTxEnd - t.nativeGasByOpcodeTracer.OnTxEnd(receipt, err) - - tracerErr := t.nativeGasByOpcodeTracer.Reason() - - if tracerErr != nil || err != nil || receipt == nil { - writeTxErrorToFile(t, receipt, err, tracerErr) - } else { // tx did not have any errors - writeTxSuccessToFile(t, receipt) - } - - // reset the tracer - t.nativeGasByOpcodeTracer = nil - t.skip = false -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockStart(ev tracing.BlockEvent) { -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEnd(err error) { -} - -func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEndMetrics(blockNumber uint64, blockInsertDuration time.Duration) { - filename := fmt.Sprintf("%d.txt", blockNumber) - dirPath := filepath.Join(t.Path, "blocks") - filepath := filepath.Join(dirPath, filename) - - // Ensure the directory exists - if err := os.MkdirAll(dirPath, 0755); err != nil { - log.Error("Failed to create directory", "path", dirPath, "error", err) - return - } - - // the output is the duration in nanoseconds - var outData int64 = blockInsertDuration.Nanoseconds() - - // Write the file - if err := os.WriteFile(filepath, fmt.Appendf(nil, "%d", outData), 0644); err != nil { - log.Error("Failed to write file", "path", filepath, "error", err) - return - } -} - -// if the transaction has any kind of error, try to get as much information -// as you can out of it, and then write that out to a file underneath -// Path/errors/block_group/blocknumber_txhash.json -func writeTxErrorToFile(t *TxGasDimensionByOpcodeLiveTracer, receipt *types.Receipt, err error, tracerError error) { - var txHashStr string = "no-tx-hash" - var blockNumberStr string = "no-block-number" - var errorInfo TxGasDimensionByOpcodeLiveTraceErrorInfo - - var errStr string = "" - var tracerErrStr string = "" - if err != nil { - errStr = err.Error() - } - if tracerError != nil { - tracerErrStr = tracerError.Error() - } - // dimensions should not error, just return empty if there is no data - dimensions := t.nativeGasByOpcodeTracer.GetOpcodeDimensionSummary() - - if receipt == nil { - // we need something to use as a name for the error file, - // and we have no tx to hash - txHashStr += time.Now().String() - outErrString := fmt.Sprintf("receipt is nil, err: %s", errStr) - errorInfo = TxGasDimensionByOpcodeLiveTraceErrorInfo{ - Error: outErrString, - TracerError: tracerErrStr, - Dimensions: dimensions, - } - } else { - // if we errored in the tracer because we had an unexpected gas cost mismatch - blockNumberStr = receipt.BlockNumber.String() - txHashStr = receipt.TxHash.Hex() - - var intrinsicGas uint64 = 0 - baseExecutionResult, err := t.nativeGasByOpcodeTracer.GetBaseExecutionResult() - if err == nil { - intrinsicGas = baseExecutionResult.IntrinsicGas - } - - errorInfo = TxGasDimensionByOpcodeLiveTraceErrorInfo{ - TxHash: txHashStr, - BlockNumber: blockNumberStr, - Error: errStr, - TracerError: tracerErrStr, - Status: receipt.Status, - GasUsed: receipt.GasUsed, - GasUsedForL1: receipt.GasUsedForL1, - GasUsedForL2: receipt.GasUsedForL2(), - IntrinsicGas: intrinsicGas, - Dimensions: dimensions, - } - } - - // Create error directory path grouped by 1000 blocks - var errorDirPath string - if receipt == nil { - // For nil receipts, use a special directory - errorDirPath = filepath.Join(t.Path, "errors", "nil-receipts") - } else { - // Group by 1000 blocks like successful transactions - blockNumber := receipt.BlockNumber.Uint64() - blockGroup := (blockNumber / 1000) * 1000 - errorDirPath = filepath.Join(t.Path, "errors", fmt.Sprintf("%d", blockGroup)) - } - - if err := os.MkdirAll(errorDirPath, 0755); err != nil { - log.Error("Failed to create error directory", "path", errorDirPath, "error", err) - return - } - // Marshal error info to JSON - errorData, err := json.MarshalIndent(errorInfo, "", " ") - if err != nil { - log.Error("Failed to marshal error info", "error", err) - return - } - - // Write error file - errorFilepath := filepath.Join(errorDirPath, fmt.Sprintf("%s_%s.json", blockNumberStr, txHashStr)) - if err := os.WriteFile(errorFilepath, errorData, 0644); err != nil { - log.Error("Failed to write error file", "path", errorFilepath, "error", err) - return - } -} - -// if the transaction is a non-erroring transaction, write it out to -// the path specified when the tracer was created, under a folder organized by -// every 1000 blocks (this avoids making a huge number of directories, -// which makes analysis iteration over the entire dataset faster) -// the individual filenames are where the filename is blocknumber_txhash.pb -// so you have Path/block_group/blocknumber_txhash.pb -// e.g. Path/1000/1890_0x123abc.pb -func writeTxSuccessToFile(t *TxGasDimensionByOpcodeLiveTracer, receipt *types.Receipt) { - // system transactions don't use any gas - // they can be skipped - if receipt.GasUsed != 0 { - txHashString := receipt.TxHash.Hex() - blockNumber := receipt.BlockNumber - executionResultBytes, err := t.nativeGasByOpcodeTracer.GetProtobufResult() - if err != nil { - log.Error("Failed to get protobuf result", "error", err) - return - } - - blockGroup := (blockNumber.Uint64() / 1000) * 1000 - dirPath := filepath.Join(t.Path, fmt.Sprintf("%d", blockGroup)) - filename := fmt.Sprintf("%s_%s.pb", blockNumber.String(), txHashString) - filepath := filepath.Join(dirPath, filename) - - // Ensure the directory exists (including block number subdirectory) - if err := os.MkdirAll(dirPath, 0755); err != nil { - log.Error("Failed to create directory", "path", dirPath, "error", err) - return - } - - // Write the file - if err := os.WriteFile(filepath, executionResultBytes, 0644); err != nil { - log.Error("Failed to write file", "path", filepath, "error", err) - return - } - } -} diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 0d51f40522..a58c60ed3f 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -46,6 +46,12 @@ func (al accessList) addAddress(address common.Address) { } } +// hasAddress checks if an address is in the accesslist. +func (al accessList) hasAddress(address common.Address) bool { + _, ok := al[address] + return ok +} + // addSlot adds a storage slot to the accesslist. func (al accessList) addSlot(address common.Address, slot common.Hash) { // Set address if not previously present @@ -55,6 +61,12 @@ func (al accessList) addSlot(address common.Address, slot common.Hash) { al[address][slot] = struct{}{} } +// hasSlot checks if a storage slot is in the accesslist. +func (al accessList) hasSlot(address common.Address, slot common.Hash) bool { + _, ok := al[address][slot] + return ok +} + // equal checks if the content of the current access list is the same as the // content of the other one. func (al accessList) equal(other accessList) bool { @@ -146,6 +158,28 @@ func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, sc a.list.addAddress(addr) } } + /* + if op == vm.CREATE { + nonce := a.env.StateDB.GetNonce(caller) + addr := crypto.CreateAddress(caller, nonce) + a.lookupAccount(addr) + a.created[addr] = true + } + if stackLen >= 4 && op == vm.CREATE2 { + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64())) + if err != nil { + log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) + return + } + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) + a.lookupAccount(addr) + a.created[addr] = true + } + */ } // AccessList returns the current accesslist maintained by the tracer. @@ -157,3 +191,13 @@ func (a *AccessListTracer) AccessList() types.AccessList { func (a *AccessListTracer) Equal(other *AccessListTracer) bool { return a.list.equal(other.list) } + +// HasAddress checks if an address is in the accesslist. +func (a *AccessListTracer) HasAddress(address common.Address) bool { + return a.list.hasAddress(address) +} + +// HasSlot checks if a storage slot is in the accesslist. +func (a *AccessListTracer) HasSlot(address common.Address, slot common.Hash) bool { + return a.list.hasSlot(address, slot) +} diff --git a/eth/tracers/native/base_gas_dimension_tracer.go b/eth/tracers/native/base_gas_dimension_tracer.go index 96ad0841cf..1a53dafdfc 100644 --- a/eth/tracers/native/base_gas_dimension_tracer.go +++ b/eth/tracers/native/base_gas_dimension_tracer.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/params" ) @@ -46,8 +47,7 @@ type BaseGasDimensionTracer struct { // if the root is a stylus contract rootIsStylusAdjustment uint64 // maintain an access list tracer to check previous access list statuses. - prevAccessListAddresses map[common.Address]int - prevAccessListSlots []map[common.Hash]struct{} + accessListTracer *logger.AccessListTracer // the amount of refund accumulated at the current step of execution refundAccumulated uint64 // in order to calculate the refund adjusted, we need to know the total execution gas @@ -90,8 +90,7 @@ func NewBaseGasDimensionTracer(cfg json.RawMessage, chainConfig *params.ChainCon chainConfig: chainConfig, depth: 1, refundAccumulated: 0, - prevAccessListAddresses: map[common.Address]int{}, - prevAccessListSlots: []map[common.Hash]struct{}{}, + accessListTracer: nil, env: nil, txHash: common.Hash{}, gasUsed: 0, @@ -150,7 +149,7 @@ func (t *BaseGasDimensionTracer) onOpcodeStart( t.opCount++ // First check if tracer has been interrupted if t.interrupt.Load() { - return true, zeroGasesByDimension(), nil, vm.OpCode(op) + return true, ZeroGasesByDimension(), nil, vm.OpCode(op) } // Depth validation - if depth doesn't match expectations, this is a tracer error @@ -163,7 +162,7 @@ func (t *BaseGasDimensionTracer) onOpcodeStart( depth, t.callStack, ) - return true, zeroGasesByDimension(), nil, vm.OpCode(op) + return true, ZeroGasesByDimension(), nil, vm.OpCode(op) } if t.depth != len(t.callStack)+1 { t.interrupt.Store(true) @@ -174,7 +173,7 @@ func (t *BaseGasDimensionTracer) onOpcodeStart( len(t.callStack), t.callStack, ) - return true, zeroGasesByDimension(), nil, vm.OpCode(op) + return true, ZeroGasesByDimension(), nil, vm.OpCode(op) } // Get the gas dimension function @@ -187,7 +186,7 @@ func (t *BaseGasDimensionTracer) onOpcodeStart( if fErr != nil { t.interrupt.Store(true) t.reason = fErr - return true, zeroGasesByDimension(), nil, vm.OpCode(op) + return true, ZeroGasesByDimension(), nil, vm.OpCode(op) } opcode = vm.OpCode(op) @@ -200,7 +199,7 @@ func (t *BaseGasDimensionTracer) onOpcodeStart( opcode.String(), callStackInfo, ) - return true, zeroGasesByDimension(), nil, vm.OpCode(op) + return true, ZeroGasesByDimension(), nil, vm.OpCode(op) } return false, gasesByDimension, callStackInfo, opcode } @@ -240,7 +239,7 @@ func (t *BaseGasDimensionTracer) callFinishFunction( if !ok { t.interrupt.Store(true) t.reason = fmt.Errorf("call stack is unexpectedly empty %d %d %d", pc, depth, t.depth) - return true, 0, zeroCallGasDimensionStackInfo(), zeroGasesByDimension() + return true, 0, zeroCallGasDimensionStackInfo(), ZeroGasesByDimension() } finishFunction := GetFinishCalcGasDimensionFunc(stackInfo.GasDimensionInfo.Op) if finishFunction == nil { @@ -250,7 +249,7 @@ func (t *BaseGasDimensionTracer) callFinishFunction( stackInfo.GasDimensionInfo.Op.String(), pc, ) - return true, 0, zeroCallGasDimensionStackInfo(), zeroGasesByDimension() + return true, 0, zeroCallGasDimensionStackInfo(), ZeroGasesByDimension() } // IMPORTANT NOTE: for some reason the only reliable way to actually get the gas cost of the call // is to subtract gas at time of call from gas at opcode AFTER return @@ -262,7 +261,7 @@ func (t *BaseGasDimensionTracer) callFinishFunction( if finishErr != nil { t.interrupt.Store(true) t.reason = finishErr - return true, 0, zeroCallGasDimensionStackInfo(), zeroGasesByDimension() + return true, 0, zeroCallGasDimensionStackInfo(), ZeroGasesByDimension() } return false, totalGasUsedByCall, stackInfo, finishGasesByDimension } @@ -277,18 +276,6 @@ func (t *BaseGasDimensionTracer) updateCallChildExecutionCost(cost uint64) { } } -// at the end of each OnOpcode, update the previous access list, so that we can -// use it should the next opcode need to check if an address is in the access list -func (t *BaseGasDimensionTracer) updatePrevAccessList(addresses map[common.Address]int, slots []map[common.Hash]struct{}) { - if addresses == nil { - t.prevAccessListAddresses = map[common.Address]int{} - t.prevAccessListSlots = []map[common.Hash]struct{}{} - return - } - t.prevAccessListAddresses = addresses - t.prevAccessListSlots = slots -} - // OnTxStart handles transaction start func (t *BaseGasDimensionTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { t.env = env @@ -313,6 +300,30 @@ func (t *BaseGasDimensionTracer) OnTxStart(env *tracing.VMContext, tx *types.Tra t.rootIsPrecompile = slices.Contains(precompileAddressList, *tx.To()) t.rootIsStylus = isStylusContract(t, *tx.To()) } + + txAcl := tx.AccessList() + aclLen := len(txAcl) + len(precompileAddressList) + 3 + if t.chainConfig.IsShanghai(env.BlockNumber, env.Time, env.ArbOSVersion) { + aclLen += 1 + } + // add any access list provided by the user when they submitted the transaction + acl := make(types.AccessList, 0, aclLen) + acl = append(acl, txAcl...) + // add all precompiles to the access list + for _, addr := range precompileAddressList { + acl = append(acl, types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}) + } + // add the sender to the access list + acl = append(acl, types.AccessTuple{Address: from, StorageKeys: []common.Hash{}}) + // add the receiver to the access list + if tx.To() != nil { + acl = append(acl, types.AccessTuple{Address: *tx.To(), StorageKeys: []common.Hash{}}) + } + // add the batch poster address to the access list + // to avoid circular import issues we hardcode the batch poster address here + batchPosterAddress := common.HexToAddress("0xA4B000000000000000000073657175656e636572") + acl = append(acl, types.AccessTuple{Address: batchPosterAddress, StorageKeys: []common.Hash{}}) + t.accessListTracer = logger.NewAccessListTracer(acl, nil) } // OnTxEnd handles transaction end @@ -396,9 +407,9 @@ func (t *BaseGasDimensionTracer) GetRootIsStylus() bool { return t.rootIsStylus } -// GetPrevAccessList returns the previous access list -func (t *BaseGasDimensionTracer) GetPrevAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{}) { - return t.prevAccessListAddresses, t.prevAccessListSlots +// GetAccessListTracer returns the access list tracer +func (t *BaseGasDimensionTracer) GetAccessListTracer() *logger.AccessListTracer { + return t.accessListTracer } // GetPrecompileAddressList returns the list of precompile addresses @@ -437,8 +448,8 @@ func (t *BaseGasDimensionTracer) adjustRefund(gasUsedByL2BeforeRefunds, refund u return refundAdjusted } -// zeroGasesByDimension returns a GasesByDimension struct with all fields set to zero -func zeroGasesByDimension() GasesByDimension { +// ZeroGasesByDimension returns a GasesByDimension struct with all fields set to zero +func ZeroGasesByDimension() GasesByDimension { return GasesByDimension{ OneDimensionalGasCost: 0, Computation: 0, @@ -455,6 +466,7 @@ func zeroCallGasDimensionInfo() CallGasDimensionInfo { return CallGasDimensionInfo{ Pc: 0, Op: 0, + DepthAtTimeOfCall: 0, GasCounterAtTimeOfCall: 0, MemoryExpansionCost: 0, AccessListComputationCost: 0, diff --git a/eth/tracers/native/gas_dimension_calc.go b/eth/tracers/native/gas_dimension_calc.go index dd5f67cf5a..2dc4cc2782 100644 --- a/eth/tracers/native/gas_dimension_calc.go +++ b/eth/tracers/native/gas_dimension_calc.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/params" ) @@ -31,6 +32,7 @@ type GasesByDimension struct { type CallGasDimensionInfo struct { Pc uint64 Op vm.OpCode + DepthAtTimeOfCall int GasCounterAtTimeOfCall uint64 MemoryExpansionCost uint64 AccessListComputationCost uint64 @@ -99,7 +101,7 @@ type DimensionTracer interface { GetOpRefund() uint64 GetRefundAccumulated() uint64 SetRefundAccumulated(uint64) - GetPrevAccessList() (addresses map[common.Address]int, slots []map[common.Hash]struct{}) + GetAccessListTracer() *logger.AccessListTracer GetPrecompileAddressList() []common.Address GetStateDB() tracing.StateDB GetCallStack() CallGasDimensionStack @@ -260,7 +262,10 @@ func calcSimpleAddressAccessSetGas( ChildExecutionCost: 0, } if err := checkGasDimensionsEqualOneDimensionalGas(pc, op, depth, gas, cost, ret); err != nil { - return GasesByDimension{}, nil, err + accessList := t.GetAccessListTracer().AccessList() + return GasesByDimension{}, nil, fmt.Errorf( + "error in calcSimpleAddressAccessSetGas: %v, address: %v, access list: %v", + err, address, accessList) } return ret, nil, nil } @@ -444,6 +449,7 @@ func calcStaticDelegateCallGas( }, &CallGasDimensionInfo{ Pc: pc, Op: vm.OpCode(op), + DepthAtTimeOfCall: depth, GasCounterAtTimeOfCall: gas, MemoryExpansionCost: memExpansionCost, AccessListComputationCost: accessListComputationCost, @@ -483,6 +489,7 @@ func finishCalcStaticDelegateCallGas( err := checkOneDimGreaterThanMultiDimGas( callGasDimensionInfo.Pc, byte(callGasDimensionInfo.Op), + callGasDimensionInfo.DepthAtTimeOfCall, codeExecutionCost, ret, ) @@ -515,6 +522,7 @@ func finishCalcStaticDelegateCallGas( err := checkGasDimensionsEqualCallGas( callGasDimensionInfo.Pc, byte(callGasDimensionInfo.Op), + callGasDimensionInfo.DepthAtTimeOfCall, codeExecutionCost, ret, ) @@ -634,6 +642,7 @@ func calcCreateGas( }, &CallGasDimensionInfo{ Pc: pc, Op: vm.OpCode(op), + DepthAtTimeOfCall: depth, GasCounterAtTimeOfCall: gas, MemoryExpansionCost: memExpansionCost, AccessListComputationCost: 0, @@ -676,11 +685,12 @@ func finishCalcCreateGas( } // it should be impossible to call a precompile from the deployment init code if callGasDimensionInfo.isTargetPrecompile { - return ret, fmt.Errorf("precompile called from deployment init code") + return ret, fmt.Errorf("precompile called from deployment init code, pc: %d, op: %s, depth: %d", callGasDimensionInfo.Pc, callGasDimensionInfo.Op.String(), callGasDimensionInfo.DepthAtTimeOfCall) } err := checkGasDimensionsEqualCallGas( callGasDimensionInfo.Pc, byte(callGasDimensionInfo.Op), + callGasDimensionInfo.DepthAtTimeOfCall, codeExecutionCost, ret, ) @@ -745,6 +755,7 @@ func calcCallGas( }, &CallGasDimensionInfo{ Pc: pc, Op: vm.OpCode(op), + DepthAtTimeOfCall: depth, GasCounterAtTimeOfCall: gas, AccessListComputationCost: 0, AccessListStateAccessCost: 0, @@ -792,6 +803,7 @@ func calcCallGas( }, &CallGasDimensionInfo{ Pc: pc, Op: vm.OpCode(op), + DepthAtTimeOfCall: depth, GasCounterAtTimeOfCall: gas, AccessListComputationCost: accessListComputationCost, AccessListStateAccessCost: accessListStateAccessCost, @@ -818,7 +830,7 @@ func finishCalcCallGas( callGasDimensionInfo CallGasDimensionInfo, ) (ret GasesByDimension, err error) { if codeExecutionCost > totalGasUsed { - return GasesByDimension{}, fmt.Errorf("CALL's totalGasUsed should never be greater than the codeExecutionCost, totalGasUsed: %d, codeExecutionCost: %d", totalGasUsed, codeExecutionCost) + return GasesByDimension{}, fmt.Errorf("CALL's totalGasUsed should never be greater than the codeExecutionCost, totalGasUsed: %d, codeExecutionCost: %d, pc: %d, op: %s, depth: %d", totalGasUsed, codeExecutionCost, callGasDimensionInfo.Pc, callGasDimensionInfo.Op.String(), callGasDimensionInfo.DepthAtTimeOfCall) } if totalGasUsed == 0 { return ret, nil @@ -901,12 +913,16 @@ func finishCalcCallGas( } } else { return GasesByDimension{}, fmt.Errorf( - "invalid addressAccessAndValueEmptyCosts: %d,"+ + "pc %d, op %s, depth: %d,"+ + " invalid addressAccessAndValueEmptyCosts: %d,"+ " oneDimensionalGas: %d,"+ " positiveValueCostLessStipend: %d,"+ " codeExecutionCost: %d,"+ " memoryExpansionCost: %d"+ " IsValueSentWithCall: %t", + callGasDimensionInfo.Pc, + callGasDimensionInfo.Op.String(), + callGasDimensionInfo.DepthAtTimeOfCall, addressAccessAndValueEmptyCosts, oneDimensionalGas, positiveValueCostLessStipend, @@ -919,6 +935,7 @@ func finishCalcCallGas( err = checkGasDimensionsEqualCallGas( callGasDimensionInfo.Pc, byte(callGasDimensionInfo.Op), + callGasDimensionInfo.DepthAtTimeOfCall, codeExecutionCost, ret, ) @@ -980,7 +997,7 @@ func calcSStoreGas( } t.SetRefundAccumulated(currentRefund) } - ret := zeroGasesByDimension() + ret := ZeroGasesByDimension() ret.OneDimensionalGasCost = cost if cost >= params.SstoreSetGas { // 22100 case and 20000 case accessCost := cost - params.SstoreSetGas @@ -1196,9 +1213,8 @@ func memoryGasCost(mem []byte, lastGasCost uint64, newMemSize uint64) (fee uint6 // helper function that calculates the gas dimensions for an access list access for an address func addressAccessListCost(t DimensionTracer, address common.Address) (computationGasCost uint64, stateAccessGasCost uint64) { - accessListAddresses, _ := t.GetPrevAccessList() - _, addressInAccessList := accessListAddresses[address] - if !addressInAccessList { + accessListTracer := t.GetAccessListTracer() + if !accessListTracer.HasAddress(address) { computationGasCost = params.WarmStorageReadCostEIP2929 stateAccessGasCost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 } else { @@ -1209,22 +1225,17 @@ func addressAccessListCost(t DimensionTracer, address common.Address) (computati } // helper function that calculates the gas dimensions for an access list access for a storage slot -func storageSlotAccessListCost(t DimensionTracer, address common.Address, slot common.Hash) (computationGasCost uint64, stateAccessGasCost uint64) { - accessListAddresses, accessListSlots := t.GetPrevAccessList() - idx, ok := accessListAddresses[address] - if !ok { - // no such address (and hence zero slots) - return params.WarmStorageReadCostEIP2929, params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 - } - if idx == -1 { - // address yes, but no slots - return params.WarmStorageReadCostEIP2929, params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 - } - _, slotPresent := accessListSlots[idx][slot] - if !slotPresent { - return params.WarmStorageReadCostEIP2929, params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 +func storageSlotAccessListCost(t DimensionTracer, address common.Address, slot common.Hash) ( + computationGasCost uint64, stateAccessGasCost uint64) { + accessListTracer := t.GetAccessListTracer() + if !accessListTracer.HasSlot(address, slot) { + computationGasCost = params.WarmStorageReadCostEIP2929 + stateAccessGasCost = params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929 + } else { + computationGasCost = params.WarmStorageReadCostEIP2929 + stateAccessGasCost = 0 } - return params.WarmStorageReadCostEIP2929, 0 + return computationGasCost, stateAccessGasCost } // wherever it's possible, check that the gas dimensions are sane @@ -1256,14 +1267,16 @@ func checkGasDimensionsEqualOneDimensionalGas( func checkGasDimensionsEqualCallGas( pc uint64, op byte, + depth int, codeExecutionCost uint64, dim GasesByDimension, ) error { if dim.OneDimensionalGasCost != dim.Computation+dim.StateAccess+dim.StateGrowth+dim.HistoryGrowth { return fmt.Errorf( - "unexpected call gas cost mismatch: pc %d, op %s, with codeExecutionCost %d, expected %d == %v", + "unexpected call gas cost mismatch: pc %d, op %s, depth %d, with codeExecutionCost %d, expected %d == %v", pc, vm.OpCode(op).String(), + depth, codeExecutionCost, dim.OneDimensionalGasCost, dim, @@ -1278,15 +1291,17 @@ func checkGasDimensionsEqualCallGas( func checkOneDimGreaterThanMultiDimGas( pc uint64, op byte, + depth int, codeExecutionCost uint64, dim GasesByDimension, ) error { if codeExecutionCost == 0 { if !(dim.OneDimensionalGasCost == dim.Computation+dim.StateAccess+dim.StateGrowth+dim.HistoryGrowth) { return fmt.Errorf( - "unexpected unequal precompile adjustment gas cost mismatch: pc %d, op %s, with codeExecutionCost %d, expected %d == %v", + "unexpected unequal precompile adjustment gas cost mismatch: pc %d, op %s, depth %d, with codeExecutionCost %d, expected %d == %v", pc, vm.OpCode(op).String(), + depth, codeExecutionCost, dim.OneDimensionalGasCost, dim, @@ -1295,9 +1310,10 @@ func checkOneDimGreaterThanMultiDimGas( } else { if !(dim.OneDimensionalGasCost > dim.Computation+dim.StateAccess+dim.StateGrowth+dim.HistoryGrowth) { return fmt.Errorf( - "unexpected precompile adjustment gas cost mismatch: pc %d, op %s, with codeExecutionCost %d, expected %d > %v", + "unexpected precompile adjustment gas cost mismatch: pc %d, op %s, depth %d, with codeExecutionCost %d, expected %d > %v", pc, vm.OpCode(op).String(), + depth, codeExecutionCost, dim.OneDimensionalGasCost, dim, diff --git a/eth/tracers/native/proto/count_transactions.py b/eth/tracers/native/proto/count_transactions.py new file mode 100644 index 0000000000..5b211e1812 --- /dev/null +++ b/eth/tracers/native/proto/count_transactions.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +""" +Script to count transactions from protobuf files in the data directory structure. + +This script iterates over the data folder, ignoring the blocks/ and errors/ folders, +and reads all .pb files to count the total number of transactions saved to disk. + +Usage: + python count_transactions.py [data_directory_path] +""" + +import os +import sys +import glob +from pathlib import Path + +# Import the generated protobuf classes +try: + from gas_dimension_pb2 import TxGasDimensionResultBatch +except ImportError: + print("Error: Could not import gas_dimension_pb2 module.") + print("Make sure the protobuf file is in the same directory as this script.") + print("You may need to run: pip install protobuf") + sys.exit(1) + + +def count_transactions_in_file(file_path): + """ + Count transactions in a single protobuf file and return transaction details. + + Args: + file_path (str): Path to the protobuf file + + Returns: + tuple: (transaction_count, list of (block_number, tx_hash) tuples) + """ + try: + with open(file_path, 'rb') as f: + batch = TxGasDimensionResultBatch() + batch.ParseFromString(f.read()) + + transaction_count = len(batch.results) + transaction_details = [] + + for result in batch.results: + if hasattr(result, 'tx_hash') and result.tx_hash: + transaction_details.append((result.block_number, result.tx_hash)) + + return transaction_count, transaction_details + except Exception as e: + print(f"Error reading file {file_path}: {e}") + return 0, [] + + +def count_all_transactions(data_dir): + """ + Count all transactions in the data directory structure. + + Args: + data_dir (str): Path to the data directory + + Returns: + tuple: (total_transactions, total_files, file_details, all_transaction_details) + """ + if not os.path.exists(data_dir): + print(f"Error: Data directory '{data_dir}' does not exist.") + return 0, 0, [], [] + + total_transactions = 0 + total_files = 0 + file_details = [] + all_transaction_details = [] + + # Walk through the data directory + for root, dirs, files in os.walk(data_dir): + # Skip blocks/ and errors/ directories + dirs[:] = [d for d in dirs if d not in ['blocks', 'errors']] + + # Find all .pb files in current directory + pb_files = [f for f in files if f.endswith('.pb')] + + for pb_file in pb_files: + file_path = os.path.join(root, pb_file) + transaction_count, transaction_details = count_transactions_in_file(file_path) + + if transaction_count > 0: + total_transactions += transaction_count + total_files += 1 + file_details.append({ + 'file': file_path, + 'transactions': transaction_count + }) + all_transaction_details.extend(transaction_details) + print(f" {file_path}: {transaction_count} transactions") + + return total_transactions, total_files, file_details, all_transaction_details + + +def get_last_transaction_hash(transaction_details): + """ + Get the last transaction hash in chronological order. + + Args: + transaction_details (list): List of (block_number, tx_hash) tuples + + Returns: + str: The last transaction hash in chronological order, or None if no transactions + """ + if not transaction_details: + return None + + # Sort by block number first, then by transaction hash for consistency + sorted_transactions = sorted(transaction_details, key=lambda x: (x[0], x[1])) + + # Return the hash of the last transaction + return sorted_transactions[-1][1] + + +def main(): + """Main function to run the transaction counting script.""" + # Get data directory from command line argument or use default + if len(sys.argv) > 1: + data_dir = sys.argv[1] + else: + data_dir = "data" + + print(f"Counting transactions in directory: {data_dir}") + print("=" * 50) + + total_transactions, total_files, file_details, transaction_details = count_all_transactions(data_dir) + + print("=" * 50) + print(f"Summary:") + print(f" Total files processed: {total_files}") + print(f" Total transactions: {total_transactions:,}") + + if total_files > 0: + avg_transactions_per_file = total_transactions / total_files + print(f" Average transactions per file: {avg_transactions_per_file:.1f}") + + # Get and print the last transaction hash in chronological order + last_tx_hash = get_last_transaction_hash(transaction_details) + if last_tx_hash: + print(f" Last transaction hash (chronological): {last_tx_hash}") + else: + print(" No transaction hashes found") + + # Show breakdown by block group if available + if file_details: + print("\nBreakdown by block group:") + block_groups = {} + for detail in file_details: + # Extract block group from path (e.g., data/1000/batch_123.pb -> 1000) + path_parts = detail['file'].split(os.sep) + if len(path_parts) >= 2: + block_group = path_parts[-2] # Second to last part + if block_group not in block_groups: + block_groups[block_group] = 0 + block_groups[block_group] += detail['transactions'] + + for block_group in sorted(block_groups.keys(), key=int): + print(f" Block group {block_group}: {block_groups[block_group]:,} transactions") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/eth/tracers/native/proto/gas_dimension.pb.go b/eth/tracers/native/proto/gas_dimension.pb.go new file mode 100644 index 0000000000..da86514f01 --- /dev/null +++ b/eth/tracers/native/proto/gas_dimension.pb.go @@ -0,0 +1,519 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: eth/tracers/native/proto/gas_dimension.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// GasesByDimension represents the gas consumption for each dimension +type GasesByDimension struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the total gas cost for the opcode, across all dimensions. + OneDimensionalGasCost uint64 `protobuf:"varint,1,opt,name=one_dimensional_gas_cost,json=oneDimensionalGasCost,proto3" json:"one_dimensional_gas_cost,omitempty"` + // how much of the gas was used for computation or local memory access, stack operations, etc. + Computation uint64 `protobuf:"varint,2,opt,name=computation,proto3" json:"computation,omitempty"` + // how much of the gas was used for state access, like reading or writing to the state. + StateAccess uint64 `protobuf:"varint,3,opt,name=state_access,json=stateAccess,proto3" json:"state_access,omitempty"` + // how much of the gas was used for state growth, like creating new contracts or storage slots. + StateGrowth uint64 `protobuf:"varint,4,opt,name=state_growth,json=stateGrowth,proto3" json:"state_growth,omitempty"` + // how much of the gas was used for history growth, like writing to the history (event logs) + HistoryGrowth uint64 `protobuf:"varint,5,opt,name=history_growth,json=historyGrowth,proto3" json:"history_growth,omitempty"` + // how much gas was refunded for removing state, only applicable to SSTORE opcodes to zero. + StateGrowthRefund int64 `protobuf:"varint,6,opt,name=state_growth_refund,json=stateGrowthRefund,proto3" json:"state_growth_refund,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GasesByDimension) Reset() { + *x = GasesByDimension{} + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GasesByDimension) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GasesByDimension) ProtoMessage() {} + +func (x *GasesByDimension) ProtoReflect() protoreflect.Message { + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GasesByDimension.ProtoReflect.Descriptor instead. +func (*GasesByDimension) Descriptor() ([]byte, []int) { + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP(), []int{0} +} + +func (x *GasesByDimension) GetOneDimensionalGasCost() uint64 { + if x != nil { + return x.OneDimensionalGasCost + } + return 0 +} + +func (x *GasesByDimension) GetComputation() uint64 { + if x != nil { + return x.Computation + } + return 0 +} + +func (x *GasesByDimension) GetStateAccess() uint64 { + if x != nil { + return x.StateAccess + } + return 0 +} + +func (x *GasesByDimension) GetStateGrowth() uint64 { + if x != nil { + return x.StateGrowth + } + return 0 +} + +func (x *GasesByDimension) GetHistoryGrowth() uint64 { + if x != nil { + return x.HistoryGrowth + } + return 0 +} + +func (x *GasesByDimension) GetStateGrowthRefund() int64 { + if x != nil { + return x.StateGrowthRefund + } + return 0 +} + +// TxGasDimensionByOpcodeExecutionResult represents the execution result +type TxGasDimensionResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the total amount of gas used in the transaction + GasUsed uint64 `protobuf:"varint,1,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + // the gas paid to post the compressed transaction to the L1 chain + GasUsedL1 uint64 `protobuf:"varint,2,opt,name=gas_used_l1,json=gasUsedL1,proto3" json:"gas_used_l1,omitempty"` + // the gas paid to execute the transaction on the L2 chain + GasUsedL2 uint64 `protobuf:"varint,3,opt,name=gas_used_l2,json=gasUsedL2,proto3" json:"gas_used_l2,omitempty"` + // the intrinsic gas of the transaction, the static cost + calldata bytes cost + IntrinsicGas uint64 `protobuf:"varint,4,opt,name=intrinsic_gas,json=intrinsicGas,proto3" json:"intrinsic_gas,omitempty"` + // the sum of the gas consumption categorized by dimension for that transaction + Dimensions *GasesByDimension `protobuf:"bytes,5,opt,name=dimensions,proto3" json:"dimensions,omitempty"` + // the adjusted gas refund amount after EIP-3529 + AdjustedRefund *uint64 `protobuf:"varint,6,opt,name=adjusted_refund,json=adjustedRefund,proto3,oneof" json:"adjusted_refund,omitempty"` + // the adjustment to the gas used for the root of the transaction if it is a precompile + RootIsPrecompileAdjustment *uint64 `protobuf:"varint,7,opt,name=root_is_precompile_adjustment,json=rootIsPrecompileAdjustment,proto3,oneof" json:"root_is_precompile_adjustment,omitempty"` + // the adjustment to the gas used for the root of the transaction if it is a stylus contract + RootIsStylusAdjustment *uint64 `protobuf:"varint,8,opt,name=root_is_stylus_adjustment,json=rootIsStylusAdjustment,proto3,oneof" json:"root_is_stylus_adjustment,omitempty"` + // whether the transaction broke the rules of the VM and was rejected + Failed *bool `protobuf:"varint,9,opt,name=failed,proto3,oneof" json:"failed,omitempty"` + // the status of the transaction, for a valid transaction that followed the rules, + // but could have still failed for reasons inside the rules, like reverts, out of gas, etc. + TransactionReverted *bool `protobuf:"varint,10,opt,name=transaction_reverted,json=transactionReverted,proto3,oneof" json:"transaction_reverted,omitempty"` + // the hash of the transaction + TxHash string `protobuf:"bytes,11,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` + // the number of the block + BlockNumber uint64 `protobuf:"varint,12,opt,name=block_number,json=blockNumber,proto3" json:"block_number,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TxGasDimensionResult) Reset() { + *x = TxGasDimensionResult{} + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TxGasDimensionResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxGasDimensionResult) ProtoMessage() {} + +func (x *TxGasDimensionResult) ProtoReflect() protoreflect.Message { + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxGasDimensionResult.ProtoReflect.Descriptor instead. +func (*TxGasDimensionResult) Descriptor() ([]byte, []int) { + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP(), []int{1} +} + +func (x *TxGasDimensionResult) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *TxGasDimensionResult) GetGasUsedL1() uint64 { + if x != nil { + return x.GasUsedL1 + } + return 0 +} + +func (x *TxGasDimensionResult) GetGasUsedL2() uint64 { + if x != nil { + return x.GasUsedL2 + } + return 0 +} + +func (x *TxGasDimensionResult) GetIntrinsicGas() uint64 { + if x != nil { + return x.IntrinsicGas + } + return 0 +} + +func (x *TxGasDimensionResult) GetDimensions() *GasesByDimension { + if x != nil { + return x.Dimensions + } + return nil +} + +func (x *TxGasDimensionResult) GetAdjustedRefund() uint64 { + if x != nil && x.AdjustedRefund != nil { + return *x.AdjustedRefund + } + return 0 +} + +func (x *TxGasDimensionResult) GetRootIsPrecompileAdjustment() uint64 { + if x != nil && x.RootIsPrecompileAdjustment != nil { + return *x.RootIsPrecompileAdjustment + } + return 0 +} + +func (x *TxGasDimensionResult) GetRootIsStylusAdjustment() uint64 { + if x != nil && x.RootIsStylusAdjustment != nil { + return *x.RootIsStylusAdjustment + } + return 0 +} + +func (x *TxGasDimensionResult) GetFailed() bool { + if x != nil && x.Failed != nil { + return *x.Failed + } + return false +} + +func (x *TxGasDimensionResult) GetTransactionReverted() bool { + if x != nil && x.TransactionReverted != nil { + return *x.TransactionReverted + } + return false +} + +func (x *TxGasDimensionResult) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +func (x *TxGasDimensionResult) GetBlockNumber() uint64 { + if x != nil { + return x.BlockNumber + } + return 0 +} + +// batch results to disk for performance +type TxGasDimensionResultBatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + Results []*TxGasDimensionResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TxGasDimensionResultBatch) Reset() { + *x = TxGasDimensionResultBatch{} + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TxGasDimensionResultBatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxGasDimensionResultBatch) ProtoMessage() {} + +func (x *TxGasDimensionResultBatch) ProtoReflect() protoreflect.Message { + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxGasDimensionResultBatch.ProtoReflect.Descriptor instead. +func (*TxGasDimensionResultBatch) Descriptor() ([]byte, []int) { + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP(), []int{2} +} + +func (x *TxGasDimensionResultBatch) GetResults() []*TxGasDimensionResult { + if x != nil { + return x.Results + } + return nil +} + +type BlockInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + // the timestamp of the block + Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // how long it took to insert the block + InsertDurationNanoseconds uint64 `protobuf:"varint,2,opt,name=insert_duration_nanoseconds,json=insertDurationNanoseconds,proto3" json:"insert_duration_nanoseconds,omitempty"` + // block base fee + BaseFee uint64 `protobuf:"varint,3,opt,name=base_fee,json=baseFee,proto3" json:"base_fee,omitempty"` + // the number of the block + BlockNumber uint64 `protobuf:"varint,4,opt,name=block_number,json=blockNumber,proto3" json:"block_number,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BlockInfo) Reset() { + *x = BlockInfo{} + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockInfo) ProtoMessage() {} + +func (x *BlockInfo) ProtoReflect() protoreflect.Message { + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockInfo.ProtoReflect.Descriptor instead. +func (*BlockInfo) Descriptor() ([]byte, []int) { + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP(), []int{3} +} + +func (x *BlockInfo) GetTimestamp() uint64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *BlockInfo) GetInsertDurationNanoseconds() uint64 { + if x != nil { + return x.InsertDurationNanoseconds + } + return 0 +} + +func (x *BlockInfo) GetBaseFee() uint64 { + if x != nil { + return x.BaseFee + } + return 0 +} + +func (x *BlockInfo) GetBlockNumber() uint64 { + if x != nil { + return x.BlockNumber + } + return 0 +} + +type BlockInfoBatch struct { + state protoimpl.MessageState `protogen:"open.v1"` + Blocks []*BlockInfo `protobuf:"bytes,1,rep,name=blocks,proto3" json:"blocks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BlockInfoBatch) Reset() { + *x = BlockInfoBatch{} + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockInfoBatch) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockInfoBatch) ProtoMessage() {} + +func (x *BlockInfoBatch) ProtoReflect() protoreflect.Message { + mi := &file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockInfoBatch.ProtoReflect.Descriptor instead. +func (*BlockInfoBatch) Descriptor() ([]byte, []int) { + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP(), []int{4} +} + +func (x *BlockInfoBatch) GetBlocks() []*BlockInfo { + if x != nil { + return x.Blocks + } + return nil +} + +var File_eth_tracers_native_proto_gas_dimension_proto protoreflect.FileDescriptor + +const file_eth_tracers_native_proto_gas_dimension_proto_rawDesc = "" + + "\n" + + ",eth/tracers/native/proto/gas_dimension.proto\x12\x18eth.tracers.native.proto\"\x8a\x02\n" + + "\x10GasesByDimension\x127\n" + + "\x18one_dimensional_gas_cost\x18\x01 \x01(\x04R\x15oneDimensionalGasCost\x12 \n" + + "\vcomputation\x18\x02 \x01(\x04R\vcomputation\x12!\n" + + "\fstate_access\x18\x03 \x01(\x04R\vstateAccess\x12!\n" + + "\fstate_growth\x18\x04 \x01(\x04R\vstateGrowth\x12%\n" + + "\x0ehistory_growth\x18\x05 \x01(\x04R\rhistoryGrowth\x12.\n" + + "\x13state_growth_refund\x18\x06 \x01(\x03R\x11stateGrowthRefund\"\xa1\x05\n" + + "\x14TxGasDimensionResult\x12\x19\n" + + "\bgas_used\x18\x01 \x01(\x04R\agasUsed\x12\x1e\n" + + "\vgas_used_l1\x18\x02 \x01(\x04R\tgasUsedL1\x12\x1e\n" + + "\vgas_used_l2\x18\x03 \x01(\x04R\tgasUsedL2\x12#\n" + + "\rintrinsic_gas\x18\x04 \x01(\x04R\fintrinsicGas\x12J\n" + + "\n" + + "dimensions\x18\x05 \x01(\v2*.eth.tracers.native.proto.GasesByDimensionR\n" + + "dimensions\x12,\n" + + "\x0fadjusted_refund\x18\x06 \x01(\x04H\x00R\x0eadjustedRefund\x88\x01\x01\x12F\n" + + "\x1droot_is_precompile_adjustment\x18\a \x01(\x04H\x01R\x1arootIsPrecompileAdjustment\x88\x01\x01\x12>\n" + + "\x19root_is_stylus_adjustment\x18\b \x01(\x04H\x02R\x16rootIsStylusAdjustment\x88\x01\x01\x12\x1b\n" + + "\x06failed\x18\t \x01(\bH\x03R\x06failed\x88\x01\x01\x126\n" + + "\x14transaction_reverted\x18\n" + + " \x01(\bH\x04R\x13transactionReverted\x88\x01\x01\x12\x17\n" + + "\atx_hash\x18\v \x01(\tR\x06txHash\x12!\n" + + "\fblock_number\x18\f \x01(\x04R\vblockNumberB\x12\n" + + "\x10_adjusted_refundB \n" + + "\x1e_root_is_precompile_adjustmentB\x1c\n" + + "\x1a_root_is_stylus_adjustmentB\t\n" + + "\a_failedB\x17\n" + + "\x15_transaction_reverted\"e\n" + + "\x19TxGasDimensionResultBatch\x12H\n" + + "\aresults\x18\x01 \x03(\v2..eth.tracers.native.proto.TxGasDimensionResultR\aresults\"\xa7\x01\n" + + "\tBlockInfo\x12\x1c\n" + + "\ttimestamp\x18\x01 \x01(\x04R\ttimestamp\x12>\n" + + "\x1binsert_duration_nanoseconds\x18\x02 \x01(\x04R\x19insertDurationNanoseconds\x12\x19\n" + + "\bbase_fee\x18\x03 \x01(\x04R\abaseFee\x12!\n" + + "\fblock_number\x18\x04 \x01(\x04R\vblockNumber\"M\n" + + "\x0eBlockInfoBatch\x12;\n" + + "\x06blocks\x18\x01 \x03(\v2#.eth.tracers.native.proto.BlockInfoR\x06blocksB:Z8github.com/ethereum/go-ethereum/eth/tracers/native/protob\x06proto3" + +var ( + file_eth_tracers_native_proto_gas_dimension_proto_rawDescOnce sync.Once + file_eth_tracers_native_proto_gas_dimension_proto_rawDescData []byte +) + +func file_eth_tracers_native_proto_gas_dimension_proto_rawDescGZIP() []byte { + file_eth_tracers_native_proto_gas_dimension_proto_rawDescOnce.Do(func() { + file_eth_tracers_native_proto_gas_dimension_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_eth_tracers_native_proto_gas_dimension_proto_rawDesc), len(file_eth_tracers_native_proto_gas_dimension_proto_rawDesc))) + }) + return file_eth_tracers_native_proto_gas_dimension_proto_rawDescData +} + +var file_eth_tracers_native_proto_gas_dimension_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_eth_tracers_native_proto_gas_dimension_proto_goTypes = []any{ + (*GasesByDimension)(nil), // 0: eth.tracers.native.proto.GasesByDimension + (*TxGasDimensionResult)(nil), // 1: eth.tracers.native.proto.TxGasDimensionResult + (*TxGasDimensionResultBatch)(nil), // 2: eth.tracers.native.proto.TxGasDimensionResultBatch + (*BlockInfo)(nil), // 3: eth.tracers.native.proto.BlockInfo + (*BlockInfoBatch)(nil), // 4: eth.tracers.native.proto.BlockInfoBatch +} +var file_eth_tracers_native_proto_gas_dimension_proto_depIdxs = []int32{ + 0, // 0: eth.tracers.native.proto.TxGasDimensionResult.dimensions:type_name -> eth.tracers.native.proto.GasesByDimension + 1, // 1: eth.tracers.native.proto.TxGasDimensionResultBatch.results:type_name -> eth.tracers.native.proto.TxGasDimensionResult + 3, // 2: eth.tracers.native.proto.BlockInfoBatch.blocks:type_name -> eth.tracers.native.proto.BlockInfo + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_eth_tracers_native_proto_gas_dimension_proto_init() } +func file_eth_tracers_native_proto_gas_dimension_proto_init() { + if File_eth_tracers_native_proto_gas_dimension_proto != nil { + return + } + file_eth_tracers_native_proto_gas_dimension_proto_msgTypes[1].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_eth_tracers_native_proto_gas_dimension_proto_rawDesc), len(file_eth_tracers_native_proto_gas_dimension_proto_rawDesc)), + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_eth_tracers_native_proto_gas_dimension_proto_goTypes, + DependencyIndexes: file_eth_tracers_native_proto_gas_dimension_proto_depIdxs, + MessageInfos: file_eth_tracers_native_proto_gas_dimension_proto_msgTypes, + }.Build() + File_eth_tracers_native_proto_gas_dimension_proto = out.File + file_eth_tracers_native_proto_gas_dimension_proto_goTypes = nil + file_eth_tracers_native_proto_gas_dimension_proto_depIdxs = nil +} diff --git a/eth/tracers/native/proto/gas_dimension_by_opcode.proto b/eth/tracers/native/proto/gas_dimension.proto similarity index 66% rename from eth/tracers/native/proto/gas_dimension_by_opcode.proto rename to eth/tracers/native/proto/gas_dimension.proto index e2ffd6de93..662e0c4075 100644 --- a/eth/tracers/native/proto/gas_dimension_by_opcode.proto +++ b/eth/tracers/native/proto/gas_dimension.proto @@ -18,39 +18,53 @@ message GasesByDimension { uint64 history_growth = 5; // how much gas was refunded for removing state, only applicable to SSTORE opcodes to zero. int64 state_growth_refund = 6; - // how much of the gas was used for child execution, for CALLs, CREATEs, etc. - uint64 child_execution_cost = 7; } // TxGasDimensionByOpcodeExecutionResult represents the execution result -message TxGasDimensionByOpcodeExecutionResult { +message TxGasDimensionResult { // the total amount of gas used in the transaction uint64 gas_used = 1; // the gas paid to post the compressed transaction to the L1 chain - uint64 gas_used_l1 = 7; + uint64 gas_used_l1 = 2; // the gas paid to execute the transaction on the L2 chain - uint64 gas_used_l2 = 8; + uint64 gas_used_l2 = 3; // the intrinsic gas of the transaction, the static cost + calldata bytes cost - uint64 intrinsic_gas = 9; + uint64 intrinsic_gas = 4; + // the sum of the gas consumption categorized by dimension for that transaction + GasesByDimension dimensions = 5; // the adjusted gas refund amount after EIP-3529 - optional uint64 adjusted_refund = 10; + optional uint64 adjusted_refund = 6; // the adjustment to the gas used for the root of the transaction if it is a precompile - optional uint64 root_is_precompile_adjustment = 12; + optional uint64 root_is_precompile_adjustment = 7; // the adjustment to the gas used for the root of the transaction if it is a stylus contract - optional uint64 root_is_stylus_adjustment = 14; + optional uint64 root_is_stylus_adjustment = 8; // whether the transaction broke the rules of the VM and was rejected - optional bool failed = 2; + optional bool failed = 9; // the status of the transaction, for a valid transaction that followed the rules, // but could have still failed for reasons inside the rules, like reverts, out of gas, etc. - //uint64 status = 11; // replaced by optional transaction_reverted which is more space efficient - optional bool transaction_reverted = 13; - // a map of each opcode to the sum of the gas consumption categorized by dimension for that opcode - map dimensions = 3; + optional bool transaction_reverted = 10; // the hash of the transaction - string tx_hash = 4; + string tx_hash = 11; + // the number of the block + uint64 block_number = 12; +} + +// batch results to disk for performance +message TxGasDimensionResultBatch { + repeated TxGasDimensionResult results = 1; +} + +message BlockInfo { // the timestamp of the block - uint64 block_timestamp = 5; - // the block number of the transaction - // Using string to represent big.Int - string block_number = 6; + uint64 timestamp = 1; + // how long it took to insert the block + uint64 insert_duration_nanoseconds = 2; + // block base fee + uint64 base_fee = 3; + // the number of the block + uint64 block_number = 4; +} + +message BlockInfoBatch { + repeated BlockInfo blocks = 1; } \ No newline at end of file diff --git a/eth/tracers/native/proto/gas_dimension_by_opcode.pb.go b/eth/tracers/native/proto/gas_dimension_by_opcode.pb.go deleted file mode 100644 index 92ba70ba14..0000000000 --- a/eth/tracers/native/proto/gas_dimension_by_opcode.pb.go +++ /dev/null @@ -1,371 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.6 -// protoc v5.29.3 -// source: eth/tracers/native/proto/gas_dimension_by_opcode.proto - -package proto - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// GasesByDimension represents the gas consumption for each dimension -type GasesByDimension struct { - state protoimpl.MessageState `protogen:"open.v1"` - // the total gas cost for the opcode, across all dimensions. - OneDimensionalGasCost uint64 `protobuf:"varint,1,opt,name=one_dimensional_gas_cost,json=oneDimensionalGasCost,proto3" json:"one_dimensional_gas_cost,omitempty"` - // how much of the gas was used for computation or local memory access, stack operations, etc. - Computation uint64 `protobuf:"varint,2,opt,name=computation,proto3" json:"computation,omitempty"` - // how much of the gas was used for state access, like reading or writing to the state. - StateAccess uint64 `protobuf:"varint,3,opt,name=state_access,json=stateAccess,proto3" json:"state_access,omitempty"` - // how much of the gas was used for state growth, like creating new contracts or storage slots. - StateGrowth uint64 `protobuf:"varint,4,opt,name=state_growth,json=stateGrowth,proto3" json:"state_growth,omitempty"` - // how much of the gas was used for history growth, like writing to the history (event logs) - HistoryGrowth uint64 `protobuf:"varint,5,opt,name=history_growth,json=historyGrowth,proto3" json:"history_growth,omitempty"` - // how much gas was refunded for removing state, only applicable to SSTORE opcodes to zero. - StateGrowthRefund int64 `protobuf:"varint,6,opt,name=state_growth_refund,json=stateGrowthRefund,proto3" json:"state_growth_refund,omitempty"` - // how much of the gas was used for child execution, for CALLs, CREATEs, etc. - ChildExecutionCost uint64 `protobuf:"varint,7,opt,name=child_execution_cost,json=childExecutionCost,proto3" json:"child_execution_cost,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GasesByDimension) Reset() { - *x = GasesByDimension{} - mi := &file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GasesByDimension) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GasesByDimension) ProtoMessage() {} - -func (x *GasesByDimension) ProtoReflect() protoreflect.Message { - mi := &file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GasesByDimension.ProtoReflect.Descriptor instead. -func (*GasesByDimension) Descriptor() ([]byte, []int) { - return file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescGZIP(), []int{0} -} - -func (x *GasesByDimension) GetOneDimensionalGasCost() uint64 { - if x != nil { - return x.OneDimensionalGasCost - } - return 0 -} - -func (x *GasesByDimension) GetComputation() uint64 { - if x != nil { - return x.Computation - } - return 0 -} - -func (x *GasesByDimension) GetStateAccess() uint64 { - if x != nil { - return x.StateAccess - } - return 0 -} - -func (x *GasesByDimension) GetStateGrowth() uint64 { - if x != nil { - return x.StateGrowth - } - return 0 -} - -func (x *GasesByDimension) GetHistoryGrowth() uint64 { - if x != nil { - return x.HistoryGrowth - } - return 0 -} - -func (x *GasesByDimension) GetStateGrowthRefund() int64 { - if x != nil { - return x.StateGrowthRefund - } - return 0 -} - -func (x *GasesByDimension) GetChildExecutionCost() uint64 { - if x != nil { - return x.ChildExecutionCost - } - return 0 -} - -// TxGasDimensionByOpcodeExecutionResult represents the execution result -type TxGasDimensionByOpcodeExecutionResult struct { - state protoimpl.MessageState `protogen:"open.v1"` - // the total amount of gas used in the transaction - GasUsed uint64 `protobuf:"varint,1,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` - // the gas paid to post the compressed transaction to the L1 chain - GasUsedL1 uint64 `protobuf:"varint,7,opt,name=gas_used_l1,json=gasUsedL1,proto3" json:"gas_used_l1,omitempty"` - // the gas paid to execute the transaction on the L2 chain - GasUsedL2 uint64 `protobuf:"varint,8,opt,name=gas_used_l2,json=gasUsedL2,proto3" json:"gas_used_l2,omitempty"` - // the intrinsic gas of the transaction, the static cost + calldata bytes cost - IntrinsicGas uint64 `protobuf:"varint,9,opt,name=intrinsic_gas,json=intrinsicGas,proto3" json:"intrinsic_gas,omitempty"` - // the adjusted gas refund amount after EIP-3529 - AdjustedRefund *uint64 `protobuf:"varint,10,opt,name=adjusted_refund,json=adjustedRefund,proto3,oneof" json:"adjusted_refund,omitempty"` - // the adjustment to the gas used for the root of the transaction if it is a precompile - RootIsPrecompileAdjustment *uint64 `protobuf:"varint,12,opt,name=root_is_precompile_adjustment,json=rootIsPrecompileAdjustment,proto3,oneof" json:"root_is_precompile_adjustment,omitempty"` - // the adjustment to the gas used for the root of the transaction if it is a stylus contract - RootIsStylusAdjustment *uint64 `protobuf:"varint,14,opt,name=root_is_stylus_adjustment,json=rootIsStylusAdjustment,proto3,oneof" json:"root_is_stylus_adjustment,omitempty"` - // whether the transaction broke the rules of the VM and was rejected - Failed *bool `protobuf:"varint,2,opt,name=failed,proto3,oneof" json:"failed,omitempty"` - // the status of the transaction, for a valid transaction that followed the rules, - // but could have still failed for reasons inside the rules, like reverts, out of gas, etc. - // uint64 status = 11; // replaced by optional transaction_reverted which is more space efficient - TransactionReverted *bool `protobuf:"varint,13,opt,name=transaction_reverted,json=transactionReverted,proto3,oneof" json:"transaction_reverted,omitempty"` - // a map of each opcode to the sum of the gas consumption categorized by dimension for that opcode - Dimensions map[uint32]*GasesByDimension `protobuf:"bytes,3,rep,name=dimensions,proto3" json:"dimensions,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - // the hash of the transaction - TxHash string `protobuf:"bytes,4,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` - // the timestamp of the block - BlockTimestamp uint64 `protobuf:"varint,5,opt,name=block_timestamp,json=blockTimestamp,proto3" json:"block_timestamp,omitempty"` - // the block number of the transaction - // Using string to represent big.Int - BlockNumber string `protobuf:"bytes,6,opt,name=block_number,json=blockNumber,proto3" json:"block_number,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TxGasDimensionByOpcodeExecutionResult) Reset() { - *x = TxGasDimensionByOpcodeExecutionResult{} - mi := &file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TxGasDimensionByOpcodeExecutionResult) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TxGasDimensionByOpcodeExecutionResult) ProtoMessage() {} - -func (x *TxGasDimensionByOpcodeExecutionResult) ProtoReflect() protoreflect.Message { - mi := &file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TxGasDimensionByOpcodeExecutionResult.ProtoReflect.Descriptor instead. -func (*TxGasDimensionByOpcodeExecutionResult) Descriptor() ([]byte, []int) { - return file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescGZIP(), []int{1} -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetGasUsed() uint64 { - if x != nil { - return x.GasUsed - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetGasUsedL1() uint64 { - if x != nil { - return x.GasUsedL1 - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetGasUsedL2() uint64 { - if x != nil { - return x.GasUsedL2 - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetIntrinsicGas() uint64 { - if x != nil { - return x.IntrinsicGas - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetAdjustedRefund() uint64 { - if x != nil && x.AdjustedRefund != nil { - return *x.AdjustedRefund - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetRootIsPrecompileAdjustment() uint64 { - if x != nil && x.RootIsPrecompileAdjustment != nil { - return *x.RootIsPrecompileAdjustment - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetRootIsStylusAdjustment() uint64 { - if x != nil && x.RootIsStylusAdjustment != nil { - return *x.RootIsStylusAdjustment - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetFailed() bool { - if x != nil && x.Failed != nil { - return *x.Failed - } - return false -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetTransactionReverted() bool { - if x != nil && x.TransactionReverted != nil { - return *x.TransactionReverted - } - return false -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetDimensions() map[uint32]*GasesByDimension { - if x != nil { - return x.Dimensions - } - return nil -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetTxHash() string { - if x != nil { - return x.TxHash - } - return "" -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetBlockTimestamp() uint64 { - if x != nil { - return x.BlockTimestamp - } - return 0 -} - -func (x *TxGasDimensionByOpcodeExecutionResult) GetBlockNumber() string { - if x != nil { - return x.BlockNumber - } - return "" -} - -var File_eth_tracers_native_proto_gas_dimension_by_opcode_proto protoreflect.FileDescriptor - -const file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDesc = "" + - "\n" + - "6eth/tracers/native/proto/gas_dimension_by_opcode.proto\x12\x18eth.tracers.native.proto\"\xbc\x02\n" + - "\x10GasesByDimension\x127\n" + - "\x18one_dimensional_gas_cost\x18\x01 \x01(\x04R\x15oneDimensionalGasCost\x12 \n" + - "\vcomputation\x18\x02 \x01(\x04R\vcomputation\x12!\n" + - "\fstate_access\x18\x03 \x01(\x04R\vstateAccess\x12!\n" + - "\fstate_growth\x18\x04 \x01(\x04R\vstateGrowth\x12%\n" + - "\x0ehistory_growth\x18\x05 \x01(\x04R\rhistoryGrowth\x12.\n" + - "\x13state_growth_refund\x18\x06 \x01(\x03R\x11stateGrowthRefund\x120\n" + - "\x14child_execution_cost\x18\a \x01(\x04R\x12childExecutionCost\"\xeb\x06\n" + - "%TxGasDimensionByOpcodeExecutionResult\x12\x19\n" + - "\bgas_used\x18\x01 \x01(\x04R\agasUsed\x12\x1e\n" + - "\vgas_used_l1\x18\a \x01(\x04R\tgasUsedL1\x12\x1e\n" + - "\vgas_used_l2\x18\b \x01(\x04R\tgasUsedL2\x12#\n" + - "\rintrinsic_gas\x18\t \x01(\x04R\fintrinsicGas\x12,\n" + - "\x0fadjusted_refund\x18\n" + - " \x01(\x04H\x00R\x0eadjustedRefund\x88\x01\x01\x12F\n" + - "\x1droot_is_precompile_adjustment\x18\f \x01(\x04H\x01R\x1arootIsPrecompileAdjustment\x88\x01\x01\x12>\n" + - "\x19root_is_stylus_adjustment\x18\x0e \x01(\x04H\x02R\x16rootIsStylusAdjustment\x88\x01\x01\x12\x1b\n" + - "\x06failed\x18\x02 \x01(\bH\x03R\x06failed\x88\x01\x01\x126\n" + - "\x14transaction_reverted\x18\r \x01(\bH\x04R\x13transactionReverted\x88\x01\x01\x12o\n" + - "\n" + - "dimensions\x18\x03 \x03(\v2O.eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult.DimensionsEntryR\n" + - "dimensions\x12\x17\n" + - "\atx_hash\x18\x04 \x01(\tR\x06txHash\x12'\n" + - "\x0fblock_timestamp\x18\x05 \x01(\x04R\x0eblockTimestamp\x12!\n" + - "\fblock_number\x18\x06 \x01(\tR\vblockNumber\x1ai\n" + - "\x0fDimensionsEntry\x12\x10\n" + - "\x03key\x18\x01 \x01(\rR\x03key\x12@\n" + - "\x05value\x18\x02 \x01(\v2*.eth.tracers.native.proto.GasesByDimensionR\x05value:\x028\x01B\x12\n" + - "\x10_adjusted_refundB \n" + - "\x1e_root_is_precompile_adjustmentB\x1c\n" + - "\x1a_root_is_stylus_adjustmentB\t\n" + - "\a_failedB\x17\n" + - "\x15_transaction_revertedB:Z8github.com/ethereum/go-ethereum/eth/tracers/native/protob\x06proto3" - -var ( - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescOnce sync.Once - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescData []byte -) - -func file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescGZIP() []byte { - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescOnce.Do(func() { - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDesc), len(file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDesc))) - }) - return file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDescData -} - -var file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_goTypes = []any{ - (*GasesByDimension)(nil), // 0: eth.tracers.native.proto.GasesByDimension - (*TxGasDimensionByOpcodeExecutionResult)(nil), // 1: eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult - nil, // 2: eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult.DimensionsEntry -} -var file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_depIdxs = []int32{ - 2, // 0: eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult.dimensions:type_name -> eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult.DimensionsEntry - 0, // 1: eth.tracers.native.proto.TxGasDimensionByOpcodeExecutionResult.DimensionsEntry.value:type_name -> eth.tracers.native.proto.GasesByDimension - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_init() } -func file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_init() { - if File_eth_tracers_native_proto_gas_dimension_by_opcode_proto != nil { - return - } - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes[1].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDesc), len(file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_rawDesc)), - NumEnums: 0, - NumMessages: 3, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_goTypes, - DependencyIndexes: file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_depIdxs, - MessageInfos: file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_msgTypes, - }.Build() - File_eth_tracers_native_proto_gas_dimension_by_opcode_proto = out.File - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_goTypes = nil - file_eth_tracers_native_proto_gas_dimension_by_opcode_proto_depIdxs = nil -} diff --git a/eth/tracers/native/proto/gas_dimension_pb2.py b/eth/tracers/native/proto/gas_dimension_pb2.py new file mode 100644 index 0000000000..767357eace --- /dev/null +++ b/eth/tracers/native/proto/gas_dimension_pb2.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: eth/tracers/native/proto/gas_dimension.proto +# Protobuf Python Version: 5.29.3 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 3, + '', + 'eth/tracers/native/proto/gas_dimension.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,eth/tracers/native/proto/gas_dimension.proto\x12\x18\x65th.tracers.native.proto\"\xaa\x01\n\x10GasesByDimension\x12 \n\x18one_dimensional_gas_cost\x18\x01 \x01(\x04\x12\x13\n\x0b\x63omputation\x18\x02 \x01(\x04\x12\x14\n\x0cstate_access\x18\x03 \x01(\x04\x12\x14\n\x0cstate_growth\x18\x04 \x01(\x04\x12\x16\n\x0ehistory_growth\x18\x05 \x01(\x04\x12\x1b\n\x13state_growth_refund\x18\x06 \x01(\x03\"\xf2\x03\n\x14TxGasDimensionResult\x12\x10\n\x08gas_used\x18\x01 \x01(\x04\x12\x13\n\x0bgas_used_l1\x18\x02 \x01(\x04\x12\x13\n\x0bgas_used_l2\x18\x03 \x01(\x04\x12\x15\n\rintrinsic_gas\x18\x04 \x01(\x04\x12>\n\ndimensions\x18\x05 \x01(\x0b\x32*.eth.tracers.native.proto.GasesByDimension\x12\x1c\n\x0f\x61\x64justed_refund\x18\x06 \x01(\x04H\x00\x88\x01\x01\x12*\n\x1droot_is_precompile_adjustment\x18\x07 \x01(\x04H\x01\x88\x01\x01\x12&\n\x19root_is_stylus_adjustment\x18\x08 \x01(\x04H\x02\x88\x01\x01\x12\x13\n\x06\x66\x61iled\x18\t \x01(\x08H\x03\x88\x01\x01\x12!\n\x14transaction_reverted\x18\n \x01(\x08H\x04\x88\x01\x01\x12\x0f\n\x07tx_hash\x18\x0b \x01(\t\x12\x14\n\x0c\x62lock_number\x18\x0c \x01(\x04\x42\x12\n\x10_adjusted_refundB \n\x1e_root_is_precompile_adjustmentB\x1c\n\x1a_root_is_stylus_adjustmentB\t\n\x07_failedB\x17\n\x15_transaction_reverted\"\\\n\x19TxGasDimensionResultBatch\x12?\n\x07results\x18\x01 \x03(\x0b\x32..eth.tracers.native.proto.TxGasDimensionResult\"k\n\tBlockInfo\x12\x11\n\ttimestamp\x18\x01 \x01(\x04\x12#\n\x1binsert_duration_nanoseconds\x18\x02 \x01(\x04\x12\x10\n\x08\x62\x61se_fee\x18\x03 \x01(\x04\x12\x14\n\x0c\x62lock_number\x18\x04 \x01(\x04\"E\n\x0e\x42lockInfoBatch\x12\x33\n\x06\x62locks\x18\x01 \x03(\x0b\x32#.eth.tracers.native.proto.BlockInfoB:Z8github.com/ethereum/go-ethereum/eth/tracers/native/protob\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'eth.tracers.native.proto.gas_dimension_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'Z8github.com/ethereum/go-ethereum/eth/tracers/native/proto' + _globals['_GASESBYDIMENSION']._serialized_start=75 + _globals['_GASESBYDIMENSION']._serialized_end=245 + _globals['_TXGASDIMENSIONRESULT']._serialized_start=248 + _globals['_TXGASDIMENSIONRESULT']._serialized_end=746 + _globals['_TXGASDIMENSIONRESULTBATCH']._serialized_start=748 + _globals['_TXGASDIMENSIONRESULTBATCH']._serialized_end=840 + _globals['_BLOCKINFO']._serialized_start=842 + _globals['_BLOCKINFO']._serialized_end=949 + _globals['_BLOCKINFOBATCH']._serialized_start=951 + _globals['_BLOCKINFOBATCH']._serialized_end=1020 +# @@protoc_insertion_point(module_scope) diff --git a/eth/tracers/native/proto/gas_dimension_pb2.pyi b/eth/tracers/native/proto/gas_dimension_pb2.pyi new file mode 100644 index 0000000000..d469ebc89a --- /dev/null +++ b/eth/tracers/native/proto/gas_dimension_pb2.pyi @@ -0,0 +1,190 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class GasesByDimension(google.protobuf.message.Message): + """GasesByDimension represents the gas consumption for each dimension""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ONE_DIMENSIONAL_GAS_COST_FIELD_NUMBER: builtins.int + COMPUTATION_FIELD_NUMBER: builtins.int + STATE_ACCESS_FIELD_NUMBER: builtins.int + STATE_GROWTH_FIELD_NUMBER: builtins.int + HISTORY_GROWTH_FIELD_NUMBER: builtins.int + STATE_GROWTH_REFUND_FIELD_NUMBER: builtins.int + one_dimensional_gas_cost: builtins.int + """the total gas cost for the opcode, across all dimensions.""" + computation: builtins.int + """how much of the gas was used for computation or local memory access, stack operations, etc.""" + state_access: builtins.int + """how much of the gas was used for state access, like reading or writing to the state.""" + state_growth: builtins.int + """how much of the gas was used for state growth, like creating new contracts or storage slots.""" + history_growth: builtins.int + """how much of the gas was used for history growth, like writing to the history (event logs)""" + state_growth_refund: builtins.int + """how much gas was refunded for removing state, only applicable to SSTORE opcodes to zero.""" + def __init__( + self, + *, + one_dimensional_gas_cost: builtins.int = ..., + computation: builtins.int = ..., + state_access: builtins.int = ..., + state_growth: builtins.int = ..., + history_growth: builtins.int = ..., + state_growth_refund: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["computation", b"computation", "history_growth", b"history_growth", "one_dimensional_gas_cost", b"one_dimensional_gas_cost", "state_access", b"state_access", "state_growth", b"state_growth", "state_growth_refund", b"state_growth_refund"]) -> None: ... + +global___GasesByDimension = GasesByDimension + +@typing.final +class TxGasDimensionResult(google.protobuf.message.Message): + """TxGasDimensionByOpcodeExecutionResult represents the execution result""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + GAS_USED_FIELD_NUMBER: builtins.int + GAS_USED_L1_FIELD_NUMBER: builtins.int + GAS_USED_L2_FIELD_NUMBER: builtins.int + INTRINSIC_GAS_FIELD_NUMBER: builtins.int + DIMENSIONS_FIELD_NUMBER: builtins.int + ADJUSTED_REFUND_FIELD_NUMBER: builtins.int + ROOT_IS_PRECOMPILE_ADJUSTMENT_FIELD_NUMBER: builtins.int + ROOT_IS_STYLUS_ADJUSTMENT_FIELD_NUMBER: builtins.int + FAILED_FIELD_NUMBER: builtins.int + TRANSACTION_REVERTED_FIELD_NUMBER: builtins.int + TX_HASH_FIELD_NUMBER: builtins.int + BLOCK_NUMBER_FIELD_NUMBER: builtins.int + gas_used: builtins.int + """the total amount of gas used in the transaction""" + gas_used_l1: builtins.int + """the gas paid to post the compressed transaction to the L1 chain""" + gas_used_l2: builtins.int + """the gas paid to execute the transaction on the L2 chain""" + intrinsic_gas: builtins.int + """the intrinsic gas of the transaction, the static cost + calldata bytes cost""" + adjusted_refund: builtins.int + """the adjusted gas refund amount after EIP-3529""" + root_is_precompile_adjustment: builtins.int + """the adjustment to the gas used for the root of the transaction if it is a precompile""" + root_is_stylus_adjustment: builtins.int + """the adjustment to the gas used for the root of the transaction if it is a stylus contract""" + failed: builtins.bool + """whether the transaction broke the rules of the VM and was rejected""" + transaction_reverted: builtins.bool + """the status of the transaction, for a valid transaction that followed the rules, + but could have still failed for reasons inside the rules, like reverts, out of gas, etc. + """ + tx_hash: builtins.str + """the hash of the transaction""" + block_number: builtins.int + """the number of the block""" + @property + def dimensions(self) -> global___GasesByDimension: + """the sum of the gas consumption categorized by dimension for that transaction""" + + def __init__( + self, + *, + gas_used: builtins.int = ..., + gas_used_l1: builtins.int = ..., + gas_used_l2: builtins.int = ..., + intrinsic_gas: builtins.int = ..., + dimensions: global___GasesByDimension | None = ..., + adjusted_refund: builtins.int | None = ..., + root_is_precompile_adjustment: builtins.int | None = ..., + root_is_stylus_adjustment: builtins.int | None = ..., + failed: builtins.bool | None = ..., + transaction_reverted: builtins.bool | None = ..., + tx_hash: builtins.str = ..., + block_number: builtins.int = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_adjusted_refund", b"_adjusted_refund", "_failed", b"_failed", "_root_is_precompile_adjustment", b"_root_is_precompile_adjustment", "_root_is_stylus_adjustment", b"_root_is_stylus_adjustment", "_transaction_reverted", b"_transaction_reverted", "adjusted_refund", b"adjusted_refund", "dimensions", b"dimensions", "failed", b"failed", "root_is_precompile_adjustment", b"root_is_precompile_adjustment", "root_is_stylus_adjustment", b"root_is_stylus_adjustment", "transaction_reverted", b"transaction_reverted"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_adjusted_refund", b"_adjusted_refund", "_failed", b"_failed", "_root_is_precompile_adjustment", b"_root_is_precompile_adjustment", "_root_is_stylus_adjustment", b"_root_is_stylus_adjustment", "_transaction_reverted", b"_transaction_reverted", "adjusted_refund", b"adjusted_refund", "block_number", b"block_number", "dimensions", b"dimensions", "failed", b"failed", "gas_used", b"gas_used", "gas_used_l1", b"gas_used_l1", "gas_used_l2", b"gas_used_l2", "intrinsic_gas", b"intrinsic_gas", "root_is_precompile_adjustment", b"root_is_precompile_adjustment", "root_is_stylus_adjustment", b"root_is_stylus_adjustment", "transaction_reverted", b"transaction_reverted", "tx_hash", b"tx_hash"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_adjusted_refund", b"_adjusted_refund"]) -> typing.Literal["adjusted_refund"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_failed", b"_failed"]) -> typing.Literal["failed"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_root_is_precompile_adjustment", b"_root_is_precompile_adjustment"]) -> typing.Literal["root_is_precompile_adjustment"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_root_is_stylus_adjustment", b"_root_is_stylus_adjustment"]) -> typing.Literal["root_is_stylus_adjustment"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_transaction_reverted", b"_transaction_reverted"]) -> typing.Literal["transaction_reverted"] | None: ... + +global___TxGasDimensionResult = TxGasDimensionResult + +@typing.final +class TxGasDimensionResultBatch(google.protobuf.message.Message): + """batch results to disk for performance""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESULTS_FIELD_NUMBER: builtins.int + @property + def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TxGasDimensionResult]: ... + def __init__( + self, + *, + results: collections.abc.Iterable[global___TxGasDimensionResult] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["results", b"results"]) -> None: ... + +global___TxGasDimensionResultBatch = TxGasDimensionResultBatch + +@typing.final +class BlockInfo(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TIMESTAMP_FIELD_NUMBER: builtins.int + INSERT_DURATION_NANOSECONDS_FIELD_NUMBER: builtins.int + BASE_FEE_FIELD_NUMBER: builtins.int + BLOCK_NUMBER_FIELD_NUMBER: builtins.int + timestamp: builtins.int + """the timestamp of the block""" + insert_duration_nanoseconds: builtins.int + """how long it took to insert the block""" + base_fee: builtins.int + """block base fee""" + block_number: builtins.int + """the number of the block""" + def __init__( + self, + *, + timestamp: builtins.int = ..., + insert_duration_nanoseconds: builtins.int = ..., + base_fee: builtins.int = ..., + block_number: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["base_fee", b"base_fee", "block_number", b"block_number", "insert_duration_nanoseconds", b"insert_duration_nanoseconds", "timestamp", b"timestamp"]) -> None: ... + +global___BlockInfo = BlockInfo + +@typing.final +class BlockInfoBatch(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + BLOCKS_FIELD_NUMBER: builtins.int + @property + def blocks(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BlockInfo]: ... + def __init__( + self, + *, + blocks: collections.abc.Iterable[global___BlockInfo] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["blocks", b"blocks"]) -> None: ... + +global___BlockInfoBatch = BlockInfoBatch diff --git a/eth/tracers/native/proto/get_latest.py b/eth/tracers/native/proto/get_latest.py new file mode 100644 index 0000000000..3733c5b997 --- /dev/null +++ b/eth/tracers/native/proto/get_latest.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Script to get the latest transaction hash from protobuf files in the data directory structure. + +This script finds the most recent .pb file and extracts the latest transaction hash from it, +assuming the files are organized in a way that allows finding the latest without parsing all files. + +Usage: + python get_latest.py [data_directory_path] +""" + +import os +import sys +import glob +from pathlib import Path + +# Import the generated protobuf classes +try: + from gas_dimension_pb2 import TxGasDimensionResultBatch +except ImportError: + print("Error: Could not import gas_dimension_pb2 module.") + print("Make sure the protobuf file is in the same directory as this script.") + print("You may need to run: pip install protobuf") + sys.exit(1) + + +def get_latest_transaction_hash(data_dir): + """ + Get the latest transaction hash from the most recent .pb file. + + Args: + data_dir (str): Path to the data directory + + Returns: + str: The latest transaction hash, or None if no transactions found + """ + if not os.path.exists(data_dir): + print(f"Error: Data directory '{data_dir}' does not exist.") + return None + + # Find all .pb files in the data directory structure + pb_files = [] + for root, dirs, files in os.walk(data_dir): + # Skip blocks/ and errors/ directories + dirs[:] = [d for d in dirs if d not in ['blocks', 'errors']] + + # Find all .pb files in current directory + for file in files: + if file.endswith('.pb'): + file_path = os.path.join(root, file) + pb_files.append(file_path) + + if not pb_files: + print("No .pb files found in the data directory.") + return None + + # Sort files by modification time to get the most recent + pb_files.sort(key=lambda x: os.path.getmtime(x), reverse=True) + latest_file = pb_files[0] + + print(f"Reading latest file: {latest_file}") + + try: + with open(latest_file, 'rb') as f: + batch = TxGasDimensionResultBatch() + batch.ParseFromString(f.read()) + + if not batch.results: + print("No transactions found in the latest file.") + return None + + # Get the last transaction from the batch + latest_result = batch.results[-1] + + if hasattr(latest_result, 'tx_hash') and latest_result.tx_hash: + return latest_result.tx_hash + else: + print("No transaction hash found in the latest transaction.") + return None + + except Exception as e: + print(f"Error reading file {latest_file}: {e}") + return None + + +def main(): + """Main function to get the latest transaction hash.""" + # Get data directory from command line argument or use default + if len(sys.argv) > 1: + data_dir = sys.argv[1] + else: + data_dir = "data" + + print(f"Getting latest transaction from directory: {data_dir}") + + latest_tx_hash = get_latest_transaction_hash(data_dir) + + if latest_tx_hash: + print(f"Latest transaction hash: {latest_tx_hash}") + else: + print("No transaction hash found.") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/eth/tracers/native/tx_gas_dimension_by_opcode.go b/eth/tracers/native/tx_gas_dimension.go similarity index 60% rename from eth/tracers/native/tx_gas_dimension_by_opcode.go rename to eth/tracers/native/tx_gas_dimension.go index fcd02b27dd..ca58d4c513 100644 --- a/eth/tracers/native/tx_gas_dimension_by_opcode.go +++ b/eth/tracers/native/tx_gas_dimension.go @@ -2,31 +2,32 @@ package native import ( "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/tracers/native/proto" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/params" - protobuf "google.golang.org/protobuf/proto" ) // initializer for the tracer func init() { - tracers.DefaultDirectory.Register("txGasDimensionByOpcode", NewTxGasDimensionByOpcodeTracer, false) + tracers.DefaultDirectory.Register("txGasDimension", NewTxGasDimensionTracer, false) } // gasDimensionTracer struct -type TxGasDimensionByOpcodeTracer struct { +type TxGasDimensionTracer struct { *BaseGasDimensionTracer - OpcodeToDimensions map[vm.OpCode]GasesByDimension + Dimensions GasesByDimension } // 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 NewTxGasDimensionByOpcodeTracer( +func NewTxGasDimensionTracer( _ *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig, @@ -35,9 +36,9 @@ func NewTxGasDimensionByOpcodeTracer( if err != nil { return nil, err } - t := &TxGasDimensionByOpcodeTracer{ + t := &TxGasDimensionTracer{ BaseGasDimensionTracer: baseGasDimensionTracer, - OpcodeToDimensions: make(map[vm.OpCode]GasesByDimension), + Dimensions: ZeroGasesByDimension(), } return &tracers.Tracer{ @@ -57,7 +58,7 @@ func NewTxGasDimensionByOpcodeTracer( // ############################################################################ // hook into each opcode execution -func (t *TxGasDimensionByOpcodeTracer) OnOpcode( +func (t *TxGasDimensionTracer) OnOpcode( pc uint64, op byte, gas, cost uint64, @@ -80,17 +81,13 @@ func (t *TxGasDimensionByOpcodeTracer) OnOpcode( // track the execution gas of all opcodes (but not the opcodes that do calls) t.AddToRootExecutionGasAccumulated(gasesByDimension.OneDimensionalGasCost) - // update the aggregrate map for this opcode - accumulatedDimensions := t.OpcodeToDimensions[opcode] - - accumulatedDimensions.OneDimensionalGasCost += gasesByDimension.OneDimensionalGasCost - accumulatedDimensions.Computation += gasesByDimension.Computation - accumulatedDimensions.StateAccess += gasesByDimension.StateAccess - accumulatedDimensions.StateGrowth += gasesByDimension.StateGrowth - accumulatedDimensions.HistoryGrowth += gasesByDimension.HistoryGrowth - accumulatedDimensions.StateGrowthRefund += gasesByDimension.StateGrowthRefund - - t.OpcodeToDimensions[opcode] = accumulatedDimensions + // update the dimensions for this opcode + t.Dimensions.OneDimensionalGasCost += gasesByDimension.OneDimensionalGasCost + t.Dimensions.Computation += gasesByDimension.Computation + t.Dimensions.StateAccess += gasesByDimension.StateAccess + t.Dimensions.StateGrowth += gasesByDimension.StateGrowth + t.Dimensions.HistoryGrowth += gasesByDimension.HistoryGrowth + t.Dimensions.StateGrowthRefund += gasesByDimension.StateGrowthRefund // if the opcode returns from the call stack depth, or // if this is an opcode immediately after a call that did not increase the stack depth @@ -98,7 +95,7 @@ func (t *TxGasDimensionByOpcodeTracer) OnOpcode( // call the appropriate finishX function to write the gas dimensions // for the call that increased the stack depth in the past if depth < t.depth { - interrupted, totalGasUsedByCall, stackInfo, finishGasesByDimension := t.callFinishFunction(pc, depth, gas) + interrupted, totalGasUsedByCall, _, finishGasesByDimension := t.callFinishFunction(pc, depth, gas) if interrupted { return } @@ -108,28 +105,44 @@ func (t *TxGasDimensionByOpcodeTracer) OnOpcode( t.AddToRootExecutionGasAccumulated(totalGasUsedByCall) } - accumulatedDimensionsCall := t.OpcodeToDimensions[stackInfo.GasDimensionInfo.Op] - - accumulatedDimensionsCall.OneDimensionalGasCost += finishGasesByDimension.OneDimensionalGasCost - accumulatedDimensionsCall.Computation += finishGasesByDimension.Computation - accumulatedDimensionsCall.StateAccess += finishGasesByDimension.StateAccess - accumulatedDimensionsCall.StateGrowth += finishGasesByDimension.StateGrowth - accumulatedDimensionsCall.HistoryGrowth += finishGasesByDimension.HistoryGrowth - accumulatedDimensionsCall.StateGrowthRefund += finishGasesByDimension.StateGrowthRefund - t.OpcodeToDimensions[stackInfo.GasDimensionInfo.Op] = accumulatedDimensionsCall + t.Dimensions.OneDimensionalGasCost += finishGasesByDimension.OneDimensionalGasCost + t.Dimensions.Computation += finishGasesByDimension.Computation + t.Dimensions.StateAccess += finishGasesByDimension.StateAccess + t.Dimensions.StateGrowth += finishGasesByDimension.StateGrowth + t.Dimensions.HistoryGrowth += finishGasesByDimension.HistoryGrowth + t.Dimensions.StateGrowthRefund += finishGasesByDimension.StateGrowthRefund t.depth -= 1 t.updateCallChildExecutionCost(totalGasUsedByCall) } t.updateCallChildExecutionCost(gasesByDimension.OneDimensionalGasCost) } - addresses, slots := t.env.StateDB.GetAccessList() - t.updatePrevAccessList(addresses, slots) + // update the access list for this opcode AFTER all the other logic is done + t.accessListTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + // sanity check + addresses, _ := t.env.StateDB.GetAccessList() + missingAddresses := make([]common.Address, 0) + for addr := range addresses { + if !t.accessListTracer.HasAddress(addr) { + missingAddresses = append(missingAddresses, addr) + } + if len(missingAddresses) > 0 { + t.interrupt.Store(true) + t.reason = fmt.Errorf( + "pc %d op %s depth %d address not in access list: %v", + pc, + vm.OpCode(op).String(), + depth, + missingAddresses, + ) + return + } + } } // if there is an error in the evm, e.g. invalid jump, // out of gas, max call depth exceeded, etc, this hook is called -func (t *TxGasDimensionByOpcodeTracer) OnFault( +func (t *TxGasDimensionTracer) OnFault( pc uint64, op byte, gas, cost uint64, @@ -149,12 +162,9 @@ func (t *TxGasDimensionByOpcodeTracer) OnFault( // because if an error happens inside a call, the gas for the call opcode // will capture the excess gas consumed by the error/revert if depth == 1 { - opcode := vm.OpCode(op) - accumulatedDimensions := t.OpcodeToDimensions[opcode] gasAfterOpcode := gas - cost // don't double charge the cost of the opcode itself - accumulatedDimensions.OneDimensionalGasCost += gasAfterOpcode - accumulatedDimensions.Computation += gasAfterOpcode - t.OpcodeToDimensions[opcode] = accumulatedDimensions + t.Dimensions.OneDimensionalGasCost += gasAfterOpcode + t.Dimensions.Computation += gasAfterOpcode } } @@ -163,33 +173,33 @@ func (t *TxGasDimensionByOpcodeTracer) OnFault( // ############################################################################ // Error returns the VM error captured by the trace. -func (t *TxGasDimensionByOpcodeTracer) Error() error { return t.err } +func (t *TxGasDimensionTracer) Error() error { return t.err } // ExecutionResult groups all dimension logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value -type TxGasDimensionByOpcodeExecutionResult struct { +type TxGasDimensionExecutionResult struct { BaseExecutionResult - Dimensions map[string]GasesByDimension `json:"dimensions"` + Dimensions GasesByDimension `json:"dimensions"` } // produce json result for output from tracer // this is what the end-user actually gets from the RPC endpoint -func (t *TxGasDimensionByOpcodeTracer) GetResult() (json.RawMessage, error) { +func (t *TxGasDimensionTracer) GetResult() (json.RawMessage, error) { baseExecutionResult, err := t.GetBaseExecutionResult() if err != nil { return nil, err } - return json.Marshal(&TxGasDimensionByOpcodeExecutionResult{ + return json.Marshal(&TxGasDimensionExecutionResult{ BaseExecutionResult: baseExecutionResult, - Dimensions: t.GetOpcodeDimensionSummary(), + Dimensions: t.Dimensions, }) } // produce protobuf serialized result // for storing to file in compact format -func (t *TxGasDimensionByOpcodeTracer) GetProtobufResult() ([]byte, error) { +func (t *TxGasDimensionTracer) GetProtobufResult() (*proto.TxGasDimensionResult, error) { baseExecutionResult, err := t.GetBaseExecutionResult() if err != nil { return nil, err @@ -224,7 +234,7 @@ func (t *TxGasDimensionByOpcodeTracer) GetProtobufResult() ([]byte, error) { transactionReverted = &trueBool } - executionResult := &proto.TxGasDimensionByOpcodeExecutionResult{ + executionResult := &proto.TxGasDimensionResult{ GasUsed: baseExecutionResult.GasUsed, GasUsedL1: baseExecutionResult.GasUsedForL1, GasUsedL2: baseExecutionResult.GasUsedForL2, @@ -234,32 +244,16 @@ func (t *TxGasDimensionByOpcodeTracer) GetProtobufResult() ([]byte, error) { RootIsStylusAdjustment: rootIsStylusAdjustment, Failed: failed, TransactionReverted: transactionReverted, - Dimensions: make(map[uint32]*proto.GasesByDimension), - TxHash: baseExecutionResult.TxHash, - BlockTimestamp: baseExecutionResult.BlockTimestamp, - BlockNumber: baseExecutionResult.BlockNumber.String(), - } - - for opcode, dimensions := range t.OpcodeToDimensions { - executionResult.Dimensions[uint32(opcode)] = &proto.GasesByDimension{ - OneDimensionalGasCost: dimensions.OneDimensionalGasCost, - Computation: dimensions.Computation, - StateAccess: dimensions.StateAccess, - StateGrowth: dimensions.StateGrowth, - HistoryGrowth: dimensions.HistoryGrowth, - StateGrowthRefund: dimensions.StateGrowthRefund, - ChildExecutionCost: dimensions.ChildExecutionCost, - } - } - - return protobuf.Marshal(executionResult) -} - -// stringify opcodes for dimension log output -func (t *TxGasDimensionByOpcodeTracer) GetOpcodeDimensionSummary() map[string]GasesByDimension { - summary := make(map[string]GasesByDimension) - for opcode, dimensions := range t.OpcodeToDimensions { - summary[opcode.String()] = dimensions + Dimensions: &proto.GasesByDimension{ + OneDimensionalGasCost: t.Dimensions.OneDimensionalGasCost, + Computation: t.Dimensions.Computation, + StateAccess: t.Dimensions.StateAccess, + StateGrowth: t.Dimensions.StateGrowth, + HistoryGrowth: t.Dimensions.HistoryGrowth, + StateGrowthRefund: t.Dimensions.StateGrowthRefund, + }, + TxHash: baseExecutionResult.TxHash, + BlockNumber: baseExecutionResult.BlockNumber.Uint64(), } - return summary + return executionResult, nil } diff --git a/eth/tracers/native/tx_gas_dimension_logger.go b/eth/tracers/native/tx_gas_dimension_logger.go index f7864ccdef..56a67095f4 100644 --- a/eth/tracers/native/tx_gas_dimension_logger.go +++ b/eth/tracers/native/tx_gas_dimension_logger.go @@ -180,8 +180,8 @@ func (t *TxGasDimensionLogger) OnOpcode( t.updateCallChildExecutionCost(gasesByDimension.OneDimensionalGasCost) } - addresses, slots := t.env.StateDB.GetAccessList() - t.updatePrevAccessList(addresses, slots) + // update the access list for this opcode AFTER all the other logic is done + t.accessListTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) } // if there is an error in the evm, e.g. invalid jump,