mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +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
|
||||
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
|
||||
# https://go.dev/dl/
|
||||
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.
|
||||
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"))
|
||||
|
|
@ -384,6 +387,7 @@ func doTest(cmdline []string) {
|
|||
if !*short {
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
downloadBALSpecTestFixtures(csdb, *cachedir)
|
||||
downloadZkevmSpecTestFixtures(csdb, *cachedir)
|
||||
}
|
||||
|
||||
// Configure the toolchain.
|
||||
|
|
@ -462,6 +466,19 @@ func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) str
|
|||
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
|
||||
// any mutations in the source file tree.
|
||||
func doCheckGenerate() {
|
||||
|
|
|
|||
|
|
@ -868,6 +868,42 @@ func TestExecutionSpecBlocktestsBAL(t *testing.T) {
|
|||
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
|
||||
|
||||
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerifyBAL bool) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package tests
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -30,6 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
|
|
@ -44,6 +46,7 @@ import (
|
|||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -68,11 +71,20 @@ type btJSON struct {
|
|||
}
|
||||
|
||||
type btBlock struct {
|
||||
BlockHeader *btHeader
|
||||
ExpectException string
|
||||
Rlp string
|
||||
UncleHeaders []*btHeader
|
||||
AccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"`
|
||||
BlockHeader *btHeader
|
||||
ExpectException string
|
||||
Rlp string
|
||||
UncleHeaders []*btHeader
|
||||
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
|
||||
|
|
@ -470,3 +482,100 @@ func (bb *btBlock) decode() (*types.Block, error) {
|
|||
err = rlp.DecodeBytes(data, &b)
|
||||
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")
|
||||
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
|
||||
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 {
|
||||
|
|
|
|||
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