Merge branch 'ethereum:master' into build/go1.24.4

This commit is contained in:
levisyin 2025-06-15 22:08:54 +08:00 committed by GitHub
commit c2c0e40176
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 1329 additions and 3028 deletions

View file

@ -2,7 +2,7 @@
# with Go source code. If you know what GOPATH is then you probably # with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make. # don't need to bother with make.
.PHONY: geth all test lint fmt clean devtools help .PHONY: geth evm all test lint fmt clean devtools help
GOBIN = ./build/bin GOBIN = ./build/bin
GO ?= latest GO ?= latest
@ -14,6 +14,12 @@ geth:
@echo "Done building." @echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth." @echo "Run \"$(GOBIN)/geth\" to launch geth."
#? evm: Build evm.
evm:
$(GORUN) build/ci.go install ./cmd/evm
@echo "Done building."
@echo "Run \"$(GOBIN)/evm\" to launch evm."
#? all: Build all packages and executables. #? all: Build all packages and executables.
all: all:
$(GORUN) build/ci.go install $(GORUN) build/ci.go install

View file

@ -123,6 +123,11 @@ type BlobAndProofV1 struct {
Proof hexutil.Bytes `json:"proof"` Proof hexutil.Bytes `json:"proof"`
} }
type BlobAndProofV2 struct {
Blob hexutil.Bytes `json:"blob"`
CellProofs []hexutil.Bytes `json:"proofs"`
}
// JSON type overrides for ExecutionPayloadEnvelope. // JSON type overrides for ExecutionPayloadEnvelope.
type executionPayloadEnvelopeMarshaling struct { type executionPayloadEnvelopeMarshaling struct {
BlockValue *hexutil.Big BlockValue *hexutil.Big
@ -331,7 +336,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
for j := range sidecar.Blobs { for j := range sidecar.Blobs {
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:])) bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:])) bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) }
for _, proof := range sidecar.Proofs {
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
} }
} }

View file

@ -0,0 +1,56 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 engine
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
)
func TestBlobs(t *testing.T) {
var (
emptyBlob = new(kzg4844.Blob)
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
emptyCellProof, _ = kzg4844.ComputeCellProofs(emptyBlob)
)
header := types.Header{}
block := types.NewBlock(&header, &types.Body{}, nil, nil)
sidecarWithoutCellProofs := &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}
env := BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithoutCellProofs}, nil)
if len(env.BlobsBundle.Proofs) != 1 {
t.Fatalf("Expect 1 proof in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
}
sidecarWithCellProofs := &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: emptyCellProof,
}
env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil)
if len(env.BlobsBundle.Proofs) != 128 {
t.Fatalf("Expect 128 proofs in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
}
}

View file

@ -1,228 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
var jt vm.JumpTable
const initcode = "INITCODE"
func init() {
jt = vm.NewEOFInstructionSetForTesting()
}
var (
hexFlag = &cli.StringFlag{
Name: "hex",
Usage: "Single container data parse and validation",
}
refTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
eofParseCommand = &cli.Command{
Name: "eofparse",
Aliases: []string{"eof"},
Usage: "Parses hex eof container and returns validation errors (if any)",
Action: eofParseAction,
Flags: []cli.Flag{
hexFlag,
refTestFlag,
},
}
eofDumpCommand = &cli.Command{
Name: "eofdump",
Usage: "Parses hex eof container and prints out human-readable representation of the container.",
Action: eofDumpAction,
Flags: []cli.Flag{
hexFlag,
},
}
)
func eofParseAction(ctx *cli.Context) error {
// If `--test` is set, parse and validate the reference test at the provided path.
if ctx.IsSet(refTestFlag.Name) {
var (
file = ctx.String(refTestFlag.Name)
executedTests int
passedTests int
)
err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
log.Debug("Executing test", "name", info.Name())
passed, tot, err := executeTest(path)
passedTests += passed
executedTests += tot
return err
})
if err != nil {
return err
}
log.Info("Executed tests", "passed", passedTests, "total executed", executedTests)
return nil
}
// If `--hex` is set, parse and validate the hex string argument.
if ctx.IsSet(hexFlag.Name) {
if _, err := parseAndValidate(ctx.String(hexFlag.Name), false); err != nil {
return fmt.Errorf("err: %w", err)
}
fmt.Println("OK")
return nil
}
// If neither are passed in, read input from stdin.
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(l, "#") || l == "" {
continue
}
if _, err := parseAndValidate(l, false); err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Println("OK")
}
}
if err := scanner.Err(); err != nil {
fmt.Println(err.Error())
}
return nil
}
type refTests struct {
Vectors map[string]eOFTest `json:"vectors"`
}
type eOFTest struct {
Code string `json:"code"`
Results map[string]etResult `json:"results"`
ContainerKind string `json:"containerKind"`
}
type etResult struct {
Result bool `json:"result"`
Exception string `json:"exception,omitempty"`
}
func executeTest(path string) (int, int, error) {
src, err := os.ReadFile(path)
if err != nil {
return 0, 0, err
}
var testsByName map[string]refTests
if err := json.Unmarshal(src, &testsByName); err != nil {
return 0, 0, err
}
passed, total := 0, 0
for testsName, tests := range testsByName {
for name, tt := range tests.Vectors {
for fork, r := range tt.Results {
total++
_, err := parseAndValidate(tt.Code, tt.ContainerKind == initcode)
if r.Result && err != nil {
log.Error("Test failure, expected validation success", "name", testsName, "idx", name, "fork", fork, "err", err)
continue
}
if !r.Result && err == nil {
log.Error("Test failure, expected validation error", "name", testsName, "idx", name, "fork", fork, "have err", r.Exception, "err", err)
continue
}
passed++
}
}
}
return passed, total, nil
}
func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("unable to decode data: %w", err)
}
return parse(b, isInitCode)
}
func parse(b []byte, isInitCode bool) (*vm.Container, error) {
var c vm.Container
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
return nil, err
}
if err := c.ValidateCode(&jt, isInitCode); err != nil {
return nil, err
}
return &c, nil
}
func eofDumpAction(ctx *cli.Context) error {
// If `--hex` is set, parse and validate the hex string argument.
if ctx.IsSet(hexFlag.Name) {
return eofDump(ctx.String(hexFlag.Name))
}
// Otherwise read from stdin
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(l, "#") || l == "" {
continue
}
if err := eofDump(l); err != nil {
return err
}
fmt.Println("")
}
return scanner.Err()
}
func eofDump(hexdata string) error {
if len(hexdata) >= 2 && strings.HasPrefix(hexdata, "0x") {
hexdata = hexdata[2:]
}
b, err := hex.DecodeString(hexdata)
if err != nil {
return fmt.Errorf("unable to decode data: %w", err)
}
var c vm.Container
if err := c.UnmarshalBinary(b, false); err != nil {
return err
}
fmt.Println(c.String())
return nil
}

View file

@ -1,182 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)
func FuzzEofParsing(f *testing.F) {
// Seed with corpus from execution-spec-tests
for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname)
if err != nil {
break
}
f.Logf("Reading seed data from %v", fname)
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
panic(err) // rotten corpus
}
f.Add(b)
}
corpus.Close()
if err := scanner.Err(); err != nil {
panic(err) // rotten corpus
}
}
// And do the fuzzing
f.Fuzz(func(t *testing.T, data []byte) {
var (
jt = vm.NewEOFInstructionSetForTesting()
c vm.Container
)
cpy := common.CopyBytes(data)
if err := c.UnmarshalBinary(data, true); err == nil {
c.ValidateCode(&jt, true)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if err := c.UnmarshalBinary(data, false); err == nil {
c.ValidateCode(&jt, false)
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
t.Fatal("Unmarshal-> Marshal failure!")
}
}
if !bytes.Equal(cpy, data) {
panic("data modified during unmarshalling")
}
})
}
func TestEofParseInitcode(t *testing.T) {
testEofParse(t, true, "testdata/eof/results.initcode.txt")
}
func TestEofParseRegular(t *testing.T) {
testEofParse(t, false, "testdata/eof/results.regular.txt")
}
func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
var wantFn func() string
var wantLoc = 0
{ // Configure the want-reader
wants, err := os.Open(wantFile)
if err != nil {
t.Fatal(err)
}
scanner := bufio.NewScanner(wants)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
wantFn = func() string {
if scanner.Scan() {
wantLoc++
return scanner.Text()
}
return "end of file reached"
}
}
for i := 0; ; i++ {
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
corpus, err := os.Open(fname)
if err != nil {
break
}
t.Logf("# Reading seed data from %v", fname)
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
line := 1
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
b, err := hex.DecodeString(s)
if err != nil {
panic(err) // rotten corpus
}
have := "OK"
if _, err := parse(b, isInitCode); err != nil {
have = fmt.Sprintf("ERR: %v", err)
}
if false { // Change this to generate the want-output
fmt.Printf("%v\n", have)
} else {
want := wantFn()
if have != want {
if len(want) > 100 {
want = want[:100]
}
if len(b) > 100 {
b = b[:100]
}
t.Errorf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n",
fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
}
}
line++
}
corpus.Close()
}
}
func BenchmarkEofParse(b *testing.B) {
corpus, err := os.Open("testdata/eof/eof_benches.txt")
if err != nil {
b.Fatal(err)
}
defer corpus.Close()
scanner := bufio.NewScanner(corpus)
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
line := 1
for scanner.Scan() {
s := scanner.Text()
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
s = s[2:]
}
data, err := hex.DecodeString(s)
if err != nil {
b.Fatal(err) // rotten corpus
}
b.Run(fmt.Sprintf("test-%d", line), func(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(data)))
for i := 0; i < b.N; i++ {
_, _ = parse(data, false)
}
})
line++
}
}

View file

@ -303,7 +303,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
} }
// Set the receipt logs and create the bloom filter. // Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash) receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash, vmContext.Time)
receipt.Bloom = types.CreateBloom(receipt) receipt.Bloom = types.CreateBloom(receipt)
// These three are non-consensus fields: // These three are non-consensus fields:

View file

@ -210,8 +210,6 @@ func init() {
stateTransitionCommand, stateTransitionCommand,
transactionCommand, transactionCommand,
blockBuilderCommand, blockBuilderCommand,
eofParseCommand,
eofDumpCommand,
} }
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx) flags.MigrateGlobalFlags(ctx)

View file

@ -26,4 +26,5 @@ the following commands (in this directory) against a synced mainnet node:
```shell ```shell
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545 > go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545 > go run . historygen --history-tests queries/history_mainnet.json http://host:8545
> go run . tracegen --trace-tests queries/trace_mainnet.json http://host:8545
``` ```

104
cmd/workload/client.go Normal file
View file

@ -0,0 +1,104 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
type client struct {
Eth *ethclient.Client
Geth *gethclient.Client
RPC *rpc.Client
}
func makeClient(ctx *cli.Context) *client {
if ctx.NArg() < 1 {
exit("missing RPC endpoint URL as command-line argument")
}
url := ctx.Args().First()
cl, err := rpc.Dial(url)
if err != nil {
exit(fmt.Errorf("could not create RPC client at %s: %v", url, err))
}
return &client{
RPC: cl,
Eth: ethclient.NewClient(cl),
Geth: gethclient.New(cl),
}
}
type simpleBlock struct {
Number hexutil.Uint64 `json:"number"`
Hash common.Hash `json:"hash"`
}
type simpleTransaction struct {
Hash common.Hash `json:"hash"`
TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
}
func (c *client) getBlockByHash(ctx context.Context, arg common.Hash, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByHash", arg, fullTx)
return r, err
}
func (c *client) getBlockByNumber(ctx context.Context, arg uint64, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByNumber", hexutil.Uint64(arg), fullTx)
return r, err
}
func (c *client) getTransactionByBlockHashAndIndex(ctx context.Context, block common.Hash, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index))
return r, err
}
func (c *client) getTransactionByBlockNumberAndIndex(ctx context.Context, block uint64, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockNumberAndIndex", hexutil.Uint64(block), hexutil.Uint64(index))
return r, err
}
func (c *client) getBlockTransactionCountByHash(ctx context.Context, block common.Hash) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByHash", block)
return uint64(r), err
}
func (c *client) getBlockTransactionCountByNumber(ctx context.Context, block uint64) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByNumber", hexutil.Uint64(block))
return uint64(r), err
}
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
var result []*types.Receipt
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
return result, err
}

View file

@ -45,11 +45,11 @@ func newFilterTestSuite(cfg testConfig) *filterTestSuite {
return s return s
} }
func (s *filterTestSuite) allTests() []utesting.Test { func (s *filterTestSuite) allTests() []workloadTest {
return []utesting.Test{ return []workloadTest{
{Name: "Filter/ShortRange", Fn: s.filterShortRange}, newWorkLoadTest("Filter/ShortRange", s.filterShortRange),
{Name: "Filter/LongRange", Fn: s.filterLongRange, Slow: true}, newSlowWorkloadTest("Filter/LongRange", s.filterLongRange),
{Name: "Filter/FullRange", Fn: s.filterFullRange, Slow: true}, newSlowWorkloadTest("Filter/FullRange", s.filterFullRange),
} }
} }

View file

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/internal/utesting"
) )
@ -65,40 +64,16 @@ func (s *historyTestSuite) loadTests() error {
return nil return nil
} }
func (s *historyTestSuite) allTests() []utesting.Test { func (s *historyTestSuite) allTests() []workloadTest {
return []utesting.Test{ return []workloadTest{
{ newWorkLoadTest("History/getBlockByHash", s.testGetBlockByHash),
Name: "History/getBlockByHash", newWorkLoadTest("History/getBlockByNumber", s.testGetBlockByNumber),
Fn: s.testGetBlockByHash, newWorkLoadTest("History/getBlockReceiptsByHash", s.testGetBlockReceiptsByHash),
}, newWorkLoadTest("History/getBlockReceiptsByNumber", s.testGetBlockReceiptsByNumber),
{ newWorkLoadTest("History/getBlockTransactionCountByHash", s.testGetBlockTransactionCountByHash),
Name: "History/getBlockByNumber", newWorkLoadTest("History/getBlockTransactionCountByNumber", s.testGetBlockTransactionCountByNumber),
Fn: s.testGetBlockByNumber, newWorkLoadTest("History/getTransactionByBlockHashAndIndex", s.testGetTransactionByBlockHashAndIndex),
}, newWorkLoadTest("History/getTransactionByBlockNumberAndIndex", s.testGetTransactionByBlockNumberAndIndex),
{
Name: "History/getBlockReceiptsByHash",
Fn: s.testGetBlockReceiptsByHash,
},
{
Name: "History/getBlockReceiptsByNumber",
Fn: s.testGetBlockReceiptsByNumber,
},
{
Name: "History/getBlockTransactionCountByHash",
Fn: s.testGetBlockTransactionCountByHash,
},
{
Name: "History/getBlockTransactionCountByNumber",
Fn: s.testGetBlockTransactionCountByNumber,
},
{
Name: "History/getTransactionByBlockHashAndIndex",
Fn: s.testGetTransactionByBlockHashAndIndex,
},
{
Name: "History/getTransactionByBlockNumberAndIndex",
Fn: s.testGetTransactionByBlockNumberAndIndex,
},
} }
} }
@ -279,55 +254,3 @@ func (s *historyTestSuite) testGetTransactionByBlockNumberAndIndex(t *utesting.T
} }
} }
} }
type simpleBlock struct {
Number hexutil.Uint64 `json:"number"`
Hash common.Hash `json:"hash"`
}
type simpleTransaction struct {
Hash common.Hash `json:"hash"`
TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
}
func (c *client) getBlockByHash(ctx context.Context, arg common.Hash, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByHash", arg, fullTx)
return r, err
}
func (c *client) getBlockByNumber(ctx context.Context, arg uint64, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByNumber", hexutil.Uint64(arg), fullTx)
return r, err
}
func (c *client) getTransactionByBlockHashAndIndex(ctx context.Context, block common.Hash, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index))
return r, err
}
func (c *client) getTransactionByBlockNumberAndIndex(ctx context.Context, block uint64, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockNumberAndIndex", hexutil.Uint64(block), hexutil.Uint64(index))
return r, err
}
func (c *client) getBlockTransactionCountByHash(ctx context.Context, block common.Hash) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByHash", block)
return uint64(r), err
}
func (c *client) getBlockTransactionCountByNumber(ctx context.Context, block uint64) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByNumber", hexutil.Uint64(block))
return uint64(r), err
}
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
var result []*types.Receipt
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
return result, err
}

View file

@ -51,7 +51,7 @@ var (
} }
historyTestEarliestFlag = &cli.IntFlag{ historyTestEarliestFlag = &cli.IntFlag{
Name: "earliest", Name: "earliest",
Usage: "JSON file containing filter test queries", Usage: "The earliest block to test queries",
Value: 0, Value: 0,
Category: flags.TestingCategory, Category: flags.TestingCategory,
} }
@ -139,7 +139,7 @@ func calcReceiptsHash(rcpt []*types.Receipt) common.Hash {
func writeJSON(fileName string, value any) { func writeJSON(fileName string, value any) {
file, err := os.Create(fileName) file, err := os.Create(fileName)
if err != nil { if err != nil {
exit(fmt.Errorf("Error creating %s: %v", fileName, err)) exit(fmt.Errorf("error creating %s: %v", fileName, err))
return return
} }
defer file.Close() defer file.Close()

View file

@ -20,10 +20,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -49,6 +47,7 @@ func init() {
runTestCommand, runTestCommand,
historyGenerateCommand, historyGenerateCommand,
filterGenerateCommand, filterGenerateCommand,
traceGenerateCommand,
filterPerfCommand, filterPerfCommand,
} }
} }
@ -57,26 +56,6 @@ func main() {
exit(app.Run(os.Args)) exit(app.Run(os.Args))
} }
type client struct {
Eth *ethclient.Client
RPC *rpc.Client
}
func makeClient(ctx *cli.Context) *client {
if ctx.NArg() < 1 {
exit("missing RPC endpoint URL as command-line argument")
}
url := ctx.Args().First()
cl, err := rpc.Dial(url)
if err != nil {
exit(fmt.Errorf("Could not create RPC client at %s: %v", url, err))
}
return &client{
RPC: cl,
Eth: ethclient.NewClient(cl),
}
}
func exit(err any) { func exit(err any) {
if err == nil { if err == nil {
os.Exit(0) os.Exit(0)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"slices"
"github.com/ethereum/go-ethereum/core/history" "github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
@ -45,10 +44,13 @@ var (
testPatternFlag, testPatternFlag,
testTAPFlag, testTAPFlag,
testSlowFlag, testSlowFlag,
testArchiveFlag,
testSepoliaFlag, testSepoliaFlag,
testMainnetFlag, testMainnetFlag,
filterQueryFileFlag, filterQueryFileFlag,
historyTestFileFlag, historyTestFileFlag,
traceTestFileFlag,
traceTestInvalidOutputFlag,
}, },
} }
testPatternFlag = &cli.StringFlag{ testPatternFlag = &cli.StringFlag{
@ -67,6 +69,12 @@ var (
Value: false, Value: false,
Category: flags.TestingCategory, Category: flags.TestingCategory,
} }
testArchiveFlag = &cli.BoolFlag{
Name: "archive",
Usage: "Enable archive tests",
Value: false,
Category: flags.TestingCategory,
}
testSepoliaFlag = &cli.BoolFlag{ testSepoliaFlag = &cli.BoolFlag{
Name: "sepolia", Name: "sepolia",
Usage: "Use test cases for sepolia network", Usage: "Use test cases for sepolia network",
@ -86,6 +94,7 @@ type testConfig struct {
filterQueryFile string filterQueryFile string
historyTestFile string historyTestFile string
historyPruneBlock *uint64 historyPruneBlock *uint64
traceTestFile string
} }
var errPrunedHistory = fmt.Errorf("attempt to access pruned history") var errPrunedHistory = fmt.Errorf("attempt to access pruned history")
@ -125,36 +134,85 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
cfg.historyTestFile = "queries/history_mainnet.json" cfg.historyTestFile = "queries/history_mainnet.json"
cfg.historyPruneBlock = new(uint64) cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber *cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
cfg.traceTestFile = "queries/trace_mainnet.json"
case ctx.Bool(testSepoliaFlag.Name): case ctx.Bool(testSepoliaFlag.Name):
cfg.fsys = builtinTestFiles cfg.fsys = builtinTestFiles
cfg.filterQueryFile = "queries/filter_queries_sepolia.json" cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
cfg.historyTestFile = "queries/history_sepolia.json" cfg.historyTestFile = "queries/history_sepolia.json"
cfg.historyPruneBlock = new(uint64) cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber *cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
cfg.traceTestFile = "queries/trace_sepolia.json"
default: default:
cfg.fsys = os.DirFS(".") cfg.fsys = os.DirFS(".")
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name) cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name) cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
} }
return cfg return cfg
} }
// workloadTest represents a single test in the workload. It's a wrapper
// of utesting.Test by adding a few additional attributes.
type workloadTest struct {
utesting.Test
archive bool // Flag whether the archive node (full state history) is required for this test
}
func newWorkLoadTest(name string, fn func(t *utesting.T)) workloadTest {
return workloadTest{
Test: utesting.Test{
Name: name,
Fn: fn,
},
}
}
func newSlowWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
t := newWorkLoadTest(name, fn)
t.Slow = true
return t
}
func newArchiveWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
t := newWorkLoadTest(name, fn)
t.archive = true
return t
}
func filterTests(tests []workloadTest, pattern string, filterFn func(t workloadTest) bool) []utesting.Test {
var utests []utesting.Test
for _, t := range tests {
if filterFn(t) {
utests = append(utests, t.Test)
}
}
if pattern == "" {
return utests
}
return utesting.MatchTests(utests, pattern)
}
func runTestCmd(ctx *cli.Context) error { func runTestCmd(ctx *cli.Context) error {
cfg := testConfigFromCLI(ctx) cfg := testConfigFromCLI(ctx)
filterSuite := newFilterTestSuite(cfg) filterSuite := newFilterTestSuite(cfg)
historySuite := newHistoryTestSuite(cfg) historySuite := newHistoryTestSuite(cfg)
traceSuite := newTraceTestSuite(cfg, ctx)
// Filter test cases. // Filter test cases.
tests := filterSuite.allTests() tests := filterSuite.allTests()
tests = append(tests, historySuite.allTests()...) tests = append(tests, historySuite.allTests()...)
if ctx.IsSet(testPatternFlag.Name) { tests = append(tests, traceSuite.allTests()...)
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
if t.Slow && !ctx.Bool(testSlowFlag.Name) {
return false
} }
if !ctx.Bool(testSlowFlag.Name) { if t.archive && !ctx.Bool(testArchiveFlag.Name) {
tests = slices.DeleteFunc(tests, func(test utesting.Test) bool { return false
return test.Slow }
return true
}) })
}
// Disable logging unless explicitly enabled. // Disable logging unless explicitly enabled.
if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") { if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
@ -166,7 +224,7 @@ func runTestCmd(ctx *cli.Context) error {
if ctx.Bool(testTAPFlag.Name) { if ctx.Bool(testTAPFlag.Name) {
run = utesting.RunTAP run = utesting.RunTAP
} }
results := run(tests, os.Stdout) results := run(utests, os.Stdout)
if utesting.CountFailures(results) > 0 { if utesting.CountFailures(results) > 0 {
os.Exit(1) os.Exit(1)
} }

126
cmd/workload/tracetest.go Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
// traceTest is the content of a history test.
type traceTest struct {
TxHashes []common.Hash `json:"txHashes"`
TraceConfigs []tracers.TraceConfig `json:"traceConfigs"`
ResultHashes []common.Hash `json:"resultHashes"`
}
type traceTestSuite struct {
cfg testConfig
tests traceTest
invalidDir string
}
func newTraceTestSuite(cfg testConfig, ctx *cli.Context) *traceTestSuite {
s := &traceTestSuite{
cfg: cfg,
invalidDir: ctx.String(traceTestInvalidOutputFlag.Name),
}
if err := s.loadTests(); err != nil {
exit(err)
}
return s
}
func (s *traceTestSuite) loadTests() error {
file, err := s.cfg.fsys.Open(s.cfg.traceTestFile)
if err != nil {
return fmt.Errorf("can't open traceTestFile: %v", err)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(&s.tests); err != nil {
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.traceTestFile, err)
}
if len(s.tests.TxHashes) == 0 {
return fmt.Errorf("traceTestFile %s has no test data", s.cfg.traceTestFile)
}
return nil
}
func (s *traceTestSuite) allTests() []workloadTest {
return []workloadTest{
newArchiveWorkloadTest("Trace/Transaction", s.traceTransaction),
}
}
// traceTransaction runs all transaction tracing tests
func (s *traceTestSuite) traceTransaction(t *utesting.T) {
ctx := context.Background()
for i, hash := range s.tests.TxHashes {
config := s.tests.TraceConfigs[i]
result, err := s.cfg.client.Geth.TraceTransaction(ctx, hash, &config)
if err != nil {
t.Fatalf("Transaction %d (hash %v): error %v", i, hash, err)
}
blob, err := json.Marshal(result)
if err != nil {
t.Fatalf("Transaction %d (hash %v): error %v", i, hash, err)
continue
}
if crypto.Keccak256Hash(blob) != s.tests.ResultHashes[i] {
t.Errorf("Transaction %d (hash %v): invalid result", i, hash)
writeInvalidTraceResult(s.invalidDir, hash, result)
}
}
}
func writeInvalidTraceResult(dir string, hash common.Hash, result any) {
if dir == "" {
return
}
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Info("Failed to make output directory", "err", err)
return
}
name := filepath.Join(dir, "invalid"+"_"+hash.String())
file, err := os.Create(name)
if err != nil {
exit(fmt.Errorf("error creating %s: %v", name, err))
return
}
defer file.Close()
data, _ := json.MarshalIndent(result, "", " ")
_, err = file.Write(data)
if err != nil {
exit(fmt.Errorf("error writing %s: %v", name, err))
return
}
}

View file

@ -0,0 +1,195 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>
package main
import (
"context"
"encoding/json"
"fmt"
"math/big"
"math/rand"
"os"
"path/filepath"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli/v2"
)
var (
defaultBlocksToTrace = 64 // the number of states assumed to be available
traceGenerateCommand = &cli.Command{
Name: "tracegen",
Usage: "Generates tests for state tracing",
ArgsUsage: "<RPC endpoint URL>",
Action: generateTraceTests,
Flags: []cli.Flag{
traceTestFileFlag,
traceTestResultOutputFlag,
traceTestBlockFlag,
},
}
traceTestFileFlag = &cli.StringFlag{
Name: "trace-tests",
Usage: "JSON file containing trace test queries",
Value: "trace_tests.json",
Category: flags.TestingCategory,
}
traceTestResultOutputFlag = &cli.StringFlag{
Name: "trace-output",
Usage: "Folder containing the trace output files",
Value: "",
Category: flags.TestingCategory,
}
traceTestBlockFlag = &cli.IntFlag{
Name: "trace-blocks",
Usage: "The number of blocks for tracing",
Value: defaultBlocksToTrace,
Category: flags.TestingCategory,
}
traceTestInvalidOutputFlag = &cli.StringFlag{
Name: "trace-invalid",
Usage: "Folder containing the mismatched trace output files",
Value: "",
Category: flags.TestingCategory,
}
)
func generateTraceTests(clictx *cli.Context) error {
var (
client = makeClient(clictx)
outputFile = clictx.String(traceTestFileFlag.Name)
outputDir = clictx.String(traceTestResultOutputFlag.Name)
blocks = clictx.Int(traceTestBlockFlag.Name)
ctx = context.Background()
test = new(traceTest)
)
if outputDir != "" {
err := os.MkdirAll(outputDir, os.ModePerm)
if err != nil {
return err
}
}
latest, err := client.Eth.BlockNumber(ctx)
if err != nil {
exit(err)
}
if latest < uint64(blocks) {
exit(fmt.Errorf("node seems not synced, latest block is %d", latest))
}
// Get blocks and assign block info into the test
var (
start = time.Now()
logged = time.Now()
failed int
)
log.Info("Trace transactions around the chain tip", "head", latest, "blocks", blocks)
for i := 0; i < blocks; i++ {
number := latest - uint64(i)
block, err := client.Eth.BlockByNumber(ctx, big.NewInt(int64(number)))
if err != nil {
exit(err)
}
for _, tx := range block.Transactions() {
config, configName := randomTraceOption()
result, err := client.Geth.TraceTransaction(ctx, tx.Hash(), config)
if err != nil {
failed += 1
break
}
blob, err := json.Marshal(result)
if err != nil {
failed += 1
break
}
test.TxHashes = append(test.TxHashes, tx.Hash())
test.TraceConfigs = append(test.TraceConfigs, *config)
test.ResultHashes = append(test.ResultHashes, crypto.Keccak256Hash(blob))
writeTraceResult(outputDir, tx.Hash(), result, configName)
}
if time.Since(logged) > time.Second*8 {
logged = time.Now()
log.Info("Tracing transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
}
}
log.Info("Traced transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
// Write output file.
writeJSON(outputFile, test)
return nil
}
func randomTraceOption() (*tracers.TraceConfig, string) {
x := rand.Intn(11)
if x == 0 {
// struct-logger, with all fields enabled, very heavy
return &tracers.TraceConfig{
Config: &logger.Config{
EnableMemory: true,
EnableReturnData: true,
},
}, "structAll"
}
if x == 1 {
// default options for struct-logger, with stack and storage capture
// enabled
return &tracers.TraceConfig{
Config: &logger.Config{},
}, "structDefault"
}
if x == 2 || x == 3 || x == 4 {
// struct-logger with storage capture enabled
return &tracers.TraceConfig{
Config: &logger.Config{
DisableStack: true,
},
}, "structStorage"
}
// Native tracer
loggers := []string{"callTracer", "4byteTracer", "flatCallTracer", "muxTracer", "noopTracer", "prestateTracer"}
return &tracers.TraceConfig{
Tracer: &loggers[x-5],
}, loggers[x-5]
}
func writeTraceResult(dir string, hash common.Hash, result any, configName string) {
if dir == "" {
return
}
name := filepath.Join(dir, configName+"_"+hash.String())
file, err := os.Create(name)
if err != nil {
exit(fmt.Errorf("error creating %s: %v", name, err))
return
}
defer file.Close()
data, _ := json.MarshalIndent(result, "", " ")
_, err = file.Write(data)
if err != nil {
exit(fmt.Errorf("error writing %s: %v", name, err))
return
}
}

View file

@ -66,7 +66,7 @@ var (
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil) headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
chainMgaspsGauge = metrics.NewRegisteredGauge("chain/mgasps", nil) chainMgaspsMeter = metrics.NewRegisteredResettingTimer("chain/mgasps", nil)
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
@ -2067,7 +2067,12 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits) blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(startTime) elapsed := time.Since(startTime) + 1 // prevent zero division
blockInsertTimer.Update(elapsed)
// TODO(rjl493456442) generalize the ResettingTimer
mgasps := float64(res.GasUsed) * 1000 / float64(elapsed)
chainMgaspsMeter.Update(time.Duration(mgasps))
return &blockProcessingResult{ return &blockProcessingResult{
usedGas: res.GasUsed, usedGas: res.GasUsed,

View file

@ -46,9 +46,6 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
elapsed = now.Sub(st.startTime) + 1 // prevent zero division elapsed = now.Sub(st.startTime) + 1 // prevent zero division
mgasps = float64(st.usedGas) * 1000 / float64(elapsed) mgasps = float64(st.usedGas) * 1000 / float64(elapsed)
) )
// Update the Mgas per second gauge
chainMgaspsGauge.Update(int64(mgasps))
// If we're at the last block of the batch or report period reached, log // If we're at the last block of the batch or report period reached, log
if index == len(chain)-1 || elapsed >= statsReportLimit { if index == len(chain)-1 || elapsed >= statsReportLimit {
// Count the number of transactions in this segment // Count the number of transactions in this segment

View file

@ -684,6 +684,7 @@ type fullLogRLP struct {
Topics []common.Hash Topics []common.Hash
Data []byte Data []byte
BlockNumber uint64 BlockNumber uint64
BlockTimestamp uint64
TxHash common.Hash TxHash common.Hash
TxIndex uint TxIndex uint
BlockHash common.Hash BlockHash common.Hash
@ -696,6 +697,7 @@ func newFullLogRLP(l *types.Log) *fullLogRLP {
Topics: l.Topics, Topics: l.Topics,
Data: l.Data, Data: l.Data,
BlockNumber: l.BlockNumber, BlockNumber: l.BlockNumber,
BlockTimestamp: l.BlockTimestamp,
TxHash: l.TxHash, TxHash: l.TxHash,
TxIndex: l.TxIndex, TxIndex: l.TxIndex,
BlockHash: l.BlockHash, BlockHash: l.BlockHash,
@ -834,7 +836,7 @@ func TestDeriveLogFields(t *testing.T) {
// Derive log metadata fields // Derive log metadata fields
number := big.NewInt(1) number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14}) hash := common.BytesToHash([]byte{0x03, 0x14})
types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, big.NewInt(0), big.NewInt(0), txs) types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 12, big.NewInt(0), big.NewInt(0), txs)
// Iterate over all the computed fields and check that they're correct // Iterate over all the computed fields and check that they're correct
logIndex := uint(0) logIndex := uint(0)
@ -846,6 +848,9 @@ func TestDeriveLogFields(t *testing.T) {
if receipts[i].Logs[j].BlockHash != hash { if receipts[i].Logs[j].BlockHash != hash {
t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String()) t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
} }
if receipts[i].Logs[j].BlockTimestamp != 12 {
t.Errorf("receipts[%d].Logs[%d].BlockTimestamp = %d, want %d", i, j, receipts[i].Logs[j].BlockTimestamp, 12)
}
if receipts[i].Logs[j].TxHash != txs[i].Hash() { if receipts[i].Logs[j].TxHash != txs[i].Hash() {
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
} }

View file

@ -252,11 +252,12 @@ func (s *StateDB) AddLog(log *types.Log) {
// GetLogs returns the logs matching the specified transaction hash, and annotates // GetLogs returns the logs matching the specified transaction hash, and annotates
// them with the given blockNumber and blockHash. // them with the given blockNumber and blockHash.
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log { func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
logs := s.logs[hash] logs := s.logs[hash]
for _, l := range logs { for _, l := range logs {
l.BlockNumber = blockNumber l.BlockNumber = blockNumber
l.BlockHash = blockHash l.BlockHash = blockHash
l.BlockTimestamp = blockTime
} }
return logs return logs
} }

View file

@ -669,9 +669,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d", return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
state.GetRefund(), checkstate.GetRefund()) state.GetRefund(), checkstate.GetRefund())
} }
if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) { if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0)) {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
} }
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) { if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
getKeys := func(dirty map[common.Address]int) string { getKeys := func(dirty map[common.Address]int) string {

View file

@ -97,7 +97,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
} }
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, tx, usedGas, evm) receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
@ -136,7 +136,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However, // and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input. // this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) { func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
if hooks := evm.Config.Tracer; hooks != nil { if hooks := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil { if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
@ -165,11 +165,11 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
statedb.AccessEvents().Merge(evm.AccessEvents) statedb.AccessEvents().Merge(evm.AccessEvents)
} }
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
} }
// MakeReceipt generates the receipt object for a transaction given its execution result. // MakeReceipt generates the receipt object for a transaction given its execution result.
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt { func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
// Create a new receipt for the transaction, storing the intermediate root and gas used // Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx. // by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas} receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
@ -192,7 +192,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
} }
// Set the receipt logs and create the bloom filter. // Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash, blockTime)
receipt.Bloom = types.CreateBloom(receipt) receipt.Bloom = types.CreateBloom(receipt)
receipt.BlockHash = blockHash receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber receipt.BlockNumber = blockNumber
@ -210,7 +210,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
return nil, err return nil, err
} }
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), tx, usedGas, evm) return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm)
} }
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root

View file

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -1302,27 +1301,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
} }
} }
// GetBlobs returns a number of blobs are proofs for the given versioned hashes. // GetBlobs returns a number of blobs and proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to // This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network. // retrieve blobs from the pools directly instead of the network.
func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) { func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
// Create a map of the blob hash to indices for faster fills sidecars := make([]*types.BlobTxSidecar, len(vhashes))
var ( for idx, vhash := range vhashes {
blobs = make([]*kzg4844.Blob, len(vhashes)) // Retrieve the datastore item (in a short lock)
proofs = make([]*kzg4844.Proof, len(vhashes))
)
index := make(map[common.Hash]int)
for i, vhash := range vhashes {
index[vhash] = i
}
// Iterate over the blob hashes, pulling transactions that fill it. Take care
// to also fill anything else the transaction might include (probably will).
for i, vhash := range vhashes {
// If already filled by a previous fetch, skip
if blobs[i] != nil {
continue
}
// Unfilled, retrieve the datastore item (in a short lock)
p.lock.RLock() p.lock.RLock()
id, exists := p.lookup.storeidOfBlob(vhash) id, exists := p.lookup.storeidOfBlob(vhash)
if !exists { if !exists {
@ -1342,16 +1327,24 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
log.Error("Blobs corrupted for traced transaction", "id", id, "err", err) log.Error("Blobs corrupted for traced transaction", "id", id, "err", err)
continue continue
} }
// Fill anything requested, not just the current versioned hash sidecars[idx] = item.BlobTxSidecar()
sidecar := item.BlobTxSidecar() }
for j, blobhash := range item.BlobHashes() { return sidecars
if idx, ok := index[blobhash]; ok { }
blobs[idx] = &sidecar.Blobs[j]
proofs[idx] = &sidecar.Proofs[j] // AvailableBlobs returns the number of blobs that are available in the subpool.
func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
available := 0
for _, vhash := range vhashes {
// Retrieve the datastore item (in a short lock)
p.lock.RLock()
_, exists := p.lookup.storeidOfBlob(vhash)
p.lock.RUnlock()
if exists {
available++
} }
} }
} return available
return blobs, proofs
} }
// Add inserts a set of blob transactions into the pool if they pass validation (both // Add inserts a set of blob transactions into the pool if they pass validation (both

View file

@ -417,8 +417,23 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
for i := range testBlobVHashes { for i := range testBlobVHashes {
copy(hashes[i][:], testBlobVHashes[i][:]) copy(hashes[i][:], testBlobVHashes[i][:])
} }
blobs, proofs := pool.GetBlobs(hashes) sidecars := pool.GetBlobs(hashes)
var blobs []*kzg4844.Blob
var proofs []*kzg4844.Proof
for idx, sidecar := range sidecars {
if sidecar == nil {
blobs = append(blobs, nil)
proofs = append(proofs, nil)
continue
}
blobHashes := sidecar.BlobHashes()
for i, hash := range blobHashes {
if hash == hashes[idx] {
blobs = append(blobs, &sidecar.Blobs[i])
proofs = append(proofs, &sidecar.Proofs[i])
}
}
}
// Cross validate what we received vs what we wanted // Cross validate what we received vs what we wanted
if len(blobs) != len(hashes) || len(proofs) != len(hashes) { if len(blobs) != len(hashes) || len(proofs) != len(hashes) {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes)) t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes))

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -1063,12 +1062,6 @@ func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
} }
} }
// GetBlobs is not supported by the legacy transaction pool, it is just here to
// implement the txpool.SubPool interface.
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
return nil, nil
}
// Has returns an indicator whether txpool has a transaction cached with the // Has returns an indicator whether txpool has a transaction cached with the
// given hash. // given hash.
func (pool *LegacyPool) Has(hash common.Hash) bool { func (pool *LegacyPool) Has(hash common.Hash) bool {

View file

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -133,11 +132,6 @@ type SubPool interface {
// given transaction hash. // given transaction hash.
GetMetadata(hash common.Hash) *TxMetadata GetMetadata(hash common.Hash) *TxMetadata
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network.
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
// ValidateTxBasics checks whether a transaction is valid according to the consensus // ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance. // rules, but does not check state-dependent validation such as sufficient balance.
// This check is meant as a static check which can be performed without holding the // This check is meant as a static check which can be performed without holding the

View file

@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -308,22 +307,6 @@ func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
return nil return nil
} }
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network.
func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
for _, subpool := range p.subpools {
// It's an ugly to assume that only one pool will be capable of returning
// anything meaningful for this call, but anythingh else requires merging
// partial responses and that's too annoying to do until we get a second
// blobpool (probably never).
if blobs, proofs := subpool.GetBlobs(vhashes); blobs != nil {
return blobs, proofs
}
}
return nil, nil
}
// Add enqueues a batch of transactions into the pool if they are valid. Due // Add enqueues a batch of transactions into the pool if they are valid. Due
// to the large transaction churn, add may postpone fully integrating the tx // to the large transaction churn, add may postpone fully integrating the tx
// to a later point to batch multiple ones together. // to a later point to batch multiple ones together.

View file

@ -138,14 +138,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip) return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
} }
if tx.Type() == types.BlobTxType { if tx.Type() == types.BlobTxType {
// Ensure the blob fee cap satisfies the minimum blob gas price return validateBlobTx(tx, head, opts)
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
} }
if tx.Type() == types.SetCodeTxType {
if len(tx.SetCodeAuthorizations()) == 0 {
return fmt.Errorf("set code tx must have at least one authorization tuple")
}
}
return nil
}
// validateBlobTx implements the blob-transaction specific validations.
func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationOptions) error {
sidecar := tx.BlobTxSidecar() sidecar := tx.BlobTxSidecar()
if sidecar == nil { if sidecar == nil {
return errors.New("missing sidecar in blob transaction") return errors.New("missing sidecar in blob transaction")
} }
// Ensure the blob fee cap satisfies the minimum blob gas price
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
}
// Ensure the number of items in the blob transaction and various side // Ensure the number of items in the blob transaction and various side
// data match up before doing any expensive validations // data match up before doing any expensive validations
hashes := tx.BlobHashes() hashes := tx.BlobHashes()
@ -156,31 +168,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if len(hashes) > maxBlobs { if len(hashes) > maxBlobs {
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs) return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
} }
// Ensure commitments, proofs and hashes are valid
if err := validateBlobSidecar(hashes, sidecar); err != nil {
return err
}
}
if tx.Type() == types.SetCodeTxType {
if len(tx.SetCodeAuthorizations()) == 0 {
return fmt.Errorf("set code tx must have at least one authorization tuple")
}
}
return nil
}
func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
if len(sidecar.Blobs) != len(hashes) { if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
} }
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
}
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil { if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err return err
} }
// Blob commitments match with the hashes in the transaction, verify the // Fork-specific sidecar checks, including proof verification.
// blobs themselves via KZG if opts.Config.IsOsaka(head.Number, head.Time) {
return validateBlobSidecarOsaka(sidecar, hashes)
}
return validateBlobSidecarLegacy(sidecar, hashes)
}
func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
if sidecar.Version != 0 {
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
}
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes))
}
for i := range sidecar.Blobs { for i := range sidecar.Blobs {
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil { if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
return fmt.Errorf("invalid blob %d: %v", i, err) return fmt.Errorf("invalid blob %d: %v", i, err)
@ -189,6 +196,16 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
return nil return nil
} }
func validateBlobSidecarOsaka(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
if sidecar.Version != 1 {
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
}
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
}
return kzg4844.VerifyCellProofs(sidecar.Blobs, sidecar.Commitments, sidecar.Proofs)
}
// ValidationOptionsWithState define certain differences between stateful transaction // ValidationOptionsWithState define certain differences between stateful transaction
// validation across the different pools without having to duplicate those checks. // validation across the different pools without having to duplicate those checks.
type ValidationOptionsWithState struct { type ValidationOptionsWithState struct {

View file

@ -22,6 +22,7 @@ func (l Log) MarshalJSON() ([]byte, error) {
TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"` TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash common.Hash `json:"blockHash" rlp:"-"` BlockHash common.Hash `json:"blockHash" rlp:"-"`
BlockTimestamp uint64 `json:"blockTimestamp" rlp:"-"`
Index hexutil.Uint `json:"logIndex" rlp:"-"` Index hexutil.Uint `json:"logIndex" rlp:"-"`
Removed bool `json:"removed" rlp:"-"` Removed bool `json:"removed" rlp:"-"`
} }
@ -33,6 +34,7 @@ func (l Log) MarshalJSON() ([]byte, error) {
enc.TxHash = l.TxHash enc.TxHash = l.TxHash
enc.TxIndex = hexutil.Uint(l.TxIndex) enc.TxIndex = hexutil.Uint(l.TxIndex)
enc.BlockHash = l.BlockHash enc.BlockHash = l.BlockHash
enc.BlockTimestamp = l.BlockTimestamp
enc.Index = hexutil.Uint(l.Index) enc.Index = hexutil.Uint(l.Index)
enc.Removed = l.Removed enc.Removed = l.Removed
return json.Marshal(&enc) return json.Marshal(&enc)
@ -48,6 +50,7 @@ func (l *Log) UnmarshalJSON(input []byte) error {
TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"` TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash *common.Hash `json:"blockHash" rlp:"-"` BlockHash *common.Hash `json:"blockHash" rlp:"-"`
BlockTimestamp *uint64 `json:"blockTimestamp" rlp:"-"`
Index *hexutil.Uint `json:"logIndex" rlp:"-"` Index *hexutil.Uint `json:"logIndex" rlp:"-"`
Removed *bool `json:"removed" rlp:"-"` Removed *bool `json:"removed" rlp:"-"`
} }
@ -80,6 +83,9 @@ func (l *Log) UnmarshalJSON(input []byte) error {
if dec.BlockHash != nil { if dec.BlockHash != nil {
l.BlockHash = *dec.BlockHash l.BlockHash = *dec.BlockHash
} }
if dec.BlockTimestamp != nil {
l.BlockTimestamp = *dec.BlockTimestamp
}
if dec.Index != nil { if dec.Index != nil {
l.Index = uint(*dec.Index) l.Index = uint(*dec.Index)
} }

View file

@ -45,6 +45,8 @@ type Log struct {
TxIndex uint `json:"transactionIndex" rlp:"-"` TxIndex uint `json:"transactionIndex" rlp:"-"`
// hash of the block in which the transaction was included // hash of the block in which the transaction was included
BlockHash common.Hash `json:"blockHash" rlp:"-"` BlockHash common.Hash `json:"blockHash" rlp:"-"`
// timestamp of the block in which the transaction was included
BlockTimestamp uint64 `json:"blockTimestamp" rlp:"-"`
// index of the log in the block // index of the log in the block
Index uint `json:"logIndex" rlp:"-"` Index uint `json:"logIndex" rlp:"-"`

View file

@ -367,6 +367,7 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
for j := 0; j < len(rs[i].Logs); j++ { for j := 0; j < len(rs[i].Logs); j++ {
rs[i].Logs[j].BlockNumber = number rs[i].Logs[j].BlockNumber = number
rs[i].Logs[j].BlockHash = hash rs[i].Logs[j].BlockHash = hash
rs[i].Logs[j].BlockTimestamp = time
rs[i].Logs[j].TxHash = rs[i].TxHash rs[i].Logs[j].TxHash = rs[i].TxHash
rs[i].Logs[j].TxIndex = uint(i) rs[i].Logs[j].TxIndex = uint(i)
rs[i].Logs[j].Index = logIndex rs[i].Logs[j].Index = logIndex

View file

@ -177,6 +177,7 @@ func getTestReceipts() Receipts {
TxHash: txs[0].Hash(), TxHash: txs[0].Hash(),
TxIndex: 0, TxIndex: 0,
BlockHash: blockHash, BlockHash: blockHash,
BlockTimestamp: blockTime,
Index: 0, Index: 0,
}, },
{ {
@ -187,6 +188,7 @@ func getTestReceipts() Receipts {
TxHash: txs[0].Hash(), TxHash: txs[0].Hash(),
TxIndex: 0, TxIndex: 0,
BlockHash: blockHash, BlockHash: blockHash,
BlockTimestamp: blockTime,
Index: 1, Index: 1,
}, },
}, },
@ -211,6 +213,7 @@ func getTestReceipts() Receipts {
TxHash: txs[1].Hash(), TxHash: txs[1].Hash(),
TxIndex: 1, TxIndex: 1,
BlockHash: blockHash, BlockHash: blockHash,
BlockTimestamp: blockTime,
Index: 2, Index: 2,
}, },
{ {
@ -221,6 +224,7 @@ func getTestReceipts() Receipts {
TxHash: txs[1].Hash(), TxHash: txs[1].Hash(),
TxIndex: 1, TxIndex: 1,
BlockHash: blockHash, BlockHash: blockHash,
BlockTimestamp: blockTime,
Index: 3, Index: 3,
}, },
}, },

View file

@ -19,9 +19,12 @@ package types
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"slices"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -55,6 +58,7 @@ type BlobTx struct {
// BlobTxSidecar contains the blobs of a blob transaction. // BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct { type BlobTxSidecar struct {
Version byte // Version
Blobs []kzg4844.Blob // Blobs needed by the blob pool Blobs []kzg4844.Blob // Blobs needed by the blob pool
Commitments []kzg4844.Commitment // Commitments needed by the blob pool Commitments []kzg4844.Commitment // Commitments needed by the blob pool
Proofs []kzg4844.Proof // Proofs needed by the blob pool Proofs []kzg4844.Proof // Proofs needed by the blob pool
@ -70,6 +74,20 @@ func (sc *BlobTxSidecar) BlobHashes() []common.Hash {
return h return h
} }
// CellProofsAt returns the cell proofs for blob with index idx.
func (sc *BlobTxSidecar) CellProofsAt(idx int) []kzg4844.Proof {
var cellProofs []kzg4844.Proof
for i := range kzg4844.CellProofsPerBlob {
index := idx*kzg4844.CellProofsPerBlob + i
if index > len(sc.Proofs) {
return nil
}
proof := sc.Proofs[index]
cellProofs = append(cellProofs, proof)
}
return cellProofs
}
// encodedSize computes the RLP size of the sidecar elements. This does NOT return the // encodedSize computes the RLP size of the sidecar elements. This does NOT return the
// encoded size of the BlobTxSidecar, it's just a helper for tx.Size(). // encoded size of the BlobTxSidecar, it's just a helper for tx.Size().
func (sc *BlobTxSidecar) encodedSize() uint64 { func (sc *BlobTxSidecar) encodedSize() uint64 {
@ -102,14 +120,55 @@ func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) erro
return nil return nil
} }
// blobTxWithBlobs is used for encoding of transactions when blobs are present. // blobTxWithBlobs represents blob tx with its corresponding sidecar.
type blobTxWithBlobs struct { // This is an interface because sidecars are versioned.
type blobTxWithBlobs interface {
tx() *BlobTx
assign(*BlobTxSidecar) error
}
type blobTxWithBlobsV0 struct {
BlobTx *BlobTx BlobTx *BlobTx
Blobs []kzg4844.Blob Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof Proofs []kzg4844.Proof
} }
type blobTxWithBlobsV1 struct {
BlobTx *BlobTx
Version byte
Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
}
func (btx *blobTxWithBlobsV0) tx() *BlobTx {
return btx.BlobTx
}
func (btx *blobTxWithBlobsV0) assign(sc *BlobTxSidecar) error {
sc.Version = 0
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
return nil
}
func (btx *blobTxWithBlobsV1) tx() *BlobTx {
return btx.BlobTx
}
func (btx *blobTxWithBlobsV1) assign(sc *BlobTxSidecar) error {
if btx.Version != 1 {
return fmt.Errorf("unsupported blob tx version %d", btx.Version)
}
sc.Version = 1
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
return nil
}
// copy creates a deep copy of the transaction data and initializes all fields. // copy creates a deep copy of the transaction data and initializes all fields.
func (tx *BlobTx) copy() TxData { func (tx *BlobTx) copy() TxData {
cpy := &BlobTx{ cpy := &BlobTx{
@ -158,9 +217,9 @@ func (tx *BlobTx) copy() TxData {
} }
if tx.Sidecar != nil { if tx.Sidecar != nil {
cpy.Sidecar = &BlobTxSidecar{ cpy.Sidecar = &BlobTxSidecar{
Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...), Blobs: slices.Clone(tx.Sidecar.Blobs),
Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...), Commitments: slices.Clone(tx.Sidecar.Commitments),
Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...), Proofs: slices.Clone(tx.Sidecar.Proofs),
} }
} }
return cpy return cpy
@ -215,48 +274,98 @@ func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
} }
func (tx *BlobTx) encode(b *bytes.Buffer) error { func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil { switch {
case tx.Sidecar == nil:
return rlp.Encode(b, tx) return rlp.Encode(b, tx)
}
inner := &blobTxWithBlobs{ case tx.Sidecar.Version == 0:
return rlp.Encode(b, &blobTxWithBlobsV0{
BlobTx: tx, BlobTx: tx,
Blobs: tx.Sidecar.Blobs, Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments, Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs, Proofs: tx.Sidecar.Proofs,
})
case tx.Sidecar.Version == 1:
return rlp.Encode(b, &blobTxWithBlobsV1{
BlobTx: tx,
Version: tx.Sidecar.Version,
Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs,
})
default:
return errors.New("unsupported sidecar version")
} }
return rlp.Encode(b, inner)
} }
func (tx *BlobTx) decode(input []byte) error { func (tx *BlobTx) decode(input []byte) error {
// Here we need to support two formats: the network protocol encoding of the tx (with // Here we need to support two outer formats: the network protocol encoding of the tx
// blobs) or the canonical encoding without blobs. // (with blobs) or the canonical encoding without blobs.
// //
// The two encodings can be distinguished by checking whether the first element of the // The canonical encoding is just a list of fields:
// input list is itself a list. //
// [chainID, nonce, ...]
//
// The network encoding is a list where the first element is the tx in the canonical encoding,
// and the remaining elements are the 'sidecar':
//
// [[chainID, nonce, ...], ...]
//
// The two outer encodings can be distinguished by checking whether the first element
// of the input list is itself a list. If it's the canonical encoding, the first
// element is the chainID, which is a number.
outerList, _, err := rlp.SplitList(input) firstElem, _, err := rlp.SplitList(input)
if err != nil { if err != nil {
return err return err
} }
firstElemKind, _, _, err := rlp.Split(outerList) firstElemKind, _, secondElem, err := rlp.Split(firstElem)
if err != nil { if err != nil {
return err return err
} }
if firstElemKind != rlp.List { if firstElemKind != rlp.List {
// Blob tx without blobs.
return rlp.DecodeBytes(input, tx) return rlp.DecodeBytes(input, tx)
} }
// It's a tx with blobs.
var inner blobTxWithBlobs // Now we know it's the network encoding with the blob sidecar. Here we again need to
if err := rlp.DecodeBytes(input, &inner); err != nil { // support multiple encodings: legacy sidecars (v0) with a blob proof, and versioned
// sidecars.
//
// The legacy encoding is:
//
// [tx, blobs, commitments, proofs]
//
// The versioned encoding is:
//
// [tx, version, blobs, ...]
//
// We can tell the two apart by checking whether the second element is the version byte.
// For legacy sidecar the second element is a list of blobs.
secondElemKind, _, _, err := rlp.Split(secondElem)
if err != nil {
return err return err
} }
*tx = *inner.BlobTx var payload blobTxWithBlobs
tx.Sidecar = &BlobTxSidecar{ if secondElemKind == rlp.List {
Blobs: inner.Blobs, // No version byte: blob sidecar v0.
Commitments: inner.Commitments, payload = new(blobTxWithBlobsV0)
Proofs: inner.Proofs, } else {
// It has a version byte. Decode as v1, version is checked by assign()
payload = new(blobTxWithBlobsV1)
} }
if err := rlp.DecodeBytes(input, payload); err != nil {
return err
}
sc := new(BlobTxSidecar)
if err := payload.assign(sc); err != nil {
return err
}
*tx = *payload.tx()
tx.Sidecar = sc
return nil return nil
} }

View file

@ -1,98 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
// eofCodeBitmap collects data locations in code.
func eofCodeBitmap(code []byte) bitvec {
// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will push zeroes onto the
// bitvector outside the bounds of the actual code.
bits := make(bitvec, len(code)/8+1+4)
return eofCodeBitmapInternal(code, bits)
}
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
// code validation.
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
var (
op = OpCode(code[pc])
numbits uint16
)
pc++
if op == RJUMPV {
// RJUMPV is unique as it has a variable sized operand.
// The total size is determined by the count byte which
// immediate follows RJUMPV. Truncation will be caught
// in other validation steps -- for now, just return a
// valid bitmap for as much of the code as is
// available.
end := uint64(len(code))
if pc >= end {
// Count missing, no more bits to mark.
return bits
}
numbits = uint16(code[pc])*2 + 3
if pc+uint64(numbits) > end {
// Jump table is truncated, mark as many bits
// as possible.
numbits = uint16(end - pc)
}
} else {
numbits = uint16(Immediates(op))
if numbits == 0 {
continue
}
}
if numbits >= 8 {
for ; numbits >= 16; numbits -= 16 {
bits.set16(pc)
pc += 16
}
for ; numbits >= 8; numbits -= 8 {
bits.set8(pc)
pc += 8
}
}
switch numbits {
case 1:
bits.set1(pc)
pc += 1
case 2:
bits.setN(set2BitsMask, pc)
pc += 2
case 3:
bits.setN(set3BitsMask, pc)
pc += 3
case 4:
bits.setN(set4BitsMask, pc)
pc += 4
case 5:
bits.setN(set5BitsMask, pc)
pc += 5
case 6:
bits.setN(set6BitsMask, pc)
pc += 6
case 7:
bits.setN(set7BitsMask, pc)
pc += 7
}
}
return bits
}

View file

@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
op = STOP op = STOP
bench.Run(op.String(), bencher) bench.Run(op.String(), bencher)
} }
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
var op OpCode
bencher := func(b *testing.B) {
code := make([]byte, analysisCodeSize)
b.SetBytes(analysisCodeSize)
for i := range code {
code[i] = byte(op)
}
bits := make(bitvec, len(code)/8+1+4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
clear(bits)
eofCodeBitmapInternal(code, bits)
}
}
for op = PUSH1; op <= PUSH32; op++ {
bench.Run(op.String(), bencher)
}
op = JUMPDEST
bench.Run(op.String(), bencher)
op = STOP
bench.Run(op.String(), bencher)
op = RJUMPV
bench.Run(op.String(), bencher)
op = EOFCREATE
bench.Run(op.String(), bencher)
}

View file

@ -459,6 +459,8 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
minPrice = 500 minPrice = 500
if maxLenOver32 { if maxLenOver32 {
gas.Add(gas, gas) gas.Add(gas, gas)
} else {
gas = big.NewInt(16)
} }
} }

View file

@ -531,176 +531,6 @@ func enable4762(jt *JumpTable) {
} }
} }
// enableEOF applies the EOF changes.
// OBS! For EOF, there are two changes:
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
// eof contracts. This one contains things like RJUMP.
// 2. The regular non-eof jumptable also needs to be modified, specifically to
// modify how EXTCODECOPY works under the hood.
//
// This method _only_ deals with case 1.
func enableEOF(jt *JumpTable) {
// Deprecate opcodes
undefined := &operation{
execute: opUndefined,
constantGas: 0,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
undefined: true,
}
jt[CALL] = undefined
jt[CALLCODE] = undefined
jt[DELEGATECALL] = undefined
jt[STATICCALL] = undefined
jt[SELFDESTRUCT] = undefined
jt[JUMP] = undefined
jt[JUMPI] = undefined
jt[PC] = undefined
jt[CREATE] = undefined
jt[CREATE2] = undefined
jt[CODESIZE] = undefined
jt[CODECOPY] = undefined
jt[EXTCODESIZE] = undefined
jt[EXTCODECOPY] = undefined
jt[EXTCODEHASH] = undefined
jt[GAS] = undefined
// Allow 0xFE to terminate sections
jt[INVALID] = &operation{
execute: opUndefined,
constantGas: 0,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
// New opcodes
jt[RJUMP] = &operation{
execute: opRjump,
constantGas: GasQuickStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RJUMPI] = &operation{
execute: opRjumpi,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
}
jt[RJUMPV] = &operation{
execute: opRjumpv,
constantGas: GasFastishStep,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
}
jt[CALLF] = &operation{
execute: opCallf,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RETF] = &operation{
execute: opRetf,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[JUMPF] = &operation{
execute: opJumpf,
constantGas: GasFastStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[EOFCREATE] = &operation{
execute: opEOFCreate,
constantGas: params.Create2Gas,
dynamicGas: gasEOFCreate,
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
memorySize: memoryEOFCreate,
}
jt[RETURNCONTRACT] = &operation{
execute: opReturnContract,
// returncontract has zero constant gas cost
dynamicGas: pureMemoryGascost,
minStack: minStack(2, 0),
maxStack: maxStack(2, 0),
memorySize: memoryReturnContract,
}
jt[DATALOAD] = &operation{
execute: opDataLoad,
constantGas: GasFastishStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[DATALOADN] = &operation{
execute: opDataLoadN,
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[DATASIZE] = &operation{
execute: opDataSize,
constantGas: GasQuickStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[DATACOPY] = &operation{
execute: opDataCopy,
constantGas: GasFastestStep,
dynamicGas: memoryCopierGas(2),
minStack: minStack(3, 0),
maxStack: maxStack(3, 0),
memorySize: memoryDataCopy,
}
jt[DUPN] = &operation{
execute: opDupN,
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
jt[SWAPN] = &operation{
execute: opSwapN,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[EXCHANGE] = &operation{
execute: opExchange,
constantGas: GasFastestStep,
minStack: minStack(0, 0),
maxStack: maxStack(0, 0),
}
jt[RETURNDATALOAD] = &operation{
execute: opReturnDataLoad,
constantGas: GasFastestStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[EXTCALL] = &operation{
execute: opExtCall,
constantGas: params.WarmStorageReadCostEIP2929,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
memorySize: memoryExtCall,
}
jt[EXTDELEGATECALL] = &operation{
execute: opExtDelegateCall,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
constantGas: params.WarmStorageReadCostEIP2929,
minStack: minStack(3, 1),
maxStack: maxStack(3, 1),
memorySize: memoryExtCall,
}
jt[EXTSTATICCALL] = &operation{
execute: opExtStaticCall,
constantGas: params.WarmStorageReadCostEIP2929,
dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
minStack: minStack(3, 1),
maxStack: maxStack(3, 1),
memorySize: memoryExtCall,
}
}
// enable7702 the EIP-7702 changes to support delegation designators. // enable7702 the EIP-7702 changes to support delegation designators.
func enable7702(jt *JumpTable) { func enable7702(jt *JumpTable) {
jt[CALL].dynamicGas = gasCallEIP7702 jt[CALL].dynamicGas = gasCallEIP7702

View file

@ -1,501 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"github.com/ethereum/go-ethereum/params"
)
const (
offsetVersion = 2
offsetTypesKind = 3
offsetCodeKind = 6
kindTypes = 1
kindCode = 2
kindContainer = 3
kindData = 4
eofFormatByte = 0xef
eof1Version = 1
maxInputItems = 127
maxOutputItems = 128
maxStackHeight = 1023
maxContainerSections = 256
)
var eofMagic = []byte{0xef, 0x00}
// HasEOFByte returns true if code starts with 0xEF byte
func HasEOFByte(code []byte) bool {
return len(code) != 0 && code[0] == eofFormatByte
}
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
func hasEOFMagic(code []byte) bool {
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
}
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
// does not verify the EOF magic is valid.
func isEOFVersion1(code []byte) bool {
return 2 < len(code) && code[2] == byte(eof1Version)
}
// Container is an EOF container object.
type Container struct {
types []*functionMetadata
codeSections [][]byte
subContainers []*Container
subContainerCodes [][]byte
data []byte
dataSize int // might be more than len(data)
}
// functionMetadata is an EOF function signature.
type functionMetadata struct {
inputs uint8
outputs uint8
maxStackHeight uint16
}
// stackDelta returns the #outputs - #inputs
func (meta *functionMetadata) stackDelta() int {
return int(meta.outputs) - int(meta.inputs)
}
// checkInputs checks the current minimum stack (stackMin) against the required inputs
// of the metadata, and returns an error if the stack is too shallow.
func (meta *functionMetadata) checkInputs(stackMin int) error {
if int(meta.inputs) > stackMin {
return ErrStackUnderflow{stackLen: stackMin, required: int(meta.inputs)}
}
return nil
}
// checkStackMax checks the if current maximum stack combined with the
// function max stack will result in a stack overflow, and if so returns an error.
func (meta *functionMetadata) checkStackMax(stackMax int) error {
newMaxStack := stackMax + int(meta.maxStackHeight) - int(meta.inputs)
if newMaxStack > int(params.StackLimit) {
return ErrStackOverflow{stackLen: newMaxStack, limit: int(params.StackLimit)}
}
return nil
}
// MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte {
// Build EOF prefix.
b := make([]byte, 2)
copy(b, eofMagic)
b = append(b, eof1Version)
// Write section headers.
b = append(b, kindTypes)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
b = append(b, kindCode)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
for _, codeSection := range c.codeSections {
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
}
var encodedContainer [][]byte
if len(c.subContainers) != 0 {
b = append(b, kindContainer)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
for _, section := range c.subContainers {
encoded := section.MarshalBinary()
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
encodedContainer = append(encodedContainer, encoded)
}
}
b = append(b, kindData)
b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
b = append(b, 0) // terminator
// Write section contents.
for _, ty := range c.types {
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
}
for _, code := range c.codeSections {
b = append(b, code...)
}
for _, section := range encodedContainer {
b = append(b, section...)
}
b = append(b, c.data...)
return b
}
// UnmarshalBinary decodes an EOF container.
func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
return c.unmarshalContainer(b, isInitcode, true)
}
// UnmarshalSubContainer decodes an EOF container that is inside another container.
func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
return c.unmarshalContainer(b, isInitcode, false)
}
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
if !hasEOFMagic(b) {
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
}
if len(b) < 14 {
return io.ErrUnexpectedEOF
}
if len(b) > params.MaxInitCodeSize {
return ErrMaxInitCodeSizeExceeded
}
if !isEOFVersion1(b) {
return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
}
var (
kind, typesSize, dataSize int
codeSizes []int
err error
)
// Parse type section header.
kind, typesSize, err = parseSection(b, offsetTypesKind)
if err != nil {
return err
}
if kind != kindTypes {
return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
}
if typesSize < 4 || typesSize%4 != 0 {
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
}
if typesSize/4 > 1024 {
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
}
// Parse code section header.
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
if err != nil {
return err
}
if kind != kindCode {
return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
}
if len(codeSizes) != typesSize/4 {
return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", errInvalidCodeSize, typesSize/4, len(codeSizes))
}
// Parse (optional) container section header.
var containerSizes []int
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
if offset < len(b) && b[offset] == kindContainer {
kind, containerSizes, err = parseSectionList(b, offset)
if err != nil {
return err
}
if kind != kindContainer {
panic("somethings wrong")
}
if len(containerSizes) == 0 {
return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
}
offset = offset + 2 + 2*len(containerSizes) + 1
}
// Parse data section header.
kind, dataSize, err = parseSection(b, offset)
if err != nil {
return err
}
if kind != kindData {
return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
}
c.dataSize = dataSize
// Check for terminator.
offsetTerminator := offset + 3
if len(b) < offsetTerminator {
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
}
if b[offsetTerminator] != 0 {
return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
}
// Verify overall container size.
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
if len(containerSizes) != 0 {
expectedSize += sum(containerSizes)
}
if len(b) < expectedSize-dataSize {
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
}
// Only check that the expected size is not exceed on non-initcode
if (!topLevel || !isInitcode) && len(b) > expectedSize {
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
}
// Parse types section.
idx := offsetTerminator + 1
var types = make([]*functionMetadata, 0, typesSize/4)
for i := 0; i < typesSize/4; i++ {
sig := &functionMetadata{
inputs: b[idx+i*4],
outputs: b[idx+i*4+1],
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
}
if sig.inputs > maxInputItems {
return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
}
if sig.outputs > maxOutputItems {
return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
}
if sig.maxStackHeight > maxStackHeight {
return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
}
types = append(types, sig)
}
if types[0].inputs != 0 || types[0].outputs != 0x80 {
return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
}
c.types = types
// Parse code sections.
idx += typesSize
codeSections := make([][]byte, len(codeSizes))
for i, size := range codeSizes {
if size == 0 {
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
}
codeSections[i] = b[idx : idx+size]
idx += size
}
c.codeSections = codeSections
// Parse the optional container sizes.
if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections {
return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
}
subContainerCodes := make([][]byte, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes {
if size == 0 || idx+size > len(b) {
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
}
subC := new(Container)
end := min(idx+size, len(b))
if err := subC.unmarshalContainer(b[idx:end], isInitcode, false); err != nil {
if topLevel {
return fmt.Errorf("%w in sub container %d", err, i)
}
return err
}
subContainers = append(subContainers, subC)
subContainerCodes = append(subContainerCodes, b[idx:end])
idx += size
}
c.subContainers = subContainers
c.subContainerCodes = subContainerCodes
}
//Parse data section.
end := len(b)
if !isInitcode {
end = min(idx+dataSize, len(b))
}
if topLevel && len(b) != idx+dataSize {
return errTruncatedTopLevelContainer
}
c.data = b[idx:end]
return nil
}
// ValidateCode validates each code section of the container against the EOF v1
// rule set.
func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
refBy := notRefByEither
if isInitCode {
refBy = refByEOFCreate
}
return c.validateSubContainer(jt, refBy)
}
func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
visited := make(map[int]struct{})
subContainerVisited := make(map[int]int)
toVisit := []int{0}
for len(toVisit) > 0 {
// TODO check if this can be used as a DOS
// Theres and edge case here where we mark something as visited that we visit before,
// This should not trigger a re-visit
// e.g. 0 -> 1, 2, 3
// 1 -> 2, 3
// should not mean 2 and 3 should be visited twice
var (
index = toVisit[0]
code = c.codeSections[index]
)
if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
if err != nil {
return err
}
visited[index] = struct{}{}
// Mark all sections that can be visited from here.
for idx := range res.visitedCode {
if _, ok := visited[idx]; !ok {
toVisit = append(toVisit, idx)
}
}
// Mark all subcontainer that can be visited from here.
for idx, reference := range res.visitedSubContainers {
// Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
if ref, ok := subContainerVisited[idx]; ok && ref != reference {
return errors.New("section referenced by both EOFCreate and ReturnContract")
}
subContainerVisited[idx] = reference
}
if refBy == refByReturnContract && res.isInitCode {
return errIncompatibleContainerKind
}
if refBy == refByEOFCreate && res.isRuntime {
return errIncompatibleContainerKind
}
}
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) {
return errUnreachableCode
}
for idx, container := range c.subContainers {
reference, ok := subContainerVisited[idx]
if !ok {
return errOrphanedSubcontainer
}
if err := container.validateSubContainer(jt, reference); err != nil {
return err
}
}
return nil
}
// parseSection decodes a (kind, size) pair from an EOF header.
func parseSection(b []byte, idx int) (kind, size int, err error) {
if idx+3 >= len(b) {
return 0, 0, io.ErrUnexpectedEOF
}
kind = int(b[idx])
size = int(binary.BigEndian.Uint16(b[idx+1:]))
return kind, size, nil
}
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
// header.
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
if idx >= len(b) {
return 0, nil, io.ErrUnexpectedEOF
}
kind = int(b[idx])
list, err = parseList(b, idx+1)
if err != nil {
return 0, nil, err
}
return kind, list, nil
}
// parseList decodes a list of uint16..
func parseList(b []byte, idx int) ([]int, error) {
if len(b) < idx+2 {
return nil, io.ErrUnexpectedEOF
}
count := binary.BigEndian.Uint16(b[idx:])
if len(b) <= idx+2+int(count)*2 {
return nil, io.ErrUnexpectedEOF
}
list := make([]int, count)
for i := 0; i < int(count); i++ {
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
}
return list, nil
}
// parseUint16 parses a 16 bit unsigned integer.
func parseUint16(b []byte) (int, error) {
if len(b) < 2 {
return 0, io.ErrUnexpectedEOF
}
return int(binary.BigEndian.Uint16(b)), nil
}
// parseInt16 parses a 16 bit signed integer.
func parseInt16(b []byte) int {
return int(int16(b[1]) | int16(b[0])<<8)
}
// sum computes the sum of a slice.
func sum(list []int) (s int) {
for _, n := range list {
s += n
}
return
}
func (c *Container) String() string {
var output = []string{
"Header",
fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
fmt.Sprintf(" - KindType: %02x", kindTypes),
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
fmt.Sprintf(" - KindCode: %02x", kindCode),
fmt.Sprintf(" - KindData: %02x", kindData),
fmt.Sprintf(" - DataSize: %04x", len(c.data)),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
}
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
if len(c.subContainers) > 0 {
for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
}
}
output = append(output, "Body")
for i, typ := range c.types {
output = append(output, fmt.Sprintf(" - Type %v: %x", i,
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
}
for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
}
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
return strings.Join(output, "\n")
}

View file

@ -1,235 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
import (
"fmt"
"github.com/ethereum/go-ethereum/params"
)
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
var (
maxStackHeight = int(metadata[section].inputs)
visitCount = 0
next = make([]int, 0, 1)
)
var (
stackBoundsMax = make([]uint16, len(code))
stackBoundsMin = make([]uint16, len(code))
)
setBounds := func(pos, min, maxi int) {
// The stackboundMax slice is a bit peculiar. We use `0` to denote
// not set. Therefore, we use `1` to represent the value `0`, and so on.
// So if the caller wants to store `1` as max bound, we internally store it as
// `2`.
if stackBoundsMax[pos] == 0 { // Not yet set
visitCount++
}
if maxi < 65535 {
stackBoundsMax[pos] = uint16(maxi + 1)
}
stackBoundsMin[pos] = uint16(min)
maxStackHeight = max(maxStackHeight, maxi)
}
getStackMaxMin := func(pos int) (ok bool, min, max int) {
maxi := stackBoundsMax[pos]
if maxi == 0 { // Not yet set
return false, 0, 0
}
return true, int(stackBoundsMin[pos]), int(maxi - 1)
}
// set the initial stack bounds
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
qualifiedExit := false
for pos := 0; pos < len(code); pos++ {
op := OpCode(code[pos])
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
if !ok {
return 0, errUnreachableCode
}
switch op {
case CALLF:
arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg]
if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
delta := newSection.stackDelta()
currentStackMax += delta
currentStackMin += delta
case RETF:
/* From the spec:
> for RETF the following must hold: stack_height_max == stack_height_min == types[current_code_index].outputs,
In other words: RETF must unambiguously return all items remaining on the stack.
*/
if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
}
numOutputs := int(metadata[section].outputs)
if numOutputs >= maxOutputItems {
return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
}
if numOutputs != currentStackMin {
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, numOutputs, currentStackMin, pos)
}
qualifiedExit = true
case JUMPF:
arg, _ := parseUint16(code[pos+1:])
newSection := metadata[arg]
if err := newSection.checkStackMax(currentStackMax); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
if newSection.outputs == 0x80 {
if err := newSection.checkInputs(currentStackMin); err != nil {
return 0, fmt.Errorf("%w: at pos %d", err, pos)
}
} else {
if currentStackMax != currentStackMin {
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
}
wantStack := int(metadata[section].outputs) - newSection.stackDelta()
if currentStackMax != wantStack {
return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
}
}
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
case DUPN:
arg := int(code[pos+1]) + 1
if want, have := arg, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
case SWAPN:
arg := int(code[pos+1]) + 1
if want, have := arg+1, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
case EXCHANGE:
arg := int(code[pos+1])
n := arg>>4 + 1
m := arg&0x0f + 1
if want, have := n+m+1, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
default:
if want, have := jt[op].minStack, currentStackMin; want > have {
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
}
}
if !terminals[op] && op != CALLF {
change := int(params.StackLimit) - jt[op].maxStack
currentStackMax += change
currentStackMin += change
}
next = next[:0]
switch op {
case RJUMP:
nextPos := pos + 2 + parseInt16(code[pos+1:])
next = append(next, nextPos)
// We set the stack bounds of the destination
// and skip the argument, only for RJUMP, all other opcodes are handled later
if nextPos+1 < pos {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok {
return 0, errInvalidBackwardJump
}
if nextMax != currentStackMax || nextMin != currentStackMin {
return 0, errInvalidMaxStackHeight
}
} else {
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
if !ok {
setBounds(nextPos+1, currentStackMin, currentStackMax)
} else {
setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
}
}
case RJUMPI:
arg := parseInt16(code[pos+1:])
next = append(next, pos+2)
next = append(next, pos+2+arg)
case RJUMPV:
count := int(code[pos+1]) + 1
next = append(next, pos+1+2*count)
for i := 0; i < count; i++ {
arg := parseInt16(code[pos+2+2*i:])
next = append(next, pos+1+2*count+arg)
}
default:
if imm := int(immediates[op]); imm != 0 {
next = append(next, pos+imm)
} else {
// Simple op, no operand.
next = append(next, pos)
}
}
if op != RJUMP && !terminals[op] {
for _, instr := range next {
nextPC := instr + 1
if nextPC >= len(code) {
return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
}
if nextPC > pos {
// target reached via forward jump or seq flow
ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok {
setBounds(nextPC, currentStackMin, currentStackMax)
} else {
setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
}
} else {
// target reached via backwards jump
ok, nextMin, nextMax := getStackMaxMin(nextPC)
if !ok {
return 0, errInvalidBackwardJump
}
if currentStackMax != nextMax {
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
}
if currentStackMin != nextMin {
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
}
}
}
}
if op == RJUMP {
pos += 2 // skip the immediate
} else {
pos = next[0]
}
}
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
}
if maxStackHeight >= int(params.StackLimit) {
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
}
if maxStackHeight != int(metadata[section].maxStackHeight) {
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
}
return visitCount, nil
}

View file

@ -1,70 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
// immediate denotes how many immediate bytes an operation uses. This information
// is not required during runtime, only during EOF-validation, so is not
// places into the op-struct in the instruction table.
// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
// the given time is performed elsewhere.
var immediates [256]uint8
// terminals denotes whether instructions can be the final opcode in a code section.
// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
// the given time is performed elsewhere.
var terminals [256]bool
func init() {
// The legacy pushes
for i := uint8(1); i < 33; i++ {
immediates[int(PUSH0)+int(i)] = i
}
// And new eof opcodes.
immediates[DATALOADN] = 2
immediates[RJUMP] = 2
immediates[RJUMPI] = 2
immediates[RJUMPV] = 3
immediates[CALLF] = 2
immediates[JUMPF] = 2
immediates[DUPN] = 1
immediates[SWAPN] = 1
immediates[EXCHANGE] = 1
immediates[EOFCREATE] = 1
immediates[RETURNCONTRACT] = 1
// Define the terminals.
terminals[STOP] = true
terminals[RETF] = true
terminals[JUMPF] = true
terminals[RETURNCONTRACT] = true
terminals[RETURN] = true
terminals[REVERT] = true
terminals[INVALID] = true
}
// Immediates returns the number bytes of immediates (argument not from
// stack but from code) a given opcode has.
// OBS:
// - This function assumes EOF instruction-set. It cannot be upon in
// a. pre-EOF code
// b. post-EOF but legacy code
// - RJUMPV is unique as it has a variable sized operand. The total size is
// determined by the count byte which immediately follows RJUMPV. This method
// will return '3' for RJUMPV, which is the minimum.
func Immediates(op OpCode) int {
return int(immediates[op])
}

View file

@ -1,112 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
// opRjump implements the RJUMP opcode.
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRjumpi implements the RJUMPI opcode
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRjumpv implements the RJUMPV opcode
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opCallf implements the CALLF opcode
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opRetf implements the RETF opcode
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opJumpf implements the JUMPF opcode
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opEOFCreate implements the EOFCREATE opcode
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opReturnContract implements the RETURNCONTRACT opcode
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataLoad implements the DATALOAD opcode
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataLoadN implements the DATALOADN opcode
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataSize implements the DATASIZE opcode
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDataCopy implements the DATACOPY opcode
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opDupN implements the DUPN opcode
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opSwapN implements the SWAPN opcode
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExchange implements the EXCHANGE opcode
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opReturnDataLoad implements the RETURNDATALOAD opcode
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtCall implements the EOFCREATE opcode
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtDelegateCall implements the EXTDELEGATECALL opcode
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}
// opExtStaticCall implements the EXTSTATICCALL opcode
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
}

View file

@ -1,117 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
import (
"encoding/hex"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestEOFMarshaling(t *testing.T) {
for i, test := range []struct {
want Container
err error
}{
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{
{inputs: 0, outputs: 0x80, maxStackHeight: 1},
{inputs: 2, outputs: 3, maxStackHeight: 4},
{inputs: 1, outputs: 1, maxStackHeight: 1},
},
codeSections: [][]byte{
common.Hex2Bytes("604200"),
common.Hex2Bytes("6042604200"),
common.Hex2Bytes("00"),
},
data: []byte{},
},
},
} {
var (
b = test.want.MarshalBinary()
got Container
)
t.Logf("b: %#x", b)
if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
}
if !reflect.DeepEqual(got, test.want) {
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
}
}
}
func TestEOFSubcontainer(t *testing.T) {
var subcontainer = new(Container)
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
t.Fatal(err)
}
container := Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
subContainers: []*Container{subcontainer},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
}
var (
b = container.MarshalBinary()
got Container
)
if err := got.UnmarshalBinary(b, true); err != nil {
t.Fatal(err)
}
if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
t.Fatalf("invalid marshalling, want %v got %v", b, res)
}
}
func TestMarshaling(t *testing.T) {
tests := []string{
"EF000101000402000100040400000000800000E0000000",
"ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
}
for i, test := range tests {
s, err := hex.DecodeString(test)
if err != nil {
t.Fatalf("test %d: error decoding: %v", i, err)
}
var got Container
if err := got.UnmarshalBinary(s, true); err != nil {
t.Fatalf("test %d: got error %v", i, err)
}
}
}

View file

@ -1,255 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
import (
"errors"
"fmt"
"io"
)
// Below are all possible errors that can occur during validation of
// EOF containers.
var (
errInvalidMagic = errors.New("invalid magic")
errUndefinedInstruction = errors.New("undefined instruction")
errTruncatedImmediate = errors.New("truncated immediate")
errInvalidSectionArgument = errors.New("invalid section argument")
errInvalidCallArgument = errors.New("callf into non-returning section")
errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
errInvalidJumpDest = errors.New("invalid jump destination")
errInvalidBackwardJump = errors.New("invalid backward jump")
errInvalidOutputs = errors.New("invalid number of outputs")
errInvalidMaxStackHeight = errors.New("invalid max stack height")
errInvalidCodeTermination = errors.New("invalid code termination")
errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
errIncompatibleContainerKind = errors.New("incompatible container kind")
errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
errTruncatedTopLevelContainer = errors.New("truncated top level container")
errUnreachableCode = errors.New("unreachable code")
errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
errInvalidVersion = errors.New("invalid version")
errMissingTypeHeader = errors.New("missing type header")
errInvalidTypeSize = errors.New("invalid type section size")
errMissingCodeHeader = errors.New("missing code header")
errInvalidCodeSize = errors.New("invalid code size")
errInvalidContainerSectionSize = errors.New("invalid container section size")
errMissingDataHeader = errors.New("missing data header")
errMissingTerminator = errors.New("missing header terminator")
errTooManyInputs = errors.New("invalid type content, too many inputs")
errTooManyOutputs = errors.New("invalid type content, too many outputs")
errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
errInvalidContainerSize = errors.New("invalid container size")
)
const (
notRefByEither = iota
refByReturnContract
refByEOFCreate
)
type validationResult struct {
visitedCode map[int]struct{}
visitedSubContainers map[int]int
isInitCode bool
isRuntime bool
}
// validateCode validates the code parameter against the EOF v1 validity requirements.
func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
var (
i = 0
// Tracks the number of actual instructions in the code (e.g.
// non-immediate values). This is used at the end to determine
// if each instruction is reachable.
count = 0
op OpCode
analysis bitvec
visitedCode map[int]struct{}
visitedSubcontainers map[int]int
hasReturnContract bool
hasStop bool
)
// This loop visits every single instruction and verifies:
// * if the instruction is valid for the given jump table.
// * if the instruction has an immediate value, it is not truncated.
// * if performing a relative jump, all jump destinations are valid.
// * if changing code sections, the new code section index is valid and
// will not cause a stack overflow.
for i < len(code) {
count++
op = OpCode(code[i])
if jt[op].undefined {
return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
}
size := int(immediates[op])
if size != 0 && len(code) <= i+size {
return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
}
switch op {
case RJUMP, RJUMPI:
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
return nil, err
}
case RJUMPV:
maxSize := int(code[i+1])
length := maxSize + 1
if len(code) <= i+length {
return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
}
offset := i + 2
for j := 0; j < length; j++ {
if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
return nil, err
}
}
i += 2 * maxSize
case CALLF:
arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
}
if container.types[arg].outputs == 0x80 {
return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case JUMPF:
arg, _ := parseUint16(code[i+1:])
if arg >= len(container.types) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
}
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
}
if visitedCode == nil {
visitedCode = make(map[int]struct{})
}
visitedCode[arg] = struct{}{}
case DATALOADN:
arg, _ := parseUint16(code[i+1:])
// TODO why are we checking this? We should just pad
if arg+32 > len(container.data) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
}
case RETURNCONTRACT:
if !isInitCode {
return nil, errIncompatibleContainerKind
}
arg := int(code[i+1])
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
// We need to store per subcontainer how it was referenced
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
if hasStop {
return nil, errStopAndReturnContract
}
hasReturnContract = true
visitedSubcontainers[arg] = refByReturnContract
case EOFCREATE:
arg := int(code[i+1])
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
}
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)
}
// We need to store per subcontainer how it was referenced
if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
}
visitedSubcontainers[arg] = refByEOFCreate
case STOP, RETURN:
if isInitCode {
return nil, errStopInInitCode
}
if hasReturnContract {
return nil, errStopAndReturnContract
}
hasStop = true
}
i += size + 1
}
// Code sections may not "fall through" and require proper termination.
// Therefore, the last instruction must be considered terminal or RJUMP.
if !terminals[op] && op != RJUMP {
return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
}
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
return nil, err
} else if paths != count {
// TODO(matt): return actual position of unreachable code
return nil, errUnreachableCode
}
return &validationResult{
visitedCode: visitedCode,
visitedSubContainers: visitedSubcontainers,
isInitCode: hasReturnContract,
isRuntime: hasStop,
}, nil
}
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
if len(code) < imm+2 {
return io.ErrUnexpectedEOF
}
if analysis != nil && *analysis == nil {
*analysis = eofCodeBitmap(code)
}
offset := parseInt16(code[imm:])
dest := from + offset
if dest < 0 || dest >= length {
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
}
if !analysis.codeSegment(uint64(dest)) {
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
}
return nil
}
//// disasm is a helper utility to show a sequence of comma-separated operations,
//// with immediates shown inline,
//// e.g: PUSH1(0x00),EOFCREATE(0x00),
//func disasm(code []byte) string {
// var ops []string
// for i := 0; i < len(code); i++ {
// var op string
// if args := immediates[code[i]]; args > 0 {
// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
// i += int(args)
// } else {
// op = OpCode(code[i]).String()
// }
// ops = append(ops, op)
// }
// return strings.Join(ops, ",")
//}

View file

@ -1,517 +0,0 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// 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 vm
import (
"encoding/binary"
"errors"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
func TestValidateCode(t *testing.T) {
for i, test := range []struct {
code []byte
section int
metadata []*functionMetadata
err error
}{
{
code: []byte{
byte(CALLER),
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
},
{
code: []byte{
byte(CALLF), 0x00, 0x00,
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
},
{
code: []byte{
byte(ADDRESS),
byte(CALLF), 0x00, 0x00,
byte(POP),
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
},
{
code: []byte{
byte(CALLER),
byte(POP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidCodeTermination,
},
{
code: []byte{
byte(RJUMP),
byte(0x00),
byte(0x01),
byte(CALLER),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: errUnreachableCode,
},
{
code: []byte{
byte(PUSH1),
byte(0x42),
byte(ADD),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: ErrStackUnderflow{stackLen: 1, required: 2},
},
{
code: []byte{
byte(PUSH1),
byte(0x42),
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
err: errInvalidMaxStackHeight,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPI),
byte(0x00),
byte(0x01),
byte(PUSH1),
byte(0x42), // jumps to here
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidJumpDest,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPV),
byte(0x01),
byte(0x00),
byte(0x01),
byte(0x00),
byte(0x02),
byte(PUSH1),
byte(0x42), // jumps to here
byte(POP), // and here
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errInvalidJumpDest,
},
{
code: []byte{
byte(PUSH0),
byte(RJUMPV),
byte(0x00),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
err: errTruncatedImmediate,
},
{
code: []byte{
byte(RJUMP), 0x00, 0x03,
byte(JUMPDEST), // this code is unreachable to forward jumps alone
byte(JUMPDEST),
byte(RETURN),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RJUMP), 0xff, 0xef,
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
err: errUnreachableCode,
},
{
code: []byte{
byte(PUSH1), 1,
byte(RJUMPI), 0x00, 0x03,
byte(JUMPDEST),
byte(JUMPDEST),
byte(STOP),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RETURN),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
},
{
code: []byte{
byte(PUSH1), 1,
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
byte(JUMPDEST),
byte(JUMPDEST),
byte(STOP),
byte(PUSH1), 20,
byte(PUSH1), 39,
byte(PUSH1), 0x00,
byte(DATACOPY),
byte(PUSH1), 20,
byte(PUSH1), 0x00,
byte(RETURN),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
},
{
code: []byte{
byte(STOP),
byte(STOP),
byte(INVALID),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
err: errUnreachableCode,
},
{
code: []byte{
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
err: errInvalidOutputs,
},
{
code: []byte{
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
},
{
code: []byte{
byte(CALLF), 0x00, 0x01,
byte(POP),
byte(STOP),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
},
{
code: []byte{
byte(ORIGIN),
byte(ORIGIN),
byte(CALLF), 0x00, 0x01,
byte(POP),
byte(RETF),
},
section: 0,
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
},
} {
container := &Container{
types: test.metadata,
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
_, err := validateCode(test.code, test.section, container, &eofInstructionSet, false)
if !errors.Is(err, test.err) {
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
}
}
}
// BenchmarkRJUMPI tries to benchmark the RJUMPI opcode validation
// For this we do a bunch of RJUMPIs that jump backwards (in a potential infinite loop).
func BenchmarkRJUMPI(b *testing.B) {
snippet := []byte{
byte(PUSH0),
byte(RJUMPI), 0xFF, 0xFC,
}
code := []byte{}
for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
code = append(code, snippet...)
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkRJUMPV tries to benchmark the validation of the RJUMPV opcode
// for this we set up as many RJUMPV opcodes with a full jumptable (containing 0s) as possible.
func BenchmarkRJUMPV(b *testing.B) {
snippet := []byte{
byte(PUSH0),
byte(RJUMPV),
0xff, // count
0x00, 0x00,
}
for i := 0; i < 255; i++ {
snippet = append(snippet, []byte{0x00, 0x00}...)
}
code := []byte{}
for i := 0; i < 24576/len(snippet)-1; i++ {
code = append(code, snippet...)
}
code = append(code, byte(PUSH0))
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
// For this we set up code that calls into 1024 code sections which can either
// - just contain a RETF opcode
// - or code to again call into 1024 code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
// First container
container.codeSections = append(container.codeSections, append(code, byte(STOP)))
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(RETF),
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
for i := 0; i < 12; i++ {
container.codeSections[i+1] = append(code, byte(RETF))
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
var container2 Container
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation2 tries to benchmark the code validation for the CALLF/RETF operation.
// For this we set up code that calls into 1024 code sections which
// - contain calls to some other code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation2(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
// First container
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(CALLF), 0x03, 0xE8,
byte(CALLF), 0x03, 0xE9,
byte(CALLF), 0x03, 0xF0,
byte(CALLF), 0x03, 0xF1,
byte(CALLF), 0x03, 0xF2,
byte(CALLF), 0x03, 0xF3,
byte(CALLF), 0x03, 0xF4,
byte(CALLF), 0x03, 0xF5,
byte(CALLF), 0x03, 0xF6,
byte(CALLF), 0x03, 0xF7,
byte(CALLF), 0x03, 0xF8,
byte(CALLF), 0x03, 0xF,
byte(RETF),
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
var container2 Container
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkEOFValidation3 tries to benchmark the code validation for the CALLF/RETF and RJUMPI/V operations.
// For this we set up code that calls into 1024 code sections which either
// - contain an RJUMP opcode
// - contain calls to other code sections
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation3(b *testing.B) {
var container Container
var code []byte
snippet := []byte{
byte(PUSH0),
byte(RJUMPV),
0xff, // count
0x00, 0x00,
}
for i := 0; i < 255; i++ {
snippet = append(snippet, []byte{0x00, 0x00}...)
}
code = append(code, snippet...)
// First container, calls into all other containers
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
// Other containers
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
// Other containers
for i := 0; i < 68; i++ {
container.codeSections[i+1] = append(snippet, byte(RETF))
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
}
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
}
b.ResetTimer()
b.ReportMetric(float64(len(bin)), "bytes")
for i := 0; i < b.N; i++ {
for k := 0; k < 40; k++ {
var container2 Container
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkRJUMPI_2(b *testing.B) {
code := []byte{
byte(PUSH0),
byte(RJUMPI), 0xFF, 0xFC,
}
for i := 0; i < params.MaxCodeSize/4-1; i++ {
code = append(code, byte(PUSH0))
x := -4 * i
code = append(code, byte(RJUMPI))
code = binary.BigEndian.AppendUint16(code, uint16(x))
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
if err != nil {
b.Fatal(err)
}
}
}
func FuzzUnmarshalBinary(f *testing.F) {
f.Fuzz(func(_ *testing.T, input []byte) {
var container Container
container.UnmarshalBinary(input, true)
})
}
func FuzzValidate(f *testing.F) {
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
var container Container
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
validateCode(code, 0, &container, &pragueInstructionSet, true)
})
}

View file

@ -485,20 +485,3 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
} }
return gas, nil return gas, nil
} }
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
// deducted in the opcode itself, since it depends on the immediate
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
}

View file

@ -62,7 +62,6 @@ var (
cancunInstructionSet = newCancunInstructionSet() cancunInstructionSet = newCancunInstructionSet()
verkleInstructionSet = newVerkleInstructionSet() verkleInstructionSet = newVerkleInstructionSet()
pragueInstructionSet = newPragueInstructionSet() pragueInstructionSet = newPragueInstructionSet()
eofInstructionSet = newEOFInstructionSetForTesting()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
@ -92,16 +91,6 @@ func newVerkleInstructionSet() JumpTable {
return validate(instructionSet) return validate(instructionSet)
} }
func NewEOFInstructionSetForTesting() JumpTable {
return newEOFInstructionSetForTesting()
}
func newEOFInstructionSetForTesting() JumpTable {
instructionSet := newPragueInstructionSet()
enableEOF(&instructionSet)
return validate(instructionSet)
}
func newPragueInstructionSet() JumpTable { func newPragueInstructionSet() JumpTable {
instructionSet := newCancunInstructionSet() instructionSet := newCancunInstructionSet()
enable7702(&instructionSet) // EIP-7702 Setcode transaction type enable7702(&instructionSet) // EIP-7702 Setcode transaction type

View file

@ -120,19 +120,3 @@ func memoryRevert(stack *Stack) (uint64, bool) {
func memoryLog(stack *Stack) (uint64, bool) { func memoryLog(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1)) return calcMemSize64(stack.Back(0), stack.Back(1))
} }
func memoryExtCall(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(1), stack.Back(2))
}
func memoryDataCopy(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(2))
}
func memoryEOFCreate(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(2), stack.Back(3))
}
func memoryReturnContract(stack *Stack) (uint64, bool) {
return calcMemSize64(stack.Back(0), stack.Back(1))
}

View file

@ -34,6 +34,8 @@ var (
blobT = reflect.TypeOf(Blob{}) blobT = reflect.TypeOf(Blob{})
commitmentT = reflect.TypeOf(Commitment{}) commitmentT = reflect.TypeOf(Commitment{})
proofT = reflect.TypeOf(Proof{}) proofT = reflect.TypeOf(Proof{})
CellProofsPerBlob = 128
) )
// Blob represents a 4844 data blob. // Blob represents a 4844 data blob.
@ -149,6 +151,16 @@ func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
return gokzgVerifyBlobProof(blob, commitment, proof) return gokzgVerifyBlobProof(blob, commitment, proof)
} }
// VerifyCellProofs verifies a batch of proofs corresponding to the blobs and commitments.
// Expects length of blobs and commitments to be equal.
// Expects length of proofs be 128 * length of blobs.
func VerifyCellProofs(blobs []Blob, commitments []Commitment, proofs []Proof) error {
if useCKZG.Load() {
return ckzgVerifyCellProofBatch(blobs, commitments, proofs)
}
return gokzgVerifyCellProofBatch(blobs, commitments, proofs)
}
// ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against // ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment. // the commitment.
// //

View file

@ -149,3 +149,44 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
} }
return p, nil return p, nil
} }
// ckzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
ckzgIniter.Do(ckzgInit)
var (
proofs = make([]ckzg4844.Bytes48, len(cellProofs))
commits = make([]ckzg4844.Bytes48, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]ckzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = (ckzg4844.Bytes48)(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, (ckzg4844.Bytes48)(commitment))
}
}
// Compute the cells and cell indices
for i := range blobs {
cellsI, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(&blobs[i]))
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}
valid, err := ckzg4844.VerifyCellKZGProofBatch(commits, cellIndices, cells, proofs)
if err != nil {
return err
}
if !valid {
return errors.New("invalid proof")
}
return nil
}

View file

@ -61,6 +61,11 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
panic("unsupported platform") panic("unsupported platform")
} }
// ckzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, proof []Proof) error {
panic("unsupported platform")
}
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against // ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment. // the commitment.
// //

View file

@ -114,3 +114,37 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
} }
return p, nil return p, nil
} }
// gokzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment.
func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
gokzgIniter.Do(gokzgInit)
var (
proofs = make([]gokzg4844.KZGProof, len(cellProofs))
commits = make([]gokzg4844.KZGCommitment, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]*gokzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = gokzg4844.KZGProof(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, gokzg4844.KZGCommitment(commitment))
}
}
// Compute the cell and cell indices
for i := range blobs {
cellsI, err := context.ComputeCells((*gokzg4844.Blob)(&blobs[i]), 2)
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}
return context.VerifyCellKZGProofBatch(commits, cellIndices, cells[:], proofs)
}

View file

@ -193,3 +193,40 @@ func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) {
VerifyBlobProof(blob, commitment, proof) VerifyBlobProof(blob, commitment, proof)
} }
} }
func TestCKZGCells(t *testing.T) { testKZGCells(t, true) }
func TestGoKZGCells(t *testing.T) { testKZGCells(t, false) }
func testKZGCells(t *testing.T, ckzg bool) {
if ckzg && !ckzgAvailable {
t.Skip("CKZG unavailable in this test build")
}
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
blob1 := randBlob()
blob2 := randBlob()
commitment1, err := BlobToCommitment(blob1)
if err != nil {
t.Fatalf("failed to create KZG commitment from blob: %v", err)
}
commitment2, err := BlobToCommitment(blob2)
if err != nil {
t.Fatalf("failed to create KZG commitment from blob: %v", err)
}
proofs1, err := ComputeCellProofs(blob1)
if err != nil {
t.Fatalf("failed to create KZG proof at point: %v", err)
}
proofs2, err := ComputeCellProofs(blob2)
if err != nil {
t.Fatalf("failed to create KZG proof at point: %v", err)
}
proofs := append(proofs1, proofs2...)
blobs := []Blob{*blob1, *blob2}
if err := VerifyCellProofs(blobs, []Commitment{commitment1, commitment2}, proofs); err != nil {
t.Fatalf("failed to verify KZG proof at point: %v", err)
}
}

View file

@ -92,6 +92,7 @@ type Ethereum struct {
// core protocol objects // core protocol objects
config *ethconfig.Config config *ethconfig.Config
txPool *txpool.TxPool txPool *txpool.TxPool
blobTxPool *blobpool.BlobPool
localTxTracker *locals.TxTracker localTxTracker *locals.TxTracker
blockchain *core.BlockChain blockchain *core.BlockChain
@ -284,9 +285,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if config.BlobPool.Datadir != "" { if config.BlobPool.Datadir != "" {
config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir) config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
} }
blobPool := blobpool.New(config.BlobPool, eth.blockchain, legacyPool.HasPendingAuth) eth.blobTxPool = blobpool.New(config.BlobPool, eth.blockchain, legacyPool.HasPendingAuth)
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, blobPool}) eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, eth.blobTxPool})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -395,6 +396,7 @@ func (s *Ethereum) Miner() *miner.Miner { return s.miner }
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool } func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool }
func (s *Ethereum) BlobTxPool() *blobpool.BlobPool { return s.blobTxPool }
func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) Engine() consensus.Engine { return s.engine }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) IsListening() bool { return true } // Always listening

View file

@ -18,6 +18,7 @@
package catalyst package catalyst
import ( import (
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
@ -30,10 +31,12 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -92,7 +95,9 @@ var caps = []string{
"engine_getPayloadV2", "engine_getPayloadV2",
"engine_getPayloadV3", "engine_getPayloadV3",
"engine_getPayloadV4", "engine_getPayloadV4",
"engine_getPayloadV5",
"engine_getBlobsV1", "engine_getBlobsV1",
"engine_getBlobsV2",
"engine_newPayloadV1", "engine_newPayloadV1",
"engine_newPayloadV2", "engine_newPayloadV2",
"engine_newPayloadV3", "engine_newPayloadV3",
@ -112,6 +117,17 @@ var caps = []string{
"engine_getClientVersionV1", "engine_getClientVersionV1",
} }
var (
// Number of blobs requested via getBlobsV2
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
// Number of blobs requested via getBlobsV2 that are present in the blobpool
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
// Number of times getBlobsV2 responded with “hit”
getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
// Number of times getBlobsV2 responded with “miss”
getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
)
type ConsensusAPI struct { type ConsensusAPI struct {
eth *eth.Ethereum eth *eth.Ethereum
@ -229,7 +245,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
return engine.STATUS_INVALID, attributesErr("missing withdrawals") return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil: case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root") return engine.STATUS_INVALID, attributesErr("missing beacon root")
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague): case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads") return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
} }
} }
@ -450,6 +466,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false) return api.getPayload(payloadID, false)
} }
// GetPayloadV5 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
}
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
data := api.localBlocks.get(payloadID, full) data := api.localBlocks.get(payloadID, full)
@ -464,14 +488,87 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
if len(hashes) > 128 { if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
} }
res := make([]*engine.BlobAndProofV1, len(hashes)) var (
res = make([]*engine.BlobAndProofV1, len(hashes))
hasher = sha256.New()
index = make(map[common.Hash]int)
sidecars = api.eth.BlobTxPool().GetBlobs(hashes)
)
blobs, proofs := api.eth.TxPool().GetBlobs(hashes) for i, hash := range hashes {
for i := 0; i < len(blobs); i++ { index[hash] = i
if blobs[i] != nil { }
res[i] = &engine.BlobAndProofV1{ for i, sidecar := range sidecars {
Blob: (*blobs[i])[:], if res[i] != nil || sidecar == nil {
Proof: (*proofs[i])[:], // already filled
continue
}
for cIdx, commitment := range sidecar.Commitments {
computed := kzg4844.CalcBlobHashV1(hasher, &commitment)
if idx, ok := index[computed]; ok {
res[idx] = &engine.BlobAndProofV1{
Blob: sidecar.Blobs[cIdx][:],
Proof: sidecar.Proofs[cIdx][:],
}
}
}
}
return res, nil
}
// GetBlobsV2 returns a blob from the transaction pool.
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
}
available := api.eth.BlobTxPool().AvailableBlobs(hashes)
getBlobsRequestedCounter.Inc(int64(len(hashes)))
getBlobsAvailableCounter.Inc(int64(available))
// Optimization: check first if all blobs are available, if not, return empty response
if available != len(hashes) {
getBlobsV2RequestMiss.Inc(1)
return nil, nil
}
getBlobsV2RequestHit.Inc(1)
// pull up the blob hashes
var (
res = make([]*engine.BlobAndProofV2, len(hashes))
index = make(map[common.Hash][]int)
sidecars = api.eth.BlobTxPool().GetBlobs(hashes)
)
for i, hash := range hashes {
index[hash] = append(index[hash], i)
}
for i, sidecar := range sidecars {
if res[i] != nil {
// already filled
continue
}
if sidecar == nil {
// not found, return empty response
return nil, nil
}
if sidecar.Version != 1 {
log.Info("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes())
return nil, nil
}
blobHashes := sidecar.BlobHashes()
for bIdx, hash := range blobHashes {
if idxes, ok := index[hash]; ok {
proofs := sidecar.CellProofsAt(bIdx)
var cellProofs []hexutil.Bytes
for _, proof := range proofs {
cellProofs = append(cellProofs, proof[:])
}
for _, idx := range idxes {
res[idx] = &engine.BlobAndProofV2{
Blob: sidecar.Blobs[bIdx][:],
CellProofs: cellProofs,
}
}
} }
} }
} }
@ -544,7 +641,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
return invalidStatus, paramsErr("nil beaconRoot post-cancun") return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil: case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague") return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague): case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)

View file

@ -463,7 +463,7 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ
// such as tx index, block hash, etc. // such as tx index, block hash, etc.
// Notably tx hash is NOT filled in because it needs // Notably tx hash is NOT filled in because it needs
// access to block body data. // access to block body data.
cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64()) cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64(), header.Time)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -98,7 +98,7 @@ type logCacheElem struct {
} }
// cachedLogElem loads block logs from the backend and caches the result. // cachedLogElem loads block logs from the backend and caches the result.
func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number uint64) (*logCacheElem, error) { func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number, time uint64) (*logCacheElem, error) {
cached, ok := sys.logsCache.Get(blockHash) cached, ok := sys.logsCache.Get(blockHash)
if ok { if ok {
return cached, nil return cached, nil
@ -119,6 +119,7 @@ func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Has
for _, log := range txLogs { for _, log := range txLogs {
log.BlockHash = blockHash log.BlockHash = blockHash
log.BlockNumber = number log.BlockNumber = number
log.BlockTimestamp = time
log.TxIndex = uint(i) log.TxIndex = uint(i)
log.Index = logIdx log.Index = logIdx
logIdx++ logIdx++

View file

@ -317,26 +317,26 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
}{ }{
{ {
f: sys.NewBlockFilter(chain[2].Hash(), []common.Address{contract}, nil), f: sys.NewBlockFilter(chain[2].Hash(), []common.Address{contract}, nil),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":30,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":20,"logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":30,"logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":10000,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}), f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}),
}, },
{ {
f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}), f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":9990,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}), f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":30,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}), f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":20,"logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","blockTimestamp":20,"logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","blockTimestamp":30,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}),
@ -349,15 +349,15 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":10000,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":9990,"logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","blockTimestamp":10000,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`, want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":9990,"logIndex":"0x0","removed":false}]`,
}, },
{ {
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),

View file

@ -889,11 +889,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
return nil, err return nil, err
} }
defer release() defer release()
msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee()) msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee())
if err != nil { if err != nil {
return nil, err return nil, err
} }
txctx := &Context{ txctx := &Context{
BlockHash: blockHash, BlockHash: blockHash,
BlockNumber: block.Number(), BlockNumber: block.Number(),
@ -1041,7 +1041,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
// Call Prepare to clear out the statedb access list // Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
_, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, tx, &usedGas, evm) _, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm)
if err != nil { if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }

View file

@ -179,8 +179,12 @@ func (s *StructLog) toLegacyJSON() json.RawMessage {
} }
if len(s.Memory) > 0 { if len(s.Memory) > 0 {
memory := make([]string, 0, (len(s.Memory)+31)/32) memory := make([]string, 0, (len(s.Memory)+31)/32)
for i := 0; i+32 <= len(s.Memory); i += 32 { for i := 0; i < len(s.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", s.Memory[i:i+32])) end := i + 32
if end > len(s.Memory) {
end = len(s.Memory)
}
memory = append(memory, fmt.Sprintf("%x", s.Memory[i:end]))
} }
msg.Memory = &memory msg.Memory = &memory
} }

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -204,6 +205,17 @@ func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- co
return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions") return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions")
} }
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (ec *Client) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig) (any, error) {
var result any
err := ec.c.CallContext(ctx, &result, "debug_traceTransaction", hash.Hex(), config)
if err != nil {
return nil, err
}
return result, nil
}
func toBlockNumArg(number *big.Int) string { func toBlockNumArg(number *big.Int) string {
if number == nil { if number == nil {
return "latest" return "latest"

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -47,13 +48,16 @@ var (
testSlot = common.HexToHash("0xdeadbeef") testSlot = common.HexToHash("0xdeadbeef")
testValue = crypto.Keccak256Hash(testSlot[:]) testValue = crypto.Keccak256Hash(testSlot[:])
testBalance = big.NewInt(2e15) testBalance = big.NewInt(2e15)
testTxHashes []common.Hash
) )
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
// Generate test chain. // Generate test chain.
genesis, blocks := generateTestChain() genesis, blocks := generateTestChain()
// Create node // Create node
n, err := node.New(&node.Config{}) n, err := node.New(&node.Config{
HTTPModules: []string{"debug", "eth", "admin"},
})
if err != nil { if err != nil {
t.Fatalf("can't create new node: %v", err) t.Fatalf("can't create new node: %v", err)
} }
@ -63,6 +67,8 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
if err != nil { if err != nil {
t.Fatalf("can't create new ethereum service: %v", err) t.Fatalf("can't create new ethereum service: %v", err)
} }
n.RegisterAPIs(tracers.APIs(ethservice.APIBackend))
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{}) filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
n.RegisterAPIs([]rpc.API{{ n.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",
@ -93,6 +99,19 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
generate := func(i int, g *core.BlockGen) { generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5) g.OffsetTime(5)
g.SetExtra([]byte("test")) g.SetExtra([]byte("test"))
to := common.BytesToAddress([]byte{byte(i + 1)})
tx := types.NewTx(&types.LegacyTx{
Nonce: uint64(i),
To: &to,
Value: big.NewInt(int64(2*i + 1)),
Gas: params.TxGas,
GasPrice: big.NewInt(params.InitialBaseFee),
Data: nil,
})
tx, _ = types.SignTx(tx, types.LatestSignerForChainID(genesis.Config.ChainID), testKey)
g.AddTx(tx)
testTxHashes = append(testTxHashes, tx.Hash())
} }
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate) _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate)
blocks = append([]*types.Block{genesis.ToBlock()}, blocks...) blocks = append([]*types.Block{genesis.ToBlock()}, blocks...)
@ -136,9 +155,6 @@ func TestGethClient(t *testing.T) {
}, { }, {
"TestSubscribePendingTxHashes", "TestSubscribePendingTxHashes",
func(t *testing.T) { testSubscribePendingTransactions(t, client) }, func(t *testing.T) { testSubscribePendingTransactions(t, client) },
}, {
"TestSubscribePendingTxs",
func(t *testing.T) { testSubscribeFullPendingTransactions(t, client) },
}, { }, {
"TestCallContract", "TestCallContract",
func(t *testing.T) { testCallContract(t, client) }, func(t *testing.T) { testCallContract(t, client) },
@ -153,7 +169,12 @@ func TestGethClient(t *testing.T) {
{ {
"TestAccessList", "TestAccessList",
func(t *testing.T) { testAccessList(t, client) }, func(t *testing.T) { testAccessList(t, client) },
}, { },
{
"TestTraceTransaction",
func(t *testing.T) { testTraceTransactions(t, client) },
},
{
"TestSetHead", "TestSetHead",
func(t *testing.T) { testSetHead(t, client) }, func(t *testing.T) { testSetHead(t, client) },
}, },
@ -197,7 +218,7 @@ func testAccessList(t *testing.T, client *rpc.Client) {
wantVMErr: "execution reverted", wantVMErr: "execution reverted",
wantAL: `[ wantAL: `[
{ {
"address": "0x3a220f351252089d385b29beca14e27f204c296a", "address": "0xdb7d6ab1f17c6b31909ae466702703daef9269cf",
"storageKeys": [ "storageKeys": [
"0x0000000000000000000000000000000000000000000000000000000000000081" "0x0000000000000000000000000000000000000000000000000000000000000081"
] ]
@ -389,16 +410,26 @@ func testSetHead(t *testing.T, client *rpc.Client) {
func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) { func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) {
ec := New(client) ec := New(client)
ethcl := ethclient.NewClient(client) ethcl := ethclient.NewClient(client)
// Subscribe to Transactions // Subscribe to Transactions
ch := make(chan common.Hash) ch1 := make(chan common.Hash)
ec.SubscribePendingTransactions(context.Background(), ch) ec.SubscribePendingTransactions(context.Background(), ch1)
// Subscribe to Transactions
ch2 := make(chan *types.Transaction)
ec.SubscribeFullPendingTransactions(context.Background(), ch2)
// Send a transaction // Send a transaction
chainID, err := ethcl.ChainID(context.Background()) chainID, err := ethcl.ChainID(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
nonce, err := ethcl.NonceAt(context.Background(), testAddr, nil)
if err != nil {
t.Fatal(err)
}
// Create transaction // Create transaction
tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) tx := types.NewTransaction(nonce, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
signer := types.LatestSignerForChainID(chainID) signer := types.LatestSignerForChainID(chainID)
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil { if err != nil {
@ -414,41 +445,12 @@ func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) {
t.Fatal(err) t.Fatal(err)
} }
// Check that the transaction was sent over the channel // Check that the transaction was sent over the channel
hash := <-ch hash := <-ch1
if hash != signedTx.Hash() { if hash != signedTx.Hash() {
t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash()) t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash())
} }
}
func testSubscribeFullPendingTransactions(t *testing.T, client *rpc.Client) {
ec := New(client)
ethcl := ethclient.NewClient(client)
// Subscribe to Transactions
ch := make(chan *types.Transaction)
ec.SubscribeFullPendingTransactions(context.Background(), ch)
// Send a transaction
chainID, err := ethcl.ChainID(context.Background())
if err != nil {
t.Fatal(err)
}
// Create transaction
tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
signer := types.LatestSignerForChainID(chainID)
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil {
t.Fatal(err)
}
signedTx, err := tx.WithSignature(signer, signature)
if err != nil {
t.Fatal(err)
}
// Send transaction
err = ethcl.SendTransaction(context.Background(), signedTx)
if err != nil {
t.Fatal(err)
}
// Check that the transaction was sent over the channel // Check that the transaction was sent over the channel
tx = <-ch tx = <-ch2
if tx.Hash() != signedTx.Hash() { if tx.Hash() != signedTx.Hash() {
t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash()) t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash())
} }
@ -478,6 +480,25 @@ func testCallContract(t *testing.T, client *rpc.Client) {
} }
} }
func testTraceTransactions(t *testing.T, client *rpc.Client) {
ec := New(client)
for _, txHash := range testTxHashes {
// Struct logger
_, err := ec.TraceTransaction(context.Background(), txHash, nil)
if err != nil {
t.Fatal(err)
}
// Struct logger
_, err = ec.TraceTransaction(context.Background(), txHash,
&tracers.TraceConfig{},
)
if err != nil {
t.Fatal(err)
}
}
}
func TestOverrideAccountMarshal(t *testing.T) { func TestOverrideAccountMarshal(t *testing.T) {
om := map[common.Address]OverrideAccount{ om := map[common.Address]OverrideAccount{
{0x11}: { {0x11}: {

View file

@ -199,9 +199,12 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
// Taken from https://github.com/cockroachdb/pebble/blob/master/internal/constants/constants.go // Taken from https://github.com/cockroachdb/pebble/blob/master/internal/constants/constants.go
maxMemTableSize := (1<<31)<<(^uint(0)>>63) - 1 maxMemTableSize := (1<<31)<<(^uint(0)>>63) - 1
// Two memory tables is configured which is identical to leveldb, // Four memory tables are configured, each with a default size of 256 MB.
// including a frozen memory table and another live one. // Having multiple smaller memory tables while keeping the total memory
memTableLimit := 2 // limit unchanged allows writes to be flushed more smoothly. This helps
// avoid compaction spikes and mitigates write stalls caused by heavy
// compaction workloads.
memTableLimit := 4
memTableSize := cache * 1024 * 1024 / 2 / memTableLimit memTableSize := cache * 1024 * 1024 / 2 / memTableLimit
// The memory table size is currently capped at maxMemTableSize-1 due to a // The memory table size is currently capped at maxMemTableSize-1 due to a

View file

@ -301,7 +301,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes()
} }
gasUsed += result.UsedGas gasUsed += result.UsedGas
receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, tx, gasUsed, root) receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, blockContext.Time, tx, gasUsed, root)
blobGasUsed += receipts[i].BlobGasUsed blobGasUsed += receipts[i].BlobGasUsed
logs := tracer.Logs() logs := tracer.Logs()
callRes := simCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} callRes := simCallResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)}

View file

@ -20,6 +20,7 @@
"transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287",
"transactionIndex": "0x0", "transactionIndex": "0x0",
"blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa", "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"blockTimestamp": 30,
"logIndex": "0x0", "logIndex": "0x0",
"removed": false "removed": false
} }

View file

@ -19,6 +19,7 @@
"transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287",
"transactionIndex": "0x0", "transactionIndex": "0x0",
"blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa", "blockHash": "0xcc6225bf39327429a3d869af71182d619a354155187d0b5a8ecd6a9309cffcaa",
"blockTimestamp": 30,
"logIndex": "0x0", "logIndex": "0x0",
"removed": false "removed": false
} }

View file

@ -7,10 +7,7 @@ import (
// GetOrRegisterCounter returns an existing Counter or constructs and registers // GetOrRegisterCounter returns an existing Counter or constructs and registers
// a new Counter. // a new Counter.
func GetOrRegisterCounter(name string, r Registry) *Counter { func GetOrRegisterCounter(name string, r Registry) *Counter {
if r == nil { return getOrRegister(name, NewCounter, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewCounter).(*Counter)
} }
// NewCounter constructs a new Counter. // NewCounter constructs a new Counter.

View file

@ -8,10 +8,7 @@ import (
// GetOrRegisterCounterFloat64 returns an existing *CounterFloat64 or constructs and registers // GetOrRegisterCounterFloat64 returns an existing *CounterFloat64 or constructs and registers
// a new CounterFloat64. // a new CounterFloat64.
func GetOrRegisterCounterFloat64(name string, r Registry) *CounterFloat64 { func GetOrRegisterCounterFloat64(name string, r Registry) *CounterFloat64 {
if nil == r { return getOrRegister(name, NewCounterFloat64, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewCounterFloat64).(*CounterFloat64)
} }
// NewCounterFloat64 constructs a new CounterFloat64. // NewCounterFloat64 constructs a new CounterFloat64.

View file

@ -11,10 +11,7 @@ func (g GaugeSnapshot) Value() int64 { return int64(g) }
// GetOrRegisterGauge returns an existing Gauge or constructs and registers a // GetOrRegisterGauge returns an existing Gauge or constructs and registers a
// new Gauge. // new Gauge.
func GetOrRegisterGauge(name string, r Registry) *Gauge { func GetOrRegisterGauge(name string, r Registry) *Gauge {
if r == nil { return getOrRegister(name, NewGauge, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewGauge).(*Gauge)
} }
// NewGauge constructs a new Gauge. // NewGauge constructs a new Gauge.

View file

@ -8,10 +8,7 @@ import (
// GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a // GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a
// new GaugeFloat64. // new GaugeFloat64.
func GetOrRegisterGaugeFloat64(name string, r Registry) *GaugeFloat64 { func GetOrRegisterGaugeFloat64(name string, r Registry) *GaugeFloat64 {
if nil == r { return getOrRegister(name, NewGaugeFloat64, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewGaugeFloat64()).(*GaugeFloat64)
} }
// GaugeFloat64Snapshot is a read-only copy of a GaugeFloat64. // GaugeFloat64Snapshot is a read-only copy of a GaugeFloat64.

View file

@ -16,10 +16,7 @@ func (val GaugeInfoValue) String() string {
// GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a // GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a
// new GaugeInfo. // new GaugeInfo.
func GetOrRegisterGaugeInfo(name string, r Registry) *GaugeInfo { func GetOrRegisterGaugeInfo(name string, r Registry) *GaugeInfo {
if nil == r { return getOrRegister(name, NewGaugeInfo, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewGaugeInfo()).(*GaugeInfo)
} }
// NewGaugeInfo constructs a new GaugeInfo. // NewGaugeInfo constructs a new GaugeInfo.

View file

@ -23,19 +23,13 @@ type Histogram interface {
// GetOrRegisterHistogram returns an existing Histogram or constructs and // GetOrRegisterHistogram returns an existing Histogram or constructs and
// registers a new StandardHistogram. // registers a new StandardHistogram.
func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram { func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
if nil == r { return getOrRegister(name, func() Histogram { return NewHistogram(s) }, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram)
} }
// GetOrRegisterHistogramLazy returns an existing Histogram or constructs and // GetOrRegisterHistogramLazy returns an existing Histogram or constructs and
// registers a new StandardHistogram. // registers a new StandardHistogram.
func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram { func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram {
if nil == r { return getOrRegister(name, func() Histogram { return NewHistogram(s()) }, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, func() Histogram { return NewHistogram(s()) }).(Histogram)
} }
// NewHistogram constructs a new StandardHistogram from a Sample. // NewHistogram constructs a new StandardHistogram from a Sample.

View file

@ -12,10 +12,7 @@ import (
// Be sure to unregister the meter from the registry once it is of no use to // Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection. // allow for garbage collection.
func GetOrRegisterMeter(name string, r Registry) *Meter { func GetOrRegisterMeter(name string, r Registry) *Meter {
if r == nil { return getOrRegister(name, NewMeter, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewMeter).(*Meter)
} }
// NewMeter constructs a new Meter and launches a goroutine. // NewMeter constructs a new Meter and launches a goroutine.

View file

@ -3,7 +3,6 @@ package metrics
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -30,10 +29,9 @@ type Registry interface {
// GetAll metrics in the Registry. // GetAll metrics in the Registry.
GetAll() map[string]map[string]interface{} GetAll() map[string]map[string]interface{}
// GetOrRegister gets an existing metric or registers the given one. // GetOrRegister returns an existing metric or registers the one returned
// The interface can be the metric to register if not found in registry, // by the given constructor.
// or a function returning the metric for lazy instantiation. GetOrRegister(string, func() interface{}) interface{}
GetOrRegister(string, interface{}) interface{}
// Register the given metric under the given name. // Register the given metric under the given name.
Register(string, interface{}) error Register(string, interface{}) error
@ -95,19 +93,13 @@ func (r *StandardRegistry) Get(name string) interface{} {
// alternative to calling Get and Register on failure. // alternative to calling Get and Register on failure.
// The interface can be the metric to register if not found in registry, // The interface can be the metric to register if not found in registry,
// or a function returning the metric for lazy instantiation. // or a function returning the metric for lazy instantiation.
func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} { func (r *StandardRegistry) GetOrRegister(name string, ctor func() interface{}) interface{} {
// fast path // fast path
cached, ok := r.metrics.Load(name) cached, ok := r.metrics.Load(name)
if ok { if ok {
return cached return cached
} }
if v := reflect.ValueOf(i); v.Kind() == reflect.Func { item, _, _ := r.loadOrRegister(name, ctor())
i = v.Call(nil)[0].Interface()
}
item, _, ok := r.loadOrRegister(name, i)
if !ok {
return i
}
return item return item
} }
@ -120,9 +112,6 @@ func (r *StandardRegistry) Register(name string, i interface{}) error {
return fmt.Errorf("%w: %v", ErrDuplicateMetric, name) return fmt.Errorf("%w: %v", ErrDuplicateMetric, name)
} }
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
i = v.Call(nil)[0].Interface()
}
_, loaded, _ := r.loadOrRegister(name, i) _, loaded, _ := r.loadOrRegister(name, i)
if loaded { if loaded {
return fmt.Errorf("%w: %v", ErrDuplicateMetric, name) return fmt.Errorf("%w: %v", ErrDuplicateMetric, name)
@ -295,9 +284,9 @@ func (r *PrefixedRegistry) Get(name string) interface{} {
// GetOrRegister gets an existing metric or registers the given one. // GetOrRegister gets an existing metric or registers the given one.
// The interface can be the metric to register if not found in registry, // The interface can be the metric to register if not found in registry,
// or a function returning the metric for lazy instantiation. // or a function returning the metric for lazy instantiation.
func (r *PrefixedRegistry) GetOrRegister(name string, metric interface{}) interface{} { func (r *PrefixedRegistry) GetOrRegister(name string, ctor func() interface{}) interface{} {
realName := r.prefix + name realName := r.prefix + name
return r.underlying.GetOrRegister(realName, metric) return r.underlying.GetOrRegister(realName, ctor)
} }
// Register the given metric under the given name. The name will be prefixed. // Register the given metric under the given name. The name will be prefixed.
@ -338,10 +327,17 @@ func Get(name string) interface{} {
// GetOrRegister gets an existing metric or creates and registers a new one. Threadsafe // GetOrRegister gets an existing metric or creates and registers a new one. Threadsafe
// alternative to calling Get and Register on failure. // alternative to calling Get and Register on failure.
func GetOrRegister(name string, i interface{}) interface{} { func GetOrRegister(name string, i func() interface{}) interface{} {
return DefaultRegistry.GetOrRegister(name, i) return DefaultRegistry.GetOrRegister(name, i)
} }
func getOrRegister[T any](name string, ctor func() T, r Registry) T {
if r == nil {
r = DefaultRegistry
}
return r.GetOrRegister(name, func() any { return ctor() }).(T)
}
// Register the given metric under the given name. Returns a ErrDuplicateMetric // Register the given metric under the given name. Returns a ErrDuplicateMetric
// if a metric by the given name is already registered. // if a metric by the given name is already registered.
func Register(name string, i interface{}) error { func Register(name string, i interface{}) error {

View file

@ -30,7 +30,7 @@ func benchmarkRegistryGetOrRegisterParallel(b *testing.B, amount int) {
wg.Add(1) wg.Add(1)
go func() { go func() {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
r.GetOrRegister("foo", NewMeter) GetOrRegisterMeter("foo", r)
} }
wg.Done() wg.Done()
}() }()
@ -98,10 +98,10 @@ func TestRegistryGetOrRegister(t *testing.T) {
r := NewRegistry() r := NewRegistry()
// First metric wins with GetOrRegister // First metric wins with GetOrRegister
_ = r.GetOrRegister("foo", NewCounter()) c1 := GetOrRegisterCounter("foo", r)
m := r.GetOrRegister("foo", NewGauge()) c2 := GetOrRegisterCounter("foo", r)
if _, ok := m.(*Counter); !ok { if c1 != c2 {
t.Fatal(m) t.Fatal("counters should've matched")
} }
i := 0 i := 0
@ -123,10 +123,10 @@ func TestRegistryGetOrRegisterWithLazyInstantiation(t *testing.T) {
r := NewRegistry() r := NewRegistry()
// First metric wins with GetOrRegister // First metric wins with GetOrRegister
_ = r.GetOrRegister("foo", NewCounter) c1 := GetOrRegisterCounter("foo", r)
m := r.GetOrRegister("foo", NewGauge) c2 := GetOrRegisterCounter("foo", r)
if _, ok := m.(*Counter); !ok { if c1 != c2 {
t.Fatal(m) t.Fatal("counters should've matched")
} }
i := 0 i := 0
@ -165,7 +165,7 @@ func TestPrefixedChildRegistryGetOrRegister(t *testing.T) {
r := NewRegistry() r := NewRegistry()
pr := NewPrefixedChildRegistry(r, "prefix.") pr := NewPrefixedChildRegistry(r, "prefix.")
_ = pr.GetOrRegister("foo", NewCounter()) _ = GetOrRegisterCounter("foo", pr)
i := 0 i := 0
r.Each(func(name string, m interface{}) { r.Each(func(name string, m interface{}) {
@ -182,7 +182,7 @@ func TestPrefixedChildRegistryGetOrRegister(t *testing.T) {
func TestPrefixedRegistryGetOrRegister(t *testing.T) { func TestPrefixedRegistryGetOrRegister(t *testing.T) {
r := NewPrefixedRegistry("prefix.") r := NewPrefixedRegistry("prefix.")
_ = r.GetOrRegister("foo", NewCounter()) _ = GetOrRegisterCounter("foo", r)
i := 0 i := 0
r.Each(func(name string, m interface{}) { r.Each(func(name string, m interface{}) {

View file

@ -8,10 +8,7 @@ import (
// GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a // GetOrRegisterResettingTimer returns an existing ResettingTimer or constructs and registers a
// new ResettingTimer. // new ResettingTimer.
func GetOrRegisterResettingTimer(name string, r Registry) *ResettingTimer { func GetOrRegisterResettingTimer(name string, r Registry) *ResettingTimer {
if nil == r { return getOrRegister(name, NewResettingTimer, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewResettingTimer).(*ResettingTimer)
} }
// NewRegisteredResettingTimer constructs and registers a new ResettingTimer. // NewRegisteredResettingTimer constructs and registers a new ResettingTimer.

View file

@ -8,11 +8,8 @@ import (
) )
func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram { func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runtimeHistogram {
if r == nil {
r = DefaultRegistry
}
constructor := func() Histogram { return newRuntimeHistogram(scale) } constructor := func() Histogram { return newRuntimeHistogram(scale) }
return r.GetOrRegister(name, constructor).(*runtimeHistogram) return getOrRegister(name, constructor, r).(*runtimeHistogram)
} }
// runtimeHistogram wraps a runtime/metrics histogram. // runtimeHistogram wraps a runtime/metrics histogram.

View file

@ -10,10 +10,7 @@ import (
// Be sure to unregister the meter from the registry once it is of no use to // Be sure to unregister the meter from the registry once it is of no use to
// allow for garbage collection. // allow for garbage collection.
func GetOrRegisterTimer(name string, r Registry) *Timer { func GetOrRegisterTimer(name string, r Registry) *Timer {
if nil == r { return getOrRegister(name, NewTimer, r)
r = DefaultRegistry
}
return r.GetOrRegister(name, NewTimer).(*Timer)
} }
// NewCustomTimer constructs a new Timer from a Histogram and a Meter. // NewCustomTimer constructs a new Timer from a Histogram and a Meter.

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -390,6 +391,23 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
continue continue
} }
// Make sure all transactions after osaka have cell proofs
if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) {
if sidecar := tx.BlobTxSidecar(); sidecar != nil {
if sidecar.Version == 0 {
log.Info("Including blob tx with v0 sidecar, recomputing proofs", "hash", ltx.Hash)
sidecar.Proofs = make([]kzg4844.Proof, 0, len(sidecar.Blobs)*kzg4844.CellProofsPerBlob)
for _, blob := range sidecar.Blobs {
cellProofs, err := kzg4844.ComputeCellProofs(&blob)
if err != nil {
panic(err)
}
sidecar.Proofs = append(sidecar.Proofs, cellProofs...)
}
}
}
}
// Error may be ignored here. The error has already been checked // Error may be ignored here. The error has already been checked
// during transaction acceptance in the transaction pool. // during transaction acceptance in the transaction pool.
from, _ := types.Sender(env.signer, tx) from, _ := types.Sender(env.signer, tx)

View file

@ -296,7 +296,7 @@ var (
ShanghaiTime: newUint64(0), ShanghaiTime: newUint64(0),
CancunTime: newUint64(0), CancunTime: newUint64(0),
PragueTime: newUint64(0), PragueTime: newUint64(0),
OsakaTime: nil, OsakaTime: newUint64(0),
VerkleTime: nil, VerkleTime: nil,
TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
@ -304,6 +304,7 @@ var (
BlobScheduleConfig: &BlobScheduleConfig{ BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig, Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig, Prague: DefaultPragueBlobConfig,
Osaka: DefaultOsakaBlobConfig,
}, },
} }

View file

@ -86,6 +86,10 @@ func (db *testDb) InsertPreimage(preimages map[common.Hash][]byte) {
rawdb.WritePreimages(db.disk, preimages) rawdb.WritePreimages(db.disk, preimages)
} }
func (db *testDb) PreimageEnabled() bool {
return true
}
func (db *testDb) Scheme() string { return db.scheme } func (db *testDb) Scheme() string { return db.scheme }
func (db *testDb) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error { func (db *testDb) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error {

View file

@ -33,6 +33,9 @@ type preimageStore interface {
// InsertPreimage commits a set of preimages along with their hashes. // InsertPreimage commits a set of preimages along with their hashes.
InsertPreimage(preimages map[common.Hash][]byte) InsertPreimage(preimages map[common.Hash][]byte)
// PreimageEnabled returns true if the preimage store is enabled.
PreimageEnabled() bool
} }
// SecureTrie is the old name of StateTrie. // SecureTrie is the old name of StateTrie.
@ -84,8 +87,7 @@ func NewStateTrie(id *ID, db database.NodeDatabase) (*StateTrie, error) {
tr := &StateTrie{trie: *trie, db: db} tr := &StateTrie{trie: *trie, db: db}
// link the preimage store if it's supported // link the preimage store if it's supported
preimages, ok := db.(preimageStore) if preimages, ok := db.(preimageStore); ok && preimages.PreimageEnabled() {
if ok {
tr.preimages = preimages tr.preimages = preimages
} }
return tr, nil return tr, nil
@ -159,8 +161,10 @@ func (t *StateTrie) GetNode(path []byte) ([]byte, int, error) {
func (t *StateTrie) MustUpdate(key, value []byte) { func (t *StateTrie) MustUpdate(key, value []byte) {
hk := crypto.Keccak256(key) hk := crypto.Keccak256(key)
t.trie.MustUpdate(hk, value) t.trie.MustUpdate(hk, value)
if t.preimages != nil {
t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key) t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key)
} }
}
// UpdateStorage associates key with value in the trie. Subsequent calls to // UpdateStorage associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value // Get will return value. If value has length zero, any existing value
@ -177,7 +181,9 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
if err != nil { if err != nil {
return err return err
} }
if t.preimages != nil {
t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key) t.getSecKeyCache()[common.Hash(hk)] = common.CopyBytes(key)
}
return nil return nil
} }
@ -191,7 +197,9 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
if err := t.trie.Update(hk, data); err != nil { if err := t.trie.Update(hk, data); err != nil {
return err return err
} }
if t.preimages != nil {
t.getSecKeyCache()[common.Hash(hk)] = address.Bytes() t.getSecKeyCache()[common.Hash(hk)] = address.Bytes()
}
return nil return nil
} }
@ -203,7 +211,9 @@ func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte
// will omit any encountered error but just print out an error message. // will omit any encountered error but just print out an error message.
func (t *StateTrie) MustDelete(key []byte) { func (t *StateTrie) MustDelete(key []byte) {
hk := crypto.Keccak256(key) hk := crypto.Keccak256(key)
if t.preimages != nil {
delete(t.getSecKeyCache(), common.Hash(hk)) delete(t.getSecKeyCache(), common.Hash(hk))
}
t.trie.MustDelete(hk) t.trie.MustDelete(hk)
} }
@ -212,26 +222,30 @@ func (t *StateTrie) MustDelete(key []byte) {
// If a node is not found in the database, a MissingNodeError is returned. // If a node is not found in the database, a MissingNodeError is returned.
func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error { func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error {
hk := crypto.Keccak256(key) hk := crypto.Keccak256(key)
if t.preimages != nil {
delete(t.getSecKeyCache(), common.Hash(hk)) delete(t.getSecKeyCache(), common.Hash(hk))
}
return t.trie.Delete(hk) return t.trie.Delete(hk)
} }
// DeleteAccount abstracts an account deletion from the trie. // DeleteAccount abstracts an account deletion from the trie.
func (t *StateTrie) DeleteAccount(address common.Address) error { func (t *StateTrie) DeleteAccount(address common.Address) error {
hk := crypto.Keccak256(address.Bytes()) hk := crypto.Keccak256(address.Bytes())
if t.preimages != nil {
delete(t.getSecKeyCache(), common.Hash(hk)) delete(t.getSecKeyCache(), common.Hash(hk))
}
return t.trie.Delete(hk) return t.trie.Delete(hk)
} }
// GetKey returns the sha3 preimage of a hashed key that was // GetKey returns the sha3 preimage of a hashed key that was
// previously used to store a value. // previously used to store a value.
func (t *StateTrie) GetKey(shaKey []byte) []byte { func (t *StateTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[common.BytesToHash(shaKey)]; ok {
return key
}
if t.preimages == nil { if t.preimages == nil {
return nil return nil
} }
if key, ok := t.getSecKeyCache()[common.BytesToHash(shaKey)]; ok {
return key
}
return t.preimages.Preimage(common.BytesToHash(shaKey)) return t.preimages.Preimage(common.BytesToHash(shaKey))
} }

View file

@ -213,6 +213,11 @@ func (db *Database) InsertPreimage(preimages map[common.Hash][]byte) {
db.preimages.insertPreimage(preimages) db.preimages.insertPreimage(preimages)
} }
// PreimageEnabled returns the indicator if the pre-image store is enabled.
func (db *Database) PreimageEnabled() bool {
return db.preimages != nil
}
// Cap iteratively flushes old but still referenced trie nodes until the total // Cap iteratively flushes old but still referenced trie nodes until the total
// memory usage goes below the given threshold. The held pre-images accumulated // memory usage goes below the given threshold. The held pre-images accumulated
// up to this point will be flushed in case the size exceeds the threshold. // up to this point will be flushed in case the size exceeds the threshold.