eth/catalyst: add initial OpenTelemetry tracing for newPayload (#33521)

This PR adds initial OpenTelemetry tracing to the Engine API, focusing
on engine_newPayload*.

```
jsonrpc.engine/newPayloadV4 
|  |- engine.newPayload  [block.number, block.hash, tx.count]
|     |- core.blockchain.InsertBlockWithoutSetHead
|        |- bc.processor.Process
|        |  |- core.ApplyTransactionWithEVM  [tx.hash, tx.index]
|        |  |- core.ApplyTransactionWithEVM  [tx.hash, tx.index]
|        |  |- ...  (one per transaction)
|        |  |- core.postExecution
|        |- bc.validator.ValidateState
```

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Jonny Rhea 2026-02-17 10:08:57 -06:00 committed by GitHub
parent 550ca91b17
commit c709c19b40
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 162 additions and 82 deletions

View file

@ -20,6 +20,8 @@ require (
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
github.com/ferranbt/fastssz v0.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/golang/snappy v1.0.0 // indirect
@ -32,6 +34,10 @@ require (
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.39.0 // indirect

View file

@ -48,6 +48,11 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@ -59,6 +64,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
@ -108,6 +117,16 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=

View file

@ -17,6 +17,7 @@
package main
import (
"context"
"fmt"
"os"
"runtime/debug"
@ -52,7 +53,7 @@ func main() {
}
vmConfig := vm.Config{}
crossStateRoot, crossReceiptRoot, err := core.ExecuteStateless(chainConfig, vmConfig, payload.Block, payload.Witness)
crossStateRoot, crossReceiptRoot, err := core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness)
if err != nil {
fmt.Fprintf(os.Stderr, "stateless self-validation failed: %v\n", err)
os.Exit(10)

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"math/big"
"testing"
"time"
@ -210,7 +211,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
case <-time.After(25 * time.Millisecond):
}
chain.InsertBlockWithoutSetHead(postBlocks[i], false)
chain.InsertBlockWithoutSetHead(context.Background(), postBlocks[i], false)
}
// Verify the blocks with pre-merge blocks and post-merge blocks

View file

@ -18,6 +18,7 @@
package core
import (
"context"
"errors"
"fmt"
"io"
@ -47,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/syncx"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@ -1818,7 +1820,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
}
defer bc.chainmu.Unlock()
_, n, err := bc.insertChain(chain, true, false) // No witness collection for mass inserts (would get super large)
_, n, err := bc.insertChain(context.Background(), chain, true, false) // No witness collection for mass inserts (would get super large)
return n, err
}
@ -1830,7 +1832,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// racey behaviour. If a sidechain import is in progress, and the historic state
// is imported, but then new canon-head is added before the actual sidechain
// completes, then the historic state could be pruned again
func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {
func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHead bool, makeWitness bool) (*stateless.Witness, int, error) {
// If the chain is terminating, don't even bother starting up.
if bc.insertStopped() {
return nil, 0, nil
@ -1912,11 +1914,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if setHead {
// First block is pruned, insert as sidechain and reorg only if TD grows enough
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
return bc.insertSideChain(block, it, makeWitness)
return bc.insertSideChain(ctx, block, it, makeWitness)
} else {
// We're post-merge and the parent is pruned, try to recover the parent state
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
_, err := bc.recoverAncestors(block, makeWitness)
_, err := bc.recoverAncestors(ctx, block, makeWitness)
return nil, it.index, err
}
// Some other error(except ErrKnownBlock) occurred, abort.
@ -1988,7 +1990,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
}
// The traced section of block import.
start := time.Now()
res, err := bc.ProcessBlock(parent.Root, block, setHead, makeWitness && len(chain) == 1)
res, err := bc.ProcessBlock(ctx, parent.Root, block, setHead, makeWitness && len(chain) == 1)
if err != nil {
return nil, it.index, err
}
@ -2073,7 +2075,7 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats {
// ProcessBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) {
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
@ -2164,7 +2166,9 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
// Process block using the parent state as reference point
pstart := time.Now()
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
pctx, _, spanEnd := telemetry.StartSpan(ctx, "bc.processor.Process")
res, err := bc.processor.Process(pctx, block, statedb, bc.cfg.VmConfig)
spanEnd(&err)
if err != nil {
bc.reportBadBlock(block, res, err)
return nil, err
@ -2172,7 +2176,10 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
ptime := time.Since(pstart)
vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil {
_, _, spanEnd = telemetry.StartSpan(ctx, "bc.validator.ValidateState")
err = bc.validator.ValidateState(block, statedb, res, false)
spanEnd(&err)
if err != nil {
bc.reportBadBlock(block, res, err)
return nil, err
}
@ -2195,7 +2202,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
task := types.NewBlockWithHeader(context).WithBody(*block.Body())
// Run the stateless self-cross-validation
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.cfg.VmConfig, task, witness)
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(ctx, bc.chainConfig, bc.cfg.VmConfig, task, witness)
if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err)
}
@ -2282,7 +2289,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
// The method writes all (header-and-body-valid) blocks to disk, then tries to
// switch over to the new chain if the TD exceeded the current chain.
// insertSideChain is only used pre-merge.
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, makeWitness bool) (*stateless.Witness, int, error) {
func (bc *BlockChain) insertSideChain(ctx context.Context, block *types.Block, it *insertIterator, makeWitness bool) (*stateless.Witness, int, error) {
var current = bc.CurrentBlock()
// The first sidechain block error is already verified to be ErrPrunedAncestor.
@ -2363,7 +2370,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma
// memory here.
if len(blocks) >= 2048 || memory > 64*1024*1024 {
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
if _, _, err := bc.insertChain(blocks, true, false); err != nil {
if _, _, err := bc.insertChain(ctx, blocks, true, false); err != nil {
return nil, 0, err
}
blocks, memory = blocks[:0], 0
@ -2377,7 +2384,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma
}
if len(blocks) > 0 {
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
return bc.insertChain(blocks, true, makeWitness)
return bc.insertChain(ctx, blocks, true, makeWitness)
}
return nil, 0, nil
}
@ -2386,7 +2393,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator, ma
// all the ancestor blocks since that.
// recoverAncestors is only used post-merge.
// We return the hash of the latest block that we could correctly validate.
func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (common.Hash, error) {
func (bc *BlockChain) recoverAncestors(ctx context.Context, block *types.Block, makeWitness bool) (common.Hash, error) {
// Gather all the sidechain hashes (full blocks may be memory heavy)
var (
hashes []common.Hash
@ -2426,7 +2433,7 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co
} else {
b = bc.GetBlock(hashes[i], numbers[i])
}
if _, _, err := bc.insertChain(types.Blocks{b}, false, makeWitness && i == 0); err != nil {
if _, _, err := bc.insertChain(ctx, types.Blocks{b}, false, makeWitness && i == 0); err != nil {
return b.ParentHash(), err
}
}
@ -2653,14 +2660,16 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire
// procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) {
func (bc *BlockChain) InsertBlockWithoutSetHead(ctx context.Context, block *types.Block, makeWitness bool) (witness *stateless.Witness, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.blockchain.InsertBlockWithoutSetHead")
defer spanEnd(&err)
if !bc.chainmu.TryLock() {
return nil, errChainStopped
}
defer bc.chainmu.Unlock()
witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness)
return witness, err
witness, _, err = bc.insertChain(ctx, types.Blocks{block}, false, makeWitness)
return
}
// SetCanonical rewinds the chain to set the new head block as the specified
@ -2674,7 +2683,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
// Re-execute the reorged chain in case the head state is missing.
if !bc.HasState(head.Root()) {
if latestValidHash, err := bc.recoverAncestors(head, false); err != nil {
if latestValidHash, err := bc.recoverAncestors(context.Background(), head, false); err != nil {
return latestValidHash, err
}
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())

View file

@ -18,6 +18,7 @@ package core
import (
"bytes"
"context"
"errors"
"fmt"
gomath "math"
@ -160,7 +161,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
res, err := blockchain.processor.Process(block, statedb, vm.Config{})
res, err := blockchain.processor.Process(context.Background(), block, statedb, vm.Config{})
if err != nil {
blockchain.reportBadBlock(block, res, err)
return err
@ -3456,7 +3457,7 @@ func testSetCanonical(t *testing.T, scheme string) {
gen.AddTx(tx)
})
for _, block := range side {
_, err := chain.InsertBlockWithoutSetHead(block, false)
_, err := chain.InsertBlockWithoutSetHead(context.Background(), block, false)
if err != nil {
t.Fatalf("Failed to insert into chain: %v", err)
}

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"fmt"
"math/big"
@ -27,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params"
)
@ -57,7 +59,7 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
var (
config = p.chainConfig()
receipts types.Receipts
@ -101,30 +103,21 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.SetTxContext(tx.Hash(), i)
_, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM",
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
telemetry.Int64Attribute("tx.index", int64(i)),
)
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
spanEnd(&err)
if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
// Read requests if Prague is enabled.
var requests [][]byte
if config.IsPrague(block.Number(), block.Time()) {
requests = [][]byte{}
// EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
}
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
}
requests, err := postExecution(ctx, config, block, allLogs, evm)
if err != nil {
return nil, err
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
@ -138,6 +131,31 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}, nil
}
// postExecution processes the post-execution system calls if Prague is enabled.
func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution")
defer spanEnd(&err)
// Read requests if Prague is enabled.
if config.IsPrague(block.Number(), block.Time()) {
requests = [][]byte{}
// EIP-6110
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
return requests, fmt.Errorf("failed to parse deposit logs: %w", err)
}
// EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process withdrawal queue: %w", err)
}
// EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return requests, fmt.Errorf("failed to process consolidation queue: %w", err)
}
}
return requests, nil
}
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input.

View file

@ -17,6 +17,8 @@
package core
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/consensus/beacon"
@ -40,7 +42,7 @@ import (
// - It cannot be placed outside of core, because it needs to construct a dud headerchain
//
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
// Sanity check if the supplied block accidentally contains a set root or
// receipt hash. If so, be very loud, but still continue.
if block.Root() != (common.Hash{}) {
@ -66,7 +68,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields
res, err := processor.Process(block, db, vmconfig)
res, err := processor.Process(ctx, block, db, vmconfig)
if err != nil {
return common.Hash{}, common.Hash{}, err
}

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"sync/atomic"
"github.com/ethereum/go-ethereum/core/state"
@ -48,7 +49,7 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
}
// ProcessResult contains the values computed by Process.

View file

@ -503,7 +503,7 @@ func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true)
if err != nil {
return nil, err
}
@ -520,7 +520,7 @@ func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWit
if parent == nil {
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
}
result, err := bc.ProcessBlock(parent.Root, block, false, true)
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true)
if err != nil {
return nil, err
}

View file

@ -18,6 +18,7 @@
package catalyst
import (
"context"
"errors"
"fmt"
"reflect"
@ -35,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
@ -625,15 +627,15 @@ func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.Blob
var invalidStatus = engine.PayloadStatusV1{Status: engine.INVALID}
// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadV1(ctx context.Context, params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil {
return invalidStatus, paramsErr("withdrawals not supported in V1")
}
return api.newPayload(params, nil, nil, nil, false)
return api.newPayload(ctx, params, nil, nil, nil, false)
}
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadV2(ctx context.Context, params engine.ExecutableData) (engine.PayloadStatusV1, error) {
var (
cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
@ -650,11 +652,11 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
return api.newPayload(params, nil, nil, nil, false)
return api.newPayload(ctx, params, nil, nil, nil, false)
}
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadV3(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
@ -669,11 +671,11 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
return api.newPayload(params, versionedHashes, beaconRoot, nil, false)
return api.newPayload(ctx, params, versionedHashes, beaconRoot, nil, false)
}
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadV4(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
@ -694,10 +696,10 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
return api.newPayload(ctx, params, versionedHashes, beaconRoot, requests, false)
}
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) newPayload(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (result engine.PayloadStatusV1, err error) {
// The locking here is, strictly, not required. Without these locks, this can happen:
//
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@ -711,6 +713,13 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
// sequentially.
// Hence, we use a lock here, to be sure that the previous call has finished before we
// check whether we already have the block locally.
var attrs = []telemetry.Attribute{
telemetry.Int64Attribute("block.number", int64(params.Number)),
telemetry.StringAttribute("block.hash", params.BlockHash.Hex()),
telemetry.Int64Attribute("tx.count", int64(len(params.Transactions))),
}
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.newPayload", attrs...)
defer spanEnd(&err)
api.newPayloadLock.Lock()
defer api.newPayloadLock.Unlock()
@ -789,7 +798,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
}
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
start := time.Now()
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(ctx, block, witness)
processingTime := time.Since(start)
if err != nil {
log.Warn("NewPayload: inserting block failed", "error", err)

View file

@ -314,7 +314,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
newResp, err := api.NewPayloadV1(*execData)
newResp, err := api.NewPayloadV1(context.Background(), *execData)
switch {
case err != nil:
t.Fatalf("Failed to insert block: %v", err)
@ -356,7 +356,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
newResp, err := api.NewPayloadV1(*execData)
newResp, err := api.NewPayloadV1(context.Background(), *execData)
if err != nil || newResp.Status != "VALID" {
t.Fatalf("Failed to insert block: %v", err)
}
@ -502,7 +502,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
}
envelope := getNewEnvelope(t, api, parent, w, h)
execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false)
execResp, err := api.newPayload(context.Background(), *envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
@ -648,7 +648,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
t.Fatalf("payload should not be empty")
}
}
execResp, err := api.NewPayloadV1(*payload.ExecutionPayload)
execResp, err := api.NewPayloadV1(context.Background(), *payload.ExecutionPayload)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
@ -708,7 +708,7 @@ func TestEmptyBlocks(t *testing.T) {
// (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor, nil, nil)
status, err := api.NewPayloadV1(*payload)
status, err := api.NewPayloadV1(context.Background(), *payload)
if err != nil {
t.Fatal(err)
}
@ -724,7 +724,7 @@ func TestEmptyBlocks(t *testing.T) {
payload.GasUsed += 1
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
status, err = api.NewPayloadV1(*payload)
status, err = api.NewPayloadV1(context.Background(), *payload)
if err != nil {
t.Fatal(err)
}
@ -742,7 +742,7 @@ func TestEmptyBlocks(t *testing.T) {
payload.ParentHash = common.Hash{1}
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
status, err = api.NewPayloadV1(*payload)
status, err = api.NewPayloadV1(context.Background(), *payload)
if err != nil {
t.Fatal(err)
}
@ -859,7 +859,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
// feed the payloads to node B
for _, payload := range invalidChain {
status, err := apiB.NewPayloadV1(*payload)
status, err := apiB.NewPayloadV1(context.Background(), *payload)
if err != nil {
panic(err)
}
@ -892,7 +892,7 @@ func TestInvalidBloom(t *testing.T) {
// (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor, nil, nil)
payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload)
status, err := api.NewPayloadV1(context.Background(), *payload)
if err != nil {
t.Fatal(err)
}
@ -931,7 +931,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
for ii := 0; ii < 10; ii++ {
go func() {
defer wg.Done()
if newResp, err := api.NewPayloadV1(*execData); err != nil {
if newResp, err := api.NewPayloadV1(context.Background(), *execData); err != nil {
errMu.Lock()
testErr = fmt.Errorf("failed to insert block: %w", err)
errMu.Unlock()
@ -1038,7 +1038,7 @@ func TestWithdrawals(t *testing.T) {
}
// 10: verify locally built block
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
if status, err := api.NewPayloadV2(context.Background(), *execData.ExecutionPayload); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != engine.VALID {
t.Fatalf("invalid payload")
@ -1082,7 +1082,7 @@ func TestWithdrawals(t *testing.T) {
if err != nil {
t.Fatalf("error getting payload, err=%v", err)
}
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
if status, err := api.NewPayloadV2(context.Background(), *execData.ExecutionPayload); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != engine.VALID {
t.Fatalf("invalid payload")
@ -1225,9 +1225,9 @@ func TestNilWithdrawals(t *testing.T) {
}
var status engine.PayloadStatusV1
if !shanghai {
status, err = api.NewPayloadV1(*execData.ExecutionPayload)
status, err = api.NewPayloadV1(context.Background(), *execData.ExecutionPayload)
} else {
status, err = api.NewPayloadV2(*execData.ExecutionPayload)
status, err = api.NewPayloadV2(context.Background(), *execData.ExecutionPayload)
}
if err != nil {
t.Fatalf("error validating payload: %v", err.(*engine.EngineAPIError).ErrorData())
@ -1598,7 +1598,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
}
// 11: verify locally built block
if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil {
if status, err := api.NewPayloadV3(context.Background(), *execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil {
t.Fatalf("error validating payload: %v", err)
} else if status.Status != engine.VALID {
t.Fatalf("invalid payload")
@ -1705,7 +1705,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
envelope.ExecutionPayload.StateRoot = wantStateRoot
envelope.ExecutionPayload.ReceiptsRoot = wantReceiptRoot
res2, err := api.NewPayloadWithWitnessV3(*envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42})
res2, err := api.NewPayloadWithWitnessV3(context.Background(), *envelope.ExecutionPayload, []common.Hash{}, &common.Hash{42})
if err != nil {
t.Fatalf("error executing stateless payload witness: %v", err)
}

View file

@ -17,6 +17,7 @@
package catalyst
import (
"context"
"crypto/rand"
"crypto/sha256"
"errors"
@ -32,11 +33,13 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/telemetry"
"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"
"go.opentelemetry.io/otel"
)
const devEpochLength = 32
@ -191,6 +194,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
}
version := payloadVersion(c.eth.BlockChain().Config(), timestamp)
tracer := otel.Tracer("")
var random [32]byte
rand.Read(random[:])
@ -255,8 +259,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
requests = envelope.Requests
}
// Create a server span for newPayload, simulating the consensus client
// sending the execution payload for validation.
npCtx, npSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
System: "jsonrpc",
Service: "engine",
Method: "newPayloadV" + fmt.Sprintf("%d", version),
})
// Mark the payload as canon
_, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false)
_, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false)
npSpanEnd(&err)
if err != nil {
return err
}

View file

@ -17,6 +17,7 @@
package catalyst
import (
"context"
"errors"
"strconv"
"time"
@ -86,16 +87,16 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadWithWitnessV1(ctx context.Context, params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
}
return api.newPayload(params, nil, nil, nil, true)
return api.newPayload(ctx, params, nil, nil, nil, true)
}
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadWithWitnessV2(ctx context.Context, params engine.ExecutableData) (engine.PayloadStatusV1, error) {
var (
cancun = api.config().IsCancun(api.config().LondonBlock, params.Timestamp)
shanghai = api.config().IsShanghai(api.config().LondonBlock, params.Timestamp)
@ -112,12 +113,12 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
return api.newPayload(params, nil, nil, nil, true)
return api.newPayload(ctx, params, nil, nil, nil, true)
}
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadWithWitnessV3(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
@ -132,12 +133,12 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, v
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
return api.newPayload(params, versionedHashes, beaconRoot, nil, true)
return api.newPayload(ctx, params, versionedHashes, beaconRoot, nil, true)
}
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
func (api *ConsensusAPI) NewPayloadWithWitnessV4(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
@ -158,7 +159,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.newPayload(params, versionedHashes, beaconRoot, requests, true)
return api.newPayload(ctx, params, versionedHashes, beaconRoot, requests, true)
}
// ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in
@ -283,7 +284,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
api.lastNewPayloadUpdate.Store(time.Now().Unix())
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
stateRoot, receiptRoot, err := core.ExecuteStateless(api.config(), vm.Config{}, block, witness)
stateRoot, receiptRoot, err := core.ExecuteStateless(context.Background(), api.config(), vm.Config{}, block, witness)
if err != nil {
log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
errorMsg := err.Error()

View file

@ -147,7 +147,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next)
}
_, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
_, err := eth.blockchain.Processor().Process(ctx, current, statedb, vm.Config{})
if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}