Compare commits

...

5 Commits

Author SHA1 Message Date
Cedrick AH 321f832f49
Merge d6ffb496ec into dab746b3ef 2025-02-18 21:33:14 +01:00
Matthieu Vachon dab746b3ef
eth/catalyst: support earlier forks in SimulatedBeacon (#31084)
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
2025-02-18 21:08:43 +01:00
Cedrick AHOUANGANSI d6ffb496ec core/vm/interpreter: reverted pcCopy arg to pc 2025-02-06 07:37:41 +01:00
Cedrick AHOUANGANSI 9489a46982 eth/tracers, core/vm: fix tracers output new opcodes for pre-cancun blocks 2025-02-06 02:44:28 +01:00
Cedrick AHOUANGANSI fbb8b85aee eth/tracers, core: update OpcodeHook to pass Cancun fork status param 2025-02-06 02:24:54 +01:00
15 changed files with 95 additions and 41 deletions

View File

@ -128,9 +128,9 @@ func (l *fileWritingTracer) hooks() *tracing.Hooks {
l.inner.OnExit(depth, output, gasUsed, err, reverted)
}
},
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
if l.inner != nil && l.inner.OnOpcode != nil {
l.inner.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
l.inner.OnOpcode(pc, op, gas, cost, scope, rData, depth, isCancun, err)
}
},
OnFault: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {

View File

@ -108,7 +108,7 @@ type (
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool)
// OpcodeHook is invoked just prior to the execution of an opcode.
OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error)
OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, isCancun bool, err error)
// FaultHook is invoked when an error occurs during the execution of an opcode.
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error)

View File

@ -216,7 +216,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
return
}
if !logged && in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, in.evm.chainConfig.IsCancun(in.evm.Context.BlockNumber, in.evm.Context.Time), VMErrorFromErr(err))
}
if logged && in.evm.Config.Tracer.OnFault != nil {
in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
@ -298,7 +298,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, in.evm.chainConfig.IsCancun(in.evm.Context.BlockNumber, in.evm.Context.Time), VMErrorFromErr(err))
logged = true
}
}

View File

@ -634,3 +634,11 @@ var stringToOp = map[string]OpCode{
func StringToOp(str string) OpCode {
return stringToOp[str]
}
// IsCancunOpcode specifies if an opcode is added at Cancun fork.
func (op OpCode) IsCancunOpcode() bool {
if op == BLOBHASH || op == BLOBBASEFEE || op == TLOAD || op == TSTORE || op == MCOPY {
return true
}
return false
}

View File

@ -680,9 +680,14 @@ func TestColdAccountAccessCost(t *testing.T) {
Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{
Tracer: &tracing.Hooks{
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
// Uncomment to investigate failures:
//t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
// opString := vm.OpCode(op).String()
// Use hex string if Cancun is inactive but the opcode is invalid before Cancun fork
// if !isCancun && vm.OpCode(op).IsCancunOpcode() {
// opString = fmt.Sprintf("0x%x", byte(vm.OpCode(op)))
// }
//t.Logf("%d: %v %d", step, opString, cost)
if step == tc.step {
have = cost
}
@ -934,9 +939,14 @@ func TestDelegatedAccountAccessCost(t *testing.T) {
State: statedb,
EVMConfig: vm.Config{
Tracer: &tracing.Hooks{
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
// Uncomment to investigate failures:
t.Logf("%d: %v %d", step, vm.OpCode(op).String(), cost)
opString := vm.OpCode(op).String()
// Use hex string if Cancun is inactive but the opcode is invalid before Cancun fork
if !isCancun && vm.OpCode(op).IsCancunOpcode() {
opString = fmt.Sprintf("0x%x", byte(vm.OpCode(op)))
}
t.Logf("%d: %v %d", step, opString, cost)
if step == tc.step {
have = cost
}

View File

@ -33,6 +33,8 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
"github.com/ethereum/go-ethereum/rpc"
)
@ -95,6 +97,16 @@ type SimulatedBeacon struct {
lastBlockTime uint64
}
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
switch config.LatestFork(time) {
case forks.Prague, forks.Cancun:
return engine.PayloadV3
case forks.Paris, forks.Shanghai:
return engine.PayloadV2
}
panic("invalid fork, simulated beacon needs to be started post-merge")
}
// NewSimulatedBeacon constructs a new simulated beacon chain.
func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) {
block := eth.BlockChain().CurrentBlock()
@ -107,7 +119,8 @@ func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, err
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
if block.Number.Sign() == 0 {
if _, err := engineAPI.ForkchoiceUpdatedV3(current, nil); err != nil {
version := payloadVersion(eth.BlockChain().Config(), block.Time)
if _, err := engineAPI.forkchoiceUpdated(current, nil, version, false); err != nil {
return nil, err
}
}
@ -171,6 +184,8 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
return fmt.Errorf("failed to sync txpool: %w", err)
}
version := payloadVersion(c.eth.BlockChain().Config(), timestamp)
var random [32]byte
rand.Read(random[:])
fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, &engine.PayloadAttributes{
@ -179,7 +194,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Withdrawals: withdrawals,
Random: random,
BeaconRoot: &common.Hash{},
}, engine.PayloadV3, false)
}, version, false)
if err != nil {
return err
}
@ -204,28 +219,39 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
}
}
// Independently calculate the blob hashes from sidecars.
blobHashes := make([]common.Hash, 0)
if envelope.BlobsBundle != nil {
hasher := sha256.New()
for _, commit := range envelope.BlobsBundle.Commitments {
var c kzg4844.Commitment
if len(commit) != len(c) {
return errors.New("invalid commitment length")
var (
blobHashes []common.Hash
beaconRoot *common.Hash
requests [][]byte
)
// Compute post-shanghai fields
if version > engine.PayloadV2 {
// Independently calculate the blob hashes from sidecars.
blobHashes = make([]common.Hash, 0)
if envelope.BlobsBundle != nil {
hasher := sha256.New()
for _, commit := range envelope.BlobsBundle.Commitments {
var c kzg4844.Commitment
if len(commit) != len(c) {
return errors.New("invalid commitment length")
}
copy(c[:], commit)
blobHashes = append(blobHashes, kzg4844.CalcBlobHashV1(hasher, &c))
}
copy(c[:], commit)
blobHashes = append(blobHashes, kzg4844.CalcBlobHashV1(hasher, &c))
}
beaconRoot = &common.Hash{}
requests = envelope.Requests
}
// Mark the payload as canon
_, err = c.engineAPI.newPayload(*payload, blobHashes, &common.Hash{}, envelope.Requests, false)
_, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false)
if err != nil {
return err
}
c.setCurrentState(payload.BlockHash, finalizedHash)
// Mark the block containing the payload as canonical
if _, err = c.engineAPI.ForkchoiceUpdatedV3(c.curForkchoiceState, nil); err != nil {
if _, err = c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, nil, version, false); err != nil {
return err
}
c.lastBlockTime = payload.Timestamp

View File

@ -329,7 +329,7 @@ func (t *jsTracer) onStart(from common.Address, to common.Address, create bool,
}
// OnOpcode implements the Tracer interface to trace a single step of VM execution.
func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
if !t.traceStep {
return
}

View File

@ -183,10 +183,10 @@ func TestHaltBetweenSteps(t *testing.T) {
evm.SetTxContext(vm.TxContext{GasPrice: big.NewInt(1)})
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0))
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, true, nil)
timeout := errors.New("stahp")
tracer.Stop(timeout)
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, true, nil)
if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) {
t.Errorf("Expected timeout error, got %v", err)

View File

@ -61,7 +61,7 @@ func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
}, nil
}
func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
}
func (t *noop) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {

View File

@ -132,7 +132,7 @@ func (a *AccessListTracer) Hooks() *tracing.Hooks {
}
// OnOpcode captures all opcodes that touch storage or addresses and adds them to the accesslist.
func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
stackData := scope.StackData()
stackLen := len(stackData)
op := vm.OpCode(opcode)

View File

@ -157,10 +157,15 @@ type structLogLegacy struct {
}
// toLegacyJSON converts the structLog to legacy json-encoded legacy form.
func (s *StructLog) toLegacyJSON() json.RawMessage {
func (s *StructLog) toLegacyJSON(isCancun bool) json.RawMessage {
opString := s.OpName()
// Use hex string if Cancun is inactive but the opcode is invalid before Cancun fork
if !isCancun && s.Op.IsCancunOpcode() {
opString = fmt.Sprintf("0x%x", byte(s.Op))
}
msg := structLogLegacy{
Pc: s.Pc,
Op: s.Op.String(),
Op: opString,
Gas: s.Gas,
GasCost: s.GasCost,
Depth: s.Depth,
@ -254,7 +259,7 @@ func (l *StructLogger) Hooks() *tracing.Hooks {
// OnOpcode logs a new structured log message and pushes it out to the environment
//
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
// If tracing was interrupted, exit
if l.interrupt.Load() {
return
@ -315,7 +320,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
// create a log
if l.writer == nil {
entry := log.toLegacyJSON()
entry := log.toLegacyJSON(isCancun)
l.resultSize += len(entry)
l.logs = append(l.logs, entry)
return
@ -491,12 +496,17 @@ func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, r
}
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
if t.skip {
return
}
stack := scope.StackData()
fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, vm.OpCode(op).String(),
opString := vm.OpCode(op).String()
// Use hex string if Cancun is inactive but the opcode is invalid before Cancun fork
if !isCancun && vm.OpCode(op).IsCancunOpcode() {
opString = fmt.Sprintf("0x%x", byte(vm.OpCode(op)))
}
fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, opString,
cost, t.env.StateDB.GetRefund())
if !t.cfg.DisableStack {

View File

@ -97,11 +97,11 @@ func NewJSONLoggerWithCallFrames(cfg *Config, writer io.Writer) *tracing.Hooks {
}
func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) {
// TODO: Add rData to this interface as well
l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err)
// TODO: Add rData and Cancun status to this interface as well
l.OnOpcode(pc, op, gas, cost, scope, nil, depth, true, err)
}
func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
memory := scope.MemoryData()
stack := scope.StackData()

View File

@ -76,10 +76,10 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params
}, nil
}
func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
for _, t := range t.tracers {
if t.OnOpcode != nil {
t.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
t.OnOpcode(pc, op, gas, cost, scope, rData, depth, isCancun, err)
}
}
}

View File

@ -58,7 +58,7 @@ func newNoopTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *param
}, nil
}
func (t *noopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *noopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
}
func (t *noopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {

View File

@ -101,7 +101,7 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *p
}
// OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, isCancun bool, err error) {
if err != nil {
return
}