Skip to content

Commit

Permalink
migrate sentio tracer to geth
Browse files Browse the repository at this point in the history
support trace call many in geth

migrate override and gas changes (#1)

migrate memory compression (ava-labs#2)

add more information for root trace (ava-labs#3)

correct handle call with value (ava-labs#4)

* correct handle call with value

* set transfer value to zero if can't transfer

Add Mapping keys to post account (ava-labs#5)

fix when tracer failed before start (ava-labs#6)

unlimited gas for debug_traceCallMany (ava-labs#7)

support multiple txs in tracecallmany

rpc client should keep result while return error

be able to return partial results

migrate tracer changes (ava-labs#8)

export meq field (ava-labs#9)

ignore init code size limit (ava-labs#10)

code address field (ava-labs#11)

patch with avacoreeth

emit output for revert (ava-labs#14)
  • Loading branch information
zfy0701 committed Mar 26, 2024
1 parent 953ba9e commit 744490c
Show file tree
Hide file tree
Showing 13 changed files with 1,327 additions and 24 deletions.
18 changes: 10 additions & 8 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,22 +442,24 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules)
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
if !st.evm.Config.IgnoreGas {
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules)
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
}
st.gasRemaining -= gas
}
st.gasRemaining -= gas

// Check clause 6
if msg.Value.Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From, msg.Value) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
}

// Check whether the init code size has been exceeded.
if rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
if !st.evm.Config.IgnoreCodeSizeLimit && rules.IsDurango && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
return nil, fmt.Errorf("%w: code size %v limit %v", vmerrs.ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
}

Expand Down
25 changes: 20 additions & 5 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,16 @@ func (c *codeAndHash) Hash() common.Hash {
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
// Depth check execution. Fail if we're trying to execute above the
// limit.
if evm.Config.CreateAddressOverride != nil {
address = *evm.Config.CreateAddressOverride
}
if evm.Config.CreationCodeOverrides != nil {
if code, ok := evm.Config.CreationCodeOverrides[address]; ok {
codeAndHash.code = code
codeAndHash.hash = common.Hash{}
_ = codeAndHash.Hash()
}
}
if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, vmerrs.ErrDepth
}
Expand All @@ -616,10 +626,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsApricotPhase2 {
evm.StateDB.AddAddressToAccessList(address)
}
// Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision
if evm.Config.CreateAddressOverride == nil {
// Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision
}
}
// Create a new account on the state
snapshot := evm.StateDB.Snapshot()
Expand All @@ -645,7 +657,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
ret, err := evm.interpreter.Run(contract, nil, false)

// Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
if err == nil && !evm.Config.IgnoreCodeSizeLimit && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
err = vmerrs.ErrMaxCodeSizeExceeded
}

Expand All @@ -660,6 +672,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// by the error checking condition below.
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if evm.Config.IgnoreGas {
createDataGas = 0
}
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(address, ret)
} else {
Expand Down
6 changes: 4 additions & 2 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
if interpreter.evm.chainRules.IsEIP150 {
if !interpreter.evm.Config.IgnoreGas && interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
// reuse size int for stackvalue
Expand Down Expand Up @@ -648,7 +648,9 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
gas = scope.Contract.Gas
)
// Apply EIP150
gas -= gas / 64
if !interpreter.evm.Config.IgnoreGas {
gas -= gas / 64
}
scope.Contract.UseGas(gas)
// reuse size int for stackvalue
stackvalue := size
Expand Down
22 changes: 22 additions & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ package vm
import (
"github.com/ava-labs/coreth/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
Expand All @@ -45,6 +46,11 @@ type Config struct {
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled

CreationCodeOverrides map[common.Address]hexutil.Bytes
CreateAddressOverride *common.Address
IgnoreGas bool
IgnoreCodeSizeLimit bool
}

// ScopeContext contains the things that are per-call, such as stack and memory,
Expand Down Expand Up @@ -111,6 +117,22 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
}
}
evm.Config.ExtraEips = extraEips
if evm.Config.IgnoreGas {
table = copyJumpTable(table)
for i, op := range table {
opCode := OpCode(i)
// retain call costs to prevent call stack from going too deep
// some contracts use a loop to burn gas
// if all codes in the loop have zero cost, it will run forever
if opCode == CALL || opCode == STATICCALL || opCode == CALLCODE || opCode == DELEGATECALL || opCode == GAS {
continue
}
op.constantGas = 0
op.dynamicGas = func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) {
return 0, nil
}
}
}
return &EVMInterpreter{evm: evm, table: table}
}

Expand Down
142 changes: 140 additions & 2 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,18 @@ type TraceConfig struct {
// Config specific to given tracer. Note struct logger
// config are historically embedded in main object.
TracerConfig json.RawMessage

StateOverrides *ethapi.StateOverride
IgnoreGas *bool
IgnoreCodeSizeLimit *bool
CreationCodeOverrides map[common.Address]hexutil.Bytes
CreateAddressOverride *common.Address
}

// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
TraceConfig
StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
}

Expand Down Expand Up @@ -906,6 +911,12 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index),
TxHash: hash,
}

if config != nil {
if err := config.StateOverrides.Apply(statedb); err != nil {
return nil, err
}
}
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
}

Expand Down Expand Up @@ -997,7 +1008,21 @@ func (api *baseAPI) traceTx(ctx context.Context, message *core.Message, txctx *C
return nil, err
}
}
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
vmConfig := vm.Config{
Tracer: tracer,
NoBaseFee: true,
}
if config != nil {
vmConfig.CreateAddressOverride = config.CreateAddressOverride
vmConfig.CreationCodeOverrides = config.CreationCodeOverrides
if config.IgnoreCodeSizeLimit != nil {
vmConfig.IgnoreCodeSizeLimit = *config.IgnoreCodeSizeLimit
}
if config.IgnoreGas != nil {
vmConfig.IgnoreGas = *config.IgnoreGas
}
}
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vmConfig)

// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {
Expand Down Expand Up @@ -1097,3 +1122,116 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig)

return copy, canon
}

type Bundle struct {
Transactions []*ethapi.TransactionArgs `json:"transactions"`
BlockOverride ethapi.BlockOverrides `json:"blockOverride"`
}

type StateContext struct {
BlockNumber *rpc.BlockNumberOrHash `json:"blockNumber"`
TransactionIndex int `json:"transactionIndex"`
}

type FailedTrace struct {
Failed string `json:"failed,omitempty"`
}

func (api *API) TraceCallMany(ctx context.Context, bundles []*Bundle, simulateContext *StateContext, config *TraceCallConfig) (interface{}, error) {
if len(bundles) == 0 {
return nil, errors.New("empty bundles")
}
var result []interface{}
for _, bundle := range bundles {
r, err := api.traceBundle(ctx, bundle, simulateContext, config)
if err != nil {
if r != nil {
// return partial results
r = append(r, &FailedTrace{Failed: err.Error()})
result = append(result, r)
return result, nil
}
return nil, err
}
result = append(result, r)
}
return result, nil
}

func (api *API) traceBundle(ctx context.Context, bundle *Bundle, simulateContext *StateContext, config *TraceCallConfig) ([]interface{}, error) {
var result []interface{}
// Try to retrieve the specified block
var (
err error
block *types.Block
)
if hash, ok := simulateContext.BlockNumber.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
} else if number, ok := simulateContext.BlockNumber.Number(); ok {
if number == rpc.PendingBlockNumber {
// We don't have access to the miner here. For tracing 'future' transactions,
// it can be done with block- and state-overrides instead, which offers
// more flexibility and stability than trying to trace on 'pending', since
// the contents of 'pending' is unstable and probably not a true representation
// of what the next actual block is likely to contain.
return nil, errors.New("tracing on top of pending is not supported")
}
block, err = api.blockByNumber(ctx, number)
} else {
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
if err != nil {
return nil, err
}
// try to recompute the state
reexec := defaultTraceReexec
if config != nil && config.Reexec != nil {
reexec = *config.Reexec
}
is158 := api.backend.ChainConfig().IsEIP158(block.Number())

if err != nil {
return nil, err
}
_, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, simulateContext.TransactionIndex, reexec)
if err != nil {
return nil, err
}
defer release()

// Apply the customization rules if required.
if config != nil {
if err := config.StateOverrides.Apply(statedb); err != nil {
return nil, err
}
config.BlockOverrides.Apply(&vmctx)
}
// Execute the trace
for idx, args := range bundle.Transactions {
if args.Gas == nil {
gasCap := api.backend.RPCGasCap()
args.Gas = (*hexutil.Uint64)(&gasCap)
}
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
if err != nil {
return result, err
}

var traceConfig *TraceConfig
if config != nil {
traceConfig = &config.TraceConfig
}
txctx := &Context{
BlockHash: block.Hash(),
BlockNumber: block.Number(),
TxIndex: simulateContext.TransactionIndex + idx,
}
r, err := api.traceTx(ctx, msg, txctx, vmctx, statedb, traceConfig)
if err != nil {
return result, err
}
result = append(result, r)
statedb.Finalise(is158)
}
return result, nil
}
1 change: 1 addition & 0 deletions eth/tracers/internal/tracetest/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// Force-load native and js packages, to trigger registration
_ "github.com/ava-labs/coreth/eth/tracers/js"
_ "github.com/ava-labs/coreth/eth/tracers/native"
_ "github.com/ava-labs/coreth/eth/tracers/sentio"
)

// To generate a new callTracer test, copy paste the makeTest method below into
Expand Down
6 changes: 6 additions & 0 deletions eth/tracers/logger/gen_structlog.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 744490c

Please sign in to comment.