mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 07:36:44 +00:00
add gas consumption hook
This commit is contained in:
parent
84d6432708
commit
ee791b2b20
20 changed files with 73 additions and 42 deletions
|
|
@ -348,6 +348,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
if st.gasRemaining < gas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
|
||||
}
|
||||
if t := st.evm.Config.Tracer; t != nil {
|
||||
t.OnGasConsumed(st.gasRemaining, gas)
|
||||
}
|
||||
st.gasRemaining -= gas
|
||||
|
||||
// Check clause 6
|
||||
|
|
|
|||
|
|
@ -159,10 +159,13 @@ func (c *Contract) Caller() common.Address {
|
|||
}
|
||||
|
||||
// UseGas attempts the use gas and subtracts it and returns true on success
|
||||
func (c *Contract) UseGas(gas uint64) (ok bool) {
|
||||
func (c *Contract) UseGas(gas uint64, logger EVMLogger) (ok bool) {
|
||||
if c.Gas < gas {
|
||||
return false
|
||||
}
|
||||
if logger != nil {
|
||||
logger.OnGasConsumed(c.Gas, gas)
|
||||
}
|
||||
c.Gas -= gas
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,11 +168,14 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
|
|||
// - the returned bytes,
|
||||
// - the _remaining_ gas,
|
||||
// - any error that occurred
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
||||
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger EVMLogger) (ret []byte, remainingGas uint64, err error) {
|
||||
gasCost := p.RequiredGas(input)
|
||||
if suppliedGas < gasCost {
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
if logger != nil {
|
||||
logger.OnGasConsumed(suppliedGas, gasCost)
|
||||
}
|
||||
suppliedGas -= gasCost
|
||||
output, err := p.Run(input)
|
||||
return output, suppliedGas, err
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
|
||||
if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil {
|
||||
t.Error(err)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||
|
|
@ -119,7 +119,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
|||
gas := p.RequiredGas(in) - 1
|
||||
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
_, _, err := RunPrecompiledContract(p, in, gas, nil)
|
||||
if err.Error() != "out of gas" {
|
||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(p, in, gas)
|
||||
_, _, err := RunPrecompiledContract(p, in, gas, nil)
|
||||
if err.Error() != test.ExpectedError {
|
||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
|||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
copy(data, in)
|
||||
res, _, err = RunPrecompiledContract(p, data, reqGas)
|
||||
res, _, err = RunPrecompiledContract(p, data, reqGas, nil)
|
||||
}
|
||||
bench.StopTimer()
|
||||
elapsed := uint64(time.Since(start))
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
}
|
||||
|
||||
if isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
|
|
@ -285,7 +285,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
|
|
@ -330,7 +330,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
|
|
@ -379,7 +379,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||
}
|
||||
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
|
||||
} else {
|
||||
// At this point, we use a copy of address. If we don't, the go compiler will
|
||||
// leak the 'contract' to the outer scope, and make allocation for 'contract'
|
||||
|
|
@ -480,7 +480,7 @@ 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 contract.UseGas(createDataGas) {
|
||||
if contract.UseGas(createDataGas, evm.Config.Tracer) {
|
||||
evm.StateDB.SetCode(address, ret)
|
||||
} else {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
|
|
@ -493,7 +493,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(contract.Gas)
|
||||
contract.UseGas(contract.Gas, evm.Config.Tracer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
|||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
|
||||
scope.Contract.UseGas(gas)
|
||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
var bigVal = big0
|
||||
if !value.IsZero() {
|
||||
|
|
@ -634,7 +634,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
|||
)
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
scope.Contract.UseGas(gas)
|
||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer)
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
} else if sLen > operation.maxStack {
|
||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
if !contract.UseGas(cost) {
|
||||
if !contract.UseGas(cost, in.evm.Config.Tracer) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if operation.dynamicGas != nil {
|
||||
|
|
@ -211,7 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
var dynamicCost uint64
|
||||
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
|
||||
cost += dynamicCost // for tracing
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
// Do tracing before memory expansion
|
||||
|
|
|
|||
|
|
@ -41,4 +41,6 @@ type EVMLogger interface {
|
|||
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
|
||||
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
|
||||
CaptureKeccakPreimage(hash common.Hash, data []byte)
|
||||
// Misc
|
||||
OnGasConsumed(gas, amount uint64)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
|||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// Charge the remaining difference here already, to correctly calculate available
|
||||
// gas for call
|
||||
if !contract.UseGas(coldCost) {
|
||||
if !contract.UseGas(coldCost, evm.Config.Tracer) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
|
|||
// jsTracer is an implementation of the Tracer interface which evaluates
|
||||
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
|
||||
type jsTracer struct {
|
||||
tracers.NoopTracer
|
||||
|
||||
vm *goja.Runtime
|
||||
env *vm.EVM
|
||||
toBig toBigFn // Converts a hex string into a JS bigint
|
||||
|
|
@ -286,9 +288,6 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
|||
}
|
||||
}
|
||||
|
||||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (t *jsTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
t.ctx["output"] = t.vm.ToValue(output)
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64,
|
|||
|
||||
func (*AccessListTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
func (*AccessListTracer) OnGasConsumed(gas, amount uint64) {}
|
||||
|
||||
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s
|
|||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (l *StructLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
func (l *StructLogger) OnGasConsumed(gas, amount uint64) {}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
l.output = output
|
||||
|
|
@ -389,6 +391,8 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope
|
|||
|
||||
func (t *mdLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
func (t *mdLogger) OnGasConsumed(gas, amount uint64) {}
|
||||
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
|
|||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (l *JSONLogger) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
func (l *JSONLogger) OnGasConsumed(gas, amount uint64) {}
|
||||
|
||||
// CaptureEnd is triggered at end of execution.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
type endLog struct {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func init() {
|
|||
// 0xc281d19e-0: 1
|
||||
// }
|
||||
type fourByteTracer struct {
|
||||
noopTracer
|
||||
tracers.NoopTracer
|
||||
ids map[string]int // ids aggregates the 4byte ids found
|
||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||
reason error // Textual reason for the interruption
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ type callFrameMarshaling struct {
|
|||
}
|
||||
|
||||
type callTracer struct {
|
||||
noopTracer
|
||||
tracers.NoopTracer
|
||||
callstack []callFrame
|
||||
config callTracerConfig
|
||||
gasLimit uint64
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ type flatCallResultMarshaling struct {
|
|||
// flatCallTracer reports call frame information of a tx in a flat format, i.e.
|
||||
// as opposed to the nested format of `callTracer`.
|
||||
type flatCallTracer struct {
|
||||
noopTracer
|
||||
tracers.NoopTracer
|
||||
tracer *callTracer
|
||||
config flatCallTracerConfig
|
||||
ctx *tracers.Context // Holds tracer context data
|
||||
|
|
|
|||
|
|
@ -93,6 +93,13 @@ func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// CaptureGasConsumed is called when gas is consumed.
|
||||
func (t *muxTracer) OnGasConsumed(gas, amount uint64) {
|
||||
for _, t := range t.tracers {
|
||||
t.OnGasConsumed(gas, amount)
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
for _, t := range t.tracers {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ type accountMarshaling struct {
|
|||
}
|
||||
|
||||
type prestateTracer struct {
|
||||
noopTracer
|
||||
tracers.NoopTracer
|
||||
env *vm.EVM
|
||||
pre state
|
||||
post state
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package native
|
||||
package tracers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -22,59 +22,61 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
|
||||
DefaultDirectory.Register("noopTracer", newNoopTracer, false)
|
||||
}
|
||||
|
||||
// noopTracer is a go implementation of the Tracer interface which
|
||||
// NoopTracer is a go implementation of the Tracer interface which
|
||||
// performs no action. It's mostly useful for testing purposes.
|
||||
type noopTracer struct{}
|
||||
type NoopTracer struct{}
|
||||
|
||||
// newNoopTracer returns a new noop tracer.
|
||||
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
|
||||
return &noopTracer{}, nil
|
||||
func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) {
|
||||
return &NoopTracer{}, nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *NoopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
|
||||
}
|
||||
|
||||
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
|
||||
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault.
|
||||
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureKeccakPreimage is called during the KECCAK256 opcode.
|
||||
func (t *noopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {}
|
||||
|
||||
// OnGasConsumed is called when gas is consumed.
|
||||
func (t *NoopTracer) OnGasConsumed(gas, amount uint64) {}
|
||||
|
||||
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
|
||||
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureExit is called when EVM exits a scope, even if the scope didn't
|
||||
// execute any code.
|
||||
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
|
||||
}
|
||||
|
||||
func (*noopTracer) CaptureTxStart(gasLimit uint64) {}
|
||||
func (*NoopTracer) CaptureTxStart(gasLimit uint64) {}
|
||||
|
||||
func (*noopTracer) CaptureTxEnd(restGas uint64) {}
|
||||
func (*NoopTracer) CaptureTxEnd(restGas uint64) {}
|
||||
|
||||
// GetResult returns an empty json object.
|
||||
func (t *noopTracer) GetResult() (json.RawMessage, error) {
|
||||
func (t *NoopTracer) GetResult() (json.RawMessage, error) {
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
|
||||
// Stop terminates execution of the tracer at the first opportune moment.
|
||||
func (t *noopTracer) Stop(err error) {
|
||||
func (t *NoopTracer) Stop(err error) {
|
||||
}
|
||||
|
|
@ -93,3 +93,7 @@ func (p *Printer) OnLog(l *types.Log) {
|
|||
func (p *Printer) OnNewAccount(a common.Address) {
|
||||
fmt.Printf("OnNewAccount: a=%v\n", a)
|
||||
}
|
||||
|
||||
func (p *Printer) OnGasConsumed(gas, amount uint64) {
|
||||
fmt.Printf("OnGasConsumed: gas=%v, amount=%v\n", gas, amount)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue