Skip to content

Commit

Permalink
add debug info for invalid gas used
Browse files Browse the repository at this point in the history
  • Loading branch information
andyzhang2023 committed Oct 12, 2024
1 parent f3bc8ce commit 4b86635
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 13 deletions.
3 changes: 2 additions & 1 deletion cmd/evm/internal/t8ntool/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -134,7 +135,7 @@ func Transaction(ctx *cli.Context) error {
}
// Check intrinsic gas
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil {
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0), &state.GasSummary{}); err != nil {
r.Error = err
results = append(results, r)
continue
Expand Down
3 changes: 2 additions & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -83,7 +84,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false, false)
gas, _ := IntrinsicGas(data, nil, false, false, false, false, &state.GasSummary{})
signer := gen.Signer()
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
Expand Down
16 changes: 16 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
Expand Down Expand Up @@ -160,11 +161,26 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
return nil
}

func debugGsSummary(gasSummaries map[int]*state.GasSummary, block *types.Block) {
for txIndex, s := range gasSummaries {
if s == nil {
log.Error("[DEBUG invalid gas used], gs is nil", "blockNumber", block.NumberU64(), "txIndex", txIndex)
fmt.Printf("[DEBUG invalid gas used], block:%d, txIndex:%d, is nil\n", block.NumberU64(), txIndex)
} else {
log.Error("[DEBUG invalid gas used], gs collected", "blockNumber", block.NumberU64(), "txIndex", txIndex, "gasSummary", s.Debug())
fmt.Printf("[DEBUG invalid gas used], block:%d, txIndex:%d, gasSummary:%s\n", block.NumberU64(), txIndex, s.Debug())
}
}
}

// ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error {
header := block.Header()
gasSummaries := statedb.PopGasSummaries()
debugGsSummary(gasSummaries, block)
if block.GasUsed() != usedGas {
//debugGsSummary(gasSummaries, block)
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}

Expand Down
2 changes: 2 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1897,7 +1897,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)

// Process block using the parent state as reference point
pstart = time.Now()
fmt.Printf("[DEBUG invalid gas used] start block %d, gas used %d\n", block.NumberU64(), block.GasUsed())
receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig)
fmt.Printf("[DEBUG invalid gas used] end block %d, gas used %d\n", block.NumberU64(), usedGas)
if err != nil {
bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true)
Expand Down
39 changes: 39 additions & 0 deletions core/state/gas_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package state

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
)

// for debug
type GasSummary struct {
TxHash common.Hash
GasLimit uint64
IntrinsicGas struct {
BasicGas uint64
InputGas struct {
NonZeroGas uint64
ZeroGas uint64
InitCodeGas uint64
}
AccessListGas struct {
AccessListGas uint64
StorageKeyGas uint64
}
}
EvmCallGas uint64
Refunds struct {
IsLondon bool
Refunds uint64
StateRefunds uint64
GasUsed uint64
}
GasUsed uint64
}

func (gs *GasSummary) Debug() string {
intri := fmt.Sprintf("BasicGas:%d, NonZeroGas:%d, ZeroGas:%d, InitCodeGas:%d, Acceslist:%d, Storagekey:%d", gs.IntrinsicGas.BasicGas, gs.IntrinsicGas.InputGas.NonZeroGas, gs.IntrinsicGas.InputGas.ZeroGas, gs.IntrinsicGas.InputGas.InitCodeGas, gs.IntrinsicGas.AccessListGas.AccessListGas, gs.IntrinsicGas.AccessListGas.StorageKeyGas)
refunds := fmt.Sprintf("IsLondon:%t, Refunds:%d, StateRefunds:%d, GasUsed:%d", gs.Refunds.IsLondon, gs.Refunds.Refunds, gs.Refunds.StateRefunds, gs.Refunds.GasUsed)
return fmt.Sprintf("TxHash:%s, GasLimit:%d, IntrinsicGas:{%s}, EvmCallGas:%d, Refunds:{%s}, GasUsed:%d", gs.TxHash.Hex(), gs.GasLimit, intri, gs.EvmCallGas, refunds, gs.GasUsed)
}
34 changes: 27 additions & 7 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@ type revision struct {
// must be created with new root and updated database for accessing post-
// commit states.
type StateDB struct {
db Database
prefetcher *triePrefetcher
trie Trie
noTrie bool
hasher crypto.KeccakState
snaps *snapshot.Tree // Nil if snapshot is not available
snap snapshot.Snapshot // Nil if snapshot is not available
gasSummaries map[int]*GasSummary
db Database
prefetcher *triePrefetcher
trie Trie
noTrie bool
hasher crypto.KeccakState
snaps *snapshot.Tree // Nil if snapshot is not available
snap snapshot.Snapshot // Nil if snapshot is not available

// originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called.
Expand Down Expand Up @@ -250,6 +251,25 @@ func (s *StateDB) MarkFullProcessed() {
s.fullProcessed = true
}

// debug gas
func (s *StateDB) CollectGasSummary(txIndex int, gs *GasSummary) {
if s.gasSummaries == nil {
s.gasSummaries = make(map[int]*GasSummary)
}
s.gasSummaries[txIndex] = gs
}

func (s *StateDB) PopGasSummaries() map[int]*GasSummary {
defer func() {
s.gasSummaries = nil
}()
gs := s.gasSummaries
if gs == nil {
gs = make(map[int]*GasSummary)
}
return gs
}

// setError remembers the first non-nil error it is called with.
func (s *StateDB) setError(err error) {
if s.dbErr == nil {
Expand Down
1 change: 1 addition & 0 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta

// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
statedb.CollectGasSummary(statedb.TxIndex(), result.GasSummary)
if err != nil {
return nil, err
}
Expand Down
24 changes: 22 additions & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
Expand All @@ -41,6 +42,7 @@ type ExecutionResult struct {
RefundedGas uint64 // Total gas refunded after execution
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
GasSummary *state.GasSummary
}

// Unwrap returns the internal evm error which allows us for further
Expand Down Expand Up @@ -71,14 +73,15 @@ func (result *ExecutionResult) Revert() []byte {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool, gs *state.GasSummary) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
gas = params.TxGasContractCreation
} else {
gas = params.TxGas
}
gs.IntrinsicGas.BasicGas = gas
dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional data
if dataLen > 0 {
Expand All @@ -98,24 +101,29 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
return 0, ErrGasUintOverflow
}
gas += nz * nonZeroGas
gs.IntrinsicGas.InputGas.NonZeroGas = nz * nonZeroGas

z := dataLen - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, ErrGasUintOverflow
}
gas += z * params.TxDataZeroGas
gs.IntrinsicGas.InputGas.ZeroGas = z * params.TxDataZeroGas

if isContractCreation && isEIP3860 {
lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
return 0, ErrGasUintOverflow
}
gas += lenWords * params.InitCodeWordGas
gs.IntrinsicGas.InputGas.InitCodeGas = lenWords * params.InitCodeWordGas
}
}
if accessList != nil {
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gs.IntrinsicGas.AccessListGas.AccessListGas = uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
gs.IntrinsicGas.AccessListGas.StorageKeyGas = uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
}
return gas, nil
}
Expand Down Expand Up @@ -474,10 +482,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
sender = vm.AccountRef(msg.From)
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
contractCreation = msg.To == nil
gs = &state.GasSummary{}
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, gs)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -510,13 +519,15 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
start := time.Now()
gs.EvmCallGas = st.gasRemaining
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value)
}
gs.EvmCallGas = gs.EvmCallGas - st.gasRemaining
DebugInnerExecutionDuration += time.Since(start)

// if deposit: skip refunds, skip tipping coinbase
Expand All @@ -532,26 +543,33 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
UsedGas: gasUsed,
Err: vmerr,
ReturnData: ret,
GasSummary: gs,
}, nil
}
// Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice
// is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually
// changing the sender's balance
var gasRefund uint64
gs.Refunds.IsLondon = rules.IsLondon
gs.Refunds.GasUsed = st.gasUsed()
gs.Refunds.StateRefunds = st.state.GetRefund()
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2
gasRefund = st.refundGas(params.RefundQuotient)
} else {
// After EIP-3529: refunds are capped to gasUsed / 5
gasRefund = st.refundGas(params.RefundQuotientEIP3529)
}
gs.Refunds.Refunds = gasRefund
gs.GasUsed = st.gasUsed()
if st.msg.IsDepositTx && rules.IsOptimismRegolith {
// Skip coinbase payments for deposit tx in Regolith
return &ExecutionResult{
UsedGas: st.gasUsed(),
RefundedGas: gasRefund,
Err: vmerr,
ReturnData: ret,
GasSummary: gs,
}, nil
}
effectiveTip := msg.GasPrice
Expand Down Expand Up @@ -590,11 +608,13 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
}
}

gs.GasUsed = st.gasUsed()
return &ExecutionResult{
UsedGas: st.gasUsed(),
RefundedGas: gasRefund,
Err: vmerr,
ReturnData: ret,
GasSummary: gs,
}, nil
}

Expand Down
2 changes: 1 addition & 1 deletion core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time), &state.GasSummary{})
if err != nil {
Meter(GasUnitOverflow).Mark(1)
return err
Expand Down
3 changes: 3 additions & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package vm

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -188,6 +190,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
op = contract.GetOp(pc)
operation := in.table[op]
cost = operation.constantGas // For tracing
fmt.Printf("[DEBUG invalid gas used] from:%s, to:%s, op:%s, cost:%d\n", contract.caller.Address(), contract.self.Address(), op.String(), cost)
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
Expand Down
3 changes: 2 additions & 1 deletion tests/transaction_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -55,7 +56,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
return nil, nil, err
}
// Intrinsic gas
requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false)
requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false, &state.GasSummary{})
if err != nil {
return nil, nil, err
}
Expand Down

0 comments on commit 4b86635

Please sign in to comment.