mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 10:20:44 +00:00
commit
This commit is contained in:
parent
e1521be67a
commit
4b13eb8478
7 changed files with 1682 additions and 6 deletions
|
|
@ -10,6 +10,11 @@ a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_devel
|
||||||
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.3.0
|
# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.3.0
|
||||||
a1840f2e74d7d53f9f36f969bfd280407829b21e85bea206ba907453fdc47ac4 fixtures_bal.tar.gz
|
a1840f2e74d7d53f9f36f969bfd280407829b21e85bea206ba907453fdc47ac4 fixtures_bal.tar.gz
|
||||||
|
|
||||||
|
# version:spec-tests-zkevm zkevm@v0.3.0
|
||||||
|
# https://github.com/ethereum/execution-spec-tests/releases
|
||||||
|
# https://github.com/ethereum/execution-spec-tests/releases/download/zkevm%40v0.3.0
|
||||||
|
7237c6504243a9b2038bd23005e4fae051bc64f1b8c9bac081131d11f33a5c3c fixtures_zkevm.tar.gz
|
||||||
|
|
||||||
# version:golang 1.25.7
|
# version:golang 1.25.7
|
||||||
# https://go.dev/dl/
|
# https://go.dev/dl/
|
||||||
178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
|
178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz
|
||||||
|
|
|
||||||
17
build/ci.go
17
build/ci.go
|
|
@ -175,6 +175,9 @@ var (
|
||||||
|
|
||||||
// This is where the bal-specific release of the tests should be unpacked.
|
// This is where the bal-specific release of the tests should be unpacked.
|
||||||
executionSpecTestsBALDir = "tests/spec-tests-bal"
|
executionSpecTestsBALDir = "tests/spec-tests-bal"
|
||||||
|
|
||||||
|
// This is where the zkevm release of the tests should be unpacked.
|
||||||
|
executionSpecTestsZkevmDir = "tests/spec-tests-zkevm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -384,6 +387,7 @@ func doTest(cmdline []string) {
|
||||||
if !*short {
|
if !*short {
|
||||||
downloadSpecTestFixtures(csdb, *cachedir)
|
downloadSpecTestFixtures(csdb, *cachedir)
|
||||||
downloadBALSpecTestFixtures(csdb, *cachedir)
|
downloadBALSpecTestFixtures(csdb, *cachedir)
|
||||||
|
downloadZkevmSpecTestFixtures(csdb, *cachedir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the toolchain.
|
// Configure the toolchain.
|
||||||
|
|
@ -462,6 +466,19 @@ func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) str
|
||||||
return filepath.Join(cachedir, base)
|
return filepath.Join(cachedir, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downloadZkevmSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||||
|
ext := ".tar.gz"
|
||||||
|
base := "fixtures_zkevm"
|
||||||
|
archivePath := filepath.Join(cachedir, base+ext)
|
||||||
|
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := build.ExtractArchive(archivePath, executionSpecTestsZkevmDir); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return filepath.Join(cachedir, base)
|
||||||
|
}
|
||||||
|
|
||||||
// doCheckGenerate ensures that re-generating generated files does not cause
|
// doCheckGenerate ensures that re-generating generated files does not cause
|
||||||
// any mutations in the source file tree.
|
// any mutations in the source file tree.
|
||||||
func doCheckGenerate() {
|
func doCheckGenerate() {
|
||||||
|
|
|
||||||
|
|
@ -868,6 +868,42 @@ func TestExecutionSpecBlocktestsBAL(t *testing.T) {
|
||||||
testExecutionSpecBlocktests(t, executionSpecBALBlockchainTestDir, skips)
|
testExecutionSpecBlocktests(t, executionSpecBALBlockchainTestDir, skips)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestExecutionSpecZkevmBlocktests runs the zkevm test fixtures from execution-spec-tests
|
||||||
|
// and validates execution witnesses against geth's stateless execution.
|
||||||
|
func TestExecutionSpecZkevmBlocktests(t *testing.T) {
|
||||||
|
if !common.FileExist(executionSpecZkevmBlockchainTestDir) {
|
||||||
|
t.Skipf("directory %s does not exist", executionSpecZkevmBlockchainTestDir)
|
||||||
|
}
|
||||||
|
bt := new(testMatcher)
|
||||||
|
|
||||||
|
bt.walk(t, executionSpecZkevmBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||||
|
execBlockTest(t, bt, test, true)
|
||||||
|
|
||||||
|
// Validate execution witnesses for blocks that have them
|
||||||
|
// TODO: Execution specs don't emit a witness when block is valid right now
|
||||||
|
for _, b := range test.json.Blocks {
|
||||||
|
if b.ExecutionWitness == nil || b.BlockHeader == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
block, err := b.decode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to decode block for witness validation: %v", err)
|
||||||
|
}
|
||||||
|
if err := test.validateExecutionWitness(block, b.ExecutionWitness); err != nil {
|
||||||
|
t.Errorf("execution witness validation failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the SSZ-encoded statelessInputBytes witness matches the JSON executionWitness
|
||||||
|
// TODO: long term, we will only have statelessInputBytes and we will have no redundancy
|
||||||
|
if b.StatelessInputBytes != nil {
|
||||||
|
if err := validateStatelessInputWitness([]byte(*b.StatelessInputBytes), b.ExecutionWitness); err != nil {
|
||||||
|
t.Errorf("stateless input witness mismatch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var failures = 0
|
var failures = 0
|
||||||
|
|
||||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerifyBAL bool) {
|
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerifyBAL bool) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -30,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||||
|
|
@ -44,6 +46,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -68,11 +71,20 @@ type btJSON struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type btBlock struct {
|
type btBlock struct {
|
||||||
BlockHeader *btHeader
|
BlockHeader *btHeader
|
||||||
ExpectException string
|
ExpectException string
|
||||||
Rlp string
|
Rlp string
|
||||||
UncleHeaders []*btHeader
|
UncleHeaders []*btHeader
|
||||||
AccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
AccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||||
|
ExecutionWitness *btExecutionWitness `json:"executionWitness,omitempty"`
|
||||||
|
StatelessInputBytes *hexutil.Bytes `json:"statelessInputBytes,omitempty"`
|
||||||
|
StatelessOutputBytes *hexutil.Bytes `json:"statelessOutputBytes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type btExecutionWitness struct {
|
||||||
|
State []hexutil.Bytes `json:"state"`
|
||||||
|
Codes []hexutil.Bytes `json:"codes"`
|
||||||
|
Headers []hexutil.Bytes `json:"headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type btHeader -field-override btHeaderMarshaling -out gen_btheader.go
|
//go:generate go run github.com/fjl/gencodec -type btHeader -field-override btHeaderMarshaling -out gen_btheader.go
|
||||||
|
|
@ -470,3 +482,100 @@ func (bb *btBlock) decode() (*types.Block, error) {
|
||||||
err = rlp.DecodeBytes(data, &b)
|
err = rlp.DecodeBytes(data, &b)
|
||||||
return &b, err
|
return &b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toWitness converts a btExecutionWitness into a stateless.Witness by RLP-decoding
|
||||||
|
// the headers and populating the codes and state fields.
|
||||||
|
func (ew *btExecutionWitness) toWitness() (*stateless.Witness, error) {
|
||||||
|
w := new(stateless.Witness)
|
||||||
|
|
||||||
|
for _, raw := range ew.Headers {
|
||||||
|
var h types.Header
|
||||||
|
if err := rlp.DecodeBytes(raw, &h); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to RLP-decode witness header: %v", err)
|
||||||
|
}
|
||||||
|
w.Headers = append(w.Headers, &h)
|
||||||
|
}
|
||||||
|
// EEST fixtures store headers in ascending block number order, but geth's
|
||||||
|
// stateless execution expects Headers[0] to be the parent (highest number)
|
||||||
|
// followed by older ancestors. Reverse the slice to match.
|
||||||
|
slices.Reverse(w.Headers)
|
||||||
|
w.Codes = make(map[string]struct{}, len(ew.Codes))
|
||||||
|
for _, code := range ew.Codes {
|
||||||
|
w.Codes[string(code)] = struct{}{}
|
||||||
|
}
|
||||||
|
w.State = make(map[string]struct{}, len(ew.State))
|
||||||
|
for _, node := range ew.State {
|
||||||
|
w.State[string(node)] = struct{}{}
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateExecutionWitness checks that the execution witness from the test fixture
|
||||||
|
// is sufficient to statelessly execute the given block and produce the correct
|
||||||
|
// state root and receipt hash.
|
||||||
|
func (t *BlockTest) validateExecutionWitness(block *types.Block, ew *btExecutionWitness) error {
|
||||||
|
config, ok := Forks[t.json.Network]
|
||||||
|
if !ok {
|
||||||
|
return UnsupportedForkError{t.json.Network}
|
||||||
|
}
|
||||||
|
witness, err := ew.toWitness()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse execution witness: %v", err)
|
||||||
|
}
|
||||||
|
// ExecuteStateless expects the block header to have zeroed root and receipt hash
|
||||||
|
// so it can compute them from the witness. Make a copy with those fields cleared.
|
||||||
|
header := types.CopyHeader(block.Header())
|
||||||
|
expectedRoot := header.Root
|
||||||
|
expectedReceiptHash := header.ReceiptHash
|
||||||
|
header.Root = common.Hash{}
|
||||||
|
header.ReceiptHash = common.Hash{}
|
||||||
|
|
||||||
|
witnessBlock := types.NewBlockWithHeader(header).WithBody(*block.Body())
|
||||||
|
stateRoot, receiptRoot, err := core.ExecuteStateless(context.TODO(), config, vm.Config{}, witnessBlock, witness)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stateless execution failed: %v", err)
|
||||||
|
}
|
||||||
|
if stateRoot != expectedRoot {
|
||||||
|
return fmt.Errorf("execution witness state root mismatch: computed %x, expected %x", stateRoot, expectedRoot)
|
||||||
|
}
|
||||||
|
if receiptRoot != expectedReceiptHash {
|
||||||
|
return fmt.Errorf("execution witness receipt root mismatch: computed %x, expected %x", receiptRoot, expectedReceiptHash)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStatelessInputWitness checks that the execution witness encoded in the
|
||||||
|
// SSZ statelessInputBytes matches the JSON executionWitness field.
|
||||||
|
func validateStatelessInputWitness(inputBytes []byte, ew *btExecutionWitness) error {
|
||||||
|
var input SszStatelessInput
|
||||||
|
if err := input.UnmarshalSSZ(inputBytes); err != nil {
|
||||||
|
return fmt.Errorf("failed to SSZ-decode statelessInputBytes: %v", err)
|
||||||
|
}
|
||||||
|
w := input.Witness
|
||||||
|
|
||||||
|
if len(w.State) != len(ew.State) {
|
||||||
|
return fmt.Errorf("witness state count mismatch: ssz=%d json=%d", len(w.State), len(ew.State))
|
||||||
|
}
|
||||||
|
for i := range w.State {
|
||||||
|
if !bytes.Equal(w.State[i], ew.State[i]) {
|
||||||
|
return fmt.Errorf("witness state[%d] mismatch", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(w.Codes) != len(ew.Codes) {
|
||||||
|
return fmt.Errorf("witness codes count mismatch: ssz=%d json=%d", len(w.Codes), len(ew.Codes))
|
||||||
|
}
|
||||||
|
for i := range w.Codes {
|
||||||
|
if !bytes.Equal(w.Codes[i], ew.Codes[i]) {
|
||||||
|
return fmt.Errorf("witness codes[%d] mismatch", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(w.Headers) != len(ew.Headers) {
|
||||||
|
return fmt.Errorf("witness headers count mismatch: ssz=%d json=%d", len(w.Headers), len(ew.Headers))
|
||||||
|
}
|
||||||
|
for i := range w.Headers {
|
||||||
|
if !bytes.Equal(w.Headers[i], ew.Headers[i]) {
|
||||||
|
return fmt.Errorf("witness headers[%d] mismatch", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ var (
|
||||||
executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests")
|
executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests")
|
||||||
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
|
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
|
||||||
benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks")
|
benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks")
|
||||||
executionSpecBALBlockchainTestDir = filepath.Join(".", "spec-tests-bal", "fixtures", "blockchain_tests")
|
executionSpecBALBlockchainTestDir = filepath.Join(".", "spec-tests-bal", "fixtures", "blockchain_tests")
|
||||||
|
executionSpecZkevmBlockchainTestDir = filepath.Join(".", "spec-tests-zkevm", "fixtures", "blockchain_tests", "for_amsterdam")
|
||||||
)
|
)
|
||||||
|
|
||||||
func readJSON(reader io.Reader, value interface{}) error {
|
func readJSON(reader io.Reader, value interface{}) error {
|
||||||
|
|
|
||||||
85
tests/stateless_ssz.go
Normal file
85
tests/stateless_ssz.go
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// 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 tests
|
||||||
|
|
||||||
|
//go:generate go run github.com/ferranbt/fastssz/sszgen -path . -objs SszWithdrawal,SszExecutionPayload,SszNewPayloadRequest,SszExecutionWitness,SszChainConfig,SszStatelessInput,SszStatelessValidationResult -output stateless_ssz_encoding.go
|
||||||
|
|
||||||
|
// SszWithdrawal mirrors the SSZ encoding of a withdrawal.
|
||||||
|
type SszWithdrawal struct {
|
||||||
|
Index uint64 `ssz-size:"8"`
|
||||||
|
ValidatorIndex uint64 `ssz-size:"8"`
|
||||||
|
Address [20]byte `ssz-size:"20"`
|
||||||
|
Amount [32]byte `ssz-size:"32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszExecutionPayload mirrors the SSZ encoding of an execution payload.
|
||||||
|
type SszExecutionPayload struct {
|
||||||
|
ParentHash [32]byte `ssz-size:"32"`
|
||||||
|
FeeRecipient [20]byte `ssz-size:"20"`
|
||||||
|
StateRoot [32]byte `ssz-size:"32"`
|
||||||
|
ReceiptsRoot [32]byte `ssz-size:"32"`
|
||||||
|
LogsBloom [256]byte `ssz-size:"256"`
|
||||||
|
PrevRandao [32]byte `ssz-size:"32"`
|
||||||
|
BlockNumber uint64
|
||||||
|
GasLimit uint64
|
||||||
|
GasUsed uint64
|
||||||
|
Timestamp uint64
|
||||||
|
ExtraData []byte `ssz-max:"32"`
|
||||||
|
BaseFeePerGas [32]byte `ssz-size:"32"`
|
||||||
|
BlockHash [32]byte `ssz-size:"32"`
|
||||||
|
Transactions [][]byte `ssz-max:"1048576,1073741824"`
|
||||||
|
Withdrawals []*SszWithdrawal `ssz-max:"65536"` // TODO: this is here because of a spec test going over 16
|
||||||
|
BlobGasUsed uint64
|
||||||
|
ExcessBlobGas uint64
|
||||||
|
BlockAccessList []byte `ssz-max:"16777216"`
|
||||||
|
SlotNumber uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszNewPayloadRequest mirrors the SSZ encoding of a new payload request.
|
||||||
|
type SszNewPayloadRequest struct {
|
||||||
|
ExecutionPayload *SszExecutionPayload
|
||||||
|
VersionedHashes [][32]byte `ssz-max:"4096" ssz-size:"?,32"`
|
||||||
|
ParentBeaconBlockRoot [32]byte `ssz-size:"32"`
|
||||||
|
ExecutionRequests [][]byte `ssz-max:"16,1048576"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszExecutionWitness mirrors the SSZ encoding of an execution witness.
|
||||||
|
type SszExecutionWitness struct {
|
||||||
|
State [][]byte `ssz-max:"1048576,1048576"`
|
||||||
|
Codes [][]byte `ssz-max:"65536,16777216"`
|
||||||
|
Headers [][]byte `ssz-max:"256,1024"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszChainConfig mirrors the SSZ encoding of a chain config.
|
||||||
|
type SszChainConfig struct {
|
||||||
|
ChainId uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszStatelessInput mirrors the SSZ encoding of a stateless execution input.
|
||||||
|
type SszStatelessInput struct {
|
||||||
|
NewPayloadRequest *SszNewPayloadRequest
|
||||||
|
Witness *SszExecutionWitness
|
||||||
|
ChainConfig *SszChainConfig
|
||||||
|
PublicKeys [][]byte `ssz-max:"1048576,48"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SszStatelessValidationResult mirrors the SSZ encoding of a stateless validation result.
|
||||||
|
type SszStatelessValidationResult struct {
|
||||||
|
NewPayloadRequestRoot [32]byte `ssz-size:"32"`
|
||||||
|
SuccessfulValidation bool
|
||||||
|
ChainConfig *SszChainConfig
|
||||||
|
}
|
||||||
1423
tests/stateless_ssz_encoding.go
Normal file
1423
tests/stateless_ssz_encoding.go
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue