Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions blocks/blockstest/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,21 @@
// NewBlock constructs an SAE block, wrapping the raw Ethereum block.
func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block, opts ...BlockOption) *blocks.Block {
tb.Helper()
b, err := TryNewBlock(tb, eth, parent, lastSettled, opts...)
require.NoError(tb, err, "blocks.New()")
return b
}

func TryNewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block, opts ...BlockOption) (*blocks.Block, error) {

Check failure on line 81 in blocks/blockstest/blocks.go

View workflow job for this annotation

GitHub Actions / golangci-lint

exported: exported function TryNewBlock should have comment or be unexported (revive)
tb.Helper()

props := options.ApplyTo(&blockProperties{}, opts...)
if props.logger == nil {
props.logger = saetest.NewTBLogger(tb, logging.Warn)
}

b, err := blocks.New(eth, parent, lastSettled, props.logger)
require.NoError(tb, err, "blocks.New()")
return b
return b, err
}

type blockProperties struct {
Expand All @@ -95,6 +101,12 @@
})
}

func WithGenesisSpec(spec *core.Genesis) GenesisOption {

Check failure on line 104 in blocks/blockstest/blocks.go

View workflow job for this annotation

GitHub Actions / golangci-lint

exported: exported function WithGenesisSpec should have comment or be unexported (revive)
return options.Func[genesisConfig](func(gc *genesisConfig) {
gc.genesisSpec = spec
})
}

// NewGenesis constructs a new [core.Genesis], writes it to the database, and
// returns wraps [core.Genesis.ToBlock] with [NewBlock]. It assumes a nil
// [triedb.Config] unless overridden by a [WithTrieDBConfig]. The block is
Expand All @@ -103,9 +115,12 @@
tb.Helper()
conf := options.ApplyTo(&genesisConfig{}, opts...)

gen := &core.Genesis{
Config: config,
Alloc: alloc,
gen := conf.genesisSpec
if gen == nil {
gen = &core.Genesis{
Config: config,
Alloc: alloc,
}
}

tdb := state.NewDatabaseWithConfig(db, conf.tdbConfig).TrieDB()
Expand All @@ -120,7 +135,8 @@
}

type genesisConfig struct {
tdbConfig *triedb.Config
tdbConfig *triedb.Config
genesisSpec *core.Genesis
}

// A GenesisOption configures [NewGenesis].
Expand Down
19 changes: 19 additions & 0 deletions blocks/blockstest/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@
return b
}

func (cb *ChainBuilder) InsertBlock(tb testing.TB, block *types.Block, opts ...ChainOption) (*blocks.Block, error) {

Check failure on line 85 in blocks/blockstest/chain.go

View workflow job for this annotation

GitHub Actions / golangci-lint

exported: exported method ChainBuilder.InsertBlock should have comment or be unexported (revive)
tb.Helper()

allOpts := new(chainOptions)
options.ApplyTo(allOpts, cb.defaultOpts...)
options.ApplyTo(allOpts, opts...)

parent := cb.Last()
// ASK: last settled should be nil?
wb, err := TryNewBlock(tb, block, parent, nil, allOpts.sae...)
if err != nil {
return nil, err
}
cb.chain = append(cb.chain, wb)
cb.blocksByHash.Store(wb.Hash(), wb)

return wb, nil
}

// Last returns the last block to be built by the builder, which MAY be the
// genesis block passed to the constructor.
func (cb *ChainBuilder) Last() *blocks.Block {
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ require (
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/fjl/gencodec v0.1.1 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,11 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fjl/gencodec v0.1.1 h1:DhQY29Q6JLXB/GgMqE86NbOEuvckiYcJCbXFu02toms=
github.com/fjl/gencodec v0.1.1/go.mod h1:chDHL3wKXuBgauP8x3XNZkl5EIAR5SoCTmmmDTZRzmw=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
Expand Down
8 changes: 4 additions & 4 deletions saexec/ethtests/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
bt.skipLoad(`.*randomStatetest94.json.*`)

bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 {

Check failure on line 65 in saexec/ethtests/block_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

G404: Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand) (gosec)
t.Skip("test (randomly) skipped on 32-bit windows")
}
execBlockTest(t, bt, test)
Expand All @@ -85,19 +85,19 @@
}

func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil {
if err := bt.checkFailure(t, test.Run(t, false, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode without snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil {
if err := bt.checkFailure(t, test.Run(t, true, rawdb.HashScheme, nil, nil)); err != nil {
t.Errorf("test in hash mode with snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil {
if err := bt.checkFailure(t, test.Run(t, false, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode without snapshotter failed: %v", err)
return
}
if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil {
if err := bt.checkFailure(t, test.Run(t, true, rawdb.PathScheme, nil, nil)); err != nil {
t.Errorf("test in path mode with snapshotter failed: %v", err)
return
}
Expand Down
102 changes: 46 additions & 56 deletions saexec/ethtests/block_test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,32 @@ package ethtests

import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"reflect"
"testing"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/common/hexutil"
"github.com/ava-labs/libevm/common/math"
"github.com/ava-labs/libevm/consensus/beacon"
"github.com/ava-labs/libevm/consensus/ethash"
"github.com/ava-labs/libevm/core"
"github.com/ava-labs/libevm/core/rawdb"
"github.com/ava-labs/libevm/core/state"
"github.com/ava-labs/libevm/core/state/snapshot"
"github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/core/vm"
"github.com/ava-labs/libevm/log"
"github.com/ava-labs/libevm/params"
"github.com/ava-labs/libevm/rlp"
"github.com/ava-labs/libevm/triedb"
"github.com/ava-labs/libevm/triedb/hashdb"
"github.com/ava-labs/libevm/triedb/pathdb"
"github.com/ava-labs/strevm/blocks/blockstest"
"github.com/ava-labs/strevm/saexec/saexectest"
"github.com/stretchr/testify/require"
)

// A BlockTest checks handling of entire blocks.
Expand Down Expand Up @@ -120,81 +123,63 @@ type btHeaderMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64
}

func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) {
func (t *BlockTest) Run(tb testing.TB, snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *saexectest.SUT)) (result error) {
config, ok := Forks[t.json.Network]
if !ok {
return UnsupportedForkError{t.json.Network}
}
// import pre accounts & construct test genesis block & state root
var (
db = rawdb.NewMemoryDatabase()
tconf = &triedb.Config{
Preimages: true,
}
)

tconf := &triedb.Config{
Preimages: true,
}

if scheme == rawdb.PathScheme {
tconf.PathDB = pathdb.Defaults
} else {
tconf.HashDB = hashdb.Defaults
}
// Commit genesis state
gspec := t.genesis(config)
triedb := triedb.NewDatabase(db, tconf)
gblock, err := gspec.Commit(db, triedb)
if err != nil {
return err
}
triedb.Close() // close the db to prevent memory leak

if gblock.Hash() != t.json.Genesis.Hash {
return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6])
}
if gblock.Root() != t.json.Genesis.StateRoot {
return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6])
}
// Wrap the original engine within the beacon-engine
engine := beacon.New(ethash.NewFaker())

cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true}
if snapshotter {
cache.SnapshotLimit = 1
cache.SnapshotWait = true
}
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
Tracer: tracer,
}, nil, nil)
if err != nil {
return err
}
defer chain.Stop()
ctx, sut := saexectest.NewSUT(tb, saexectest.DefaultHooks(), saexectest.WithGenesisSpec(gspec), saexectest.WithTrieDBConfig(tconf))
gblock := sut.LastExecuted()
require.Equal(tb, gblock.Hash(), t.json.Genesis.Hash)
require.Equal(tb, gblock.PostExecutionStateRoot(), t.json.Genesis.StateRoot)
require.Equal(tb, gblock.Header().Root, t.json.Genesis.StateRoot)

validBlocks, err := t.insertBlocks(chain)
validBlocks, err := t.insertBlocks(ctx, tb, &sut)
if err != nil {
return err
}
// Import succeeded: regardless of whether the _test_ succeeds or not, schedule
// the post-check to run
if postCheck != nil {
defer postCheck(result, chain)
defer postCheck(result, &sut)
}
cmlast := chain.CurrentBlock().Hash()
if common.Hash(t.json.BestBlock) != cmlast {
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast)
last := sut.Chain.Last()
lastHash := last.Hash()
if common.Hash(t.json.BestBlock) != lastHash {
return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, lastHash)
}
newDB, err := chain.State()
if err != nil {
return err
}
if err = t.validatePostState(newDB); err != nil {

sdb, err := state.New(last.PostExecutionStateRoot(), sut.StateCache(), nil)
require.NoErrorf(tb, err, "state.New(%T.PostExecutionStateRoot(), %T.StateCache(), nil)", last, sut)
if err = t.validatePostState(sdb); err != nil {
return fmt.Errorf("post state validation failed: %v", err)
}
// Cross-check the snapshot-to-hash against the trie hash
if snapshotter {
if err := chain.Snapshots().Verify(chain.CurrentBlock().Root); err != nil {
conf := snapshot.Config{
CacheSize: 1,
AsyncBuild: false,
}
snaps, err := snapshot.New(conf, sut.DB, sut.StateCache().TrieDB(), last.PostExecutionStateRoot())
require.NoErrorf(tb, err, "snapshot.New(..., %T.PostExecutionStateRoot())", sut)
if err := snaps.Verify(last.PostExecutionStateRoot()); err != nil {
return err
}
}
return t.validateImportedHeaders(chain, validBlocks)
return t.validateImportedHeaders(sut.Chain, validBlocks)
}

func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
Expand Down Expand Up @@ -229,22 +214,22 @@ See https://github.com/ethereum/tests/wiki/Blockchain-Tests-II
expected we are expected to ignore it and continue processing and then validate the
post state.
*/
func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) {
func (t *BlockTest) insertBlocks(ctx context.Context, tb testing.TB, sut *saexectest.SUT) ([]btBlock, error) {
validBlocks := make([]btBlock, 0)
// insert the test blocks, which will execute all transactions
for bi, b := range t.json.Blocks {
cb, err := b.decode()
if err != nil {
if b.BlockHeader == nil {
log.Info("Block decoding failed", "index", bi, "err", err)
tb.Log("Block decoding failed", "index", bi, "err", err)
continue // OK - block is supposed to be invalid, continue with next block
} else {
return nil, fmt.Errorf("block RLP decoding failed when expected to succeed: %v", err)
}
}
// RLP decoding worked, try to insert into chain:
blocks := types.Blocks{cb}
i, err := blockchain.InsertChain(blocks)
i, err := sut.InsertChain(ctx, tb, blocks)
if err != nil {
if b.BlockHeader == nil {
continue // OK - block is supposed to be invalid, continue with next block
Expand Down Expand Up @@ -360,7 +345,7 @@ func (t *BlockTest) validatePostState(statedb *state.StateDB) error {
return nil
}

func (t *BlockTest) validateImportedHeaders(cm *core.BlockChain, validBlocks []btBlock) error {
func (t *BlockTest) validateImportedHeaders(cb *blockstest.ChainBuilder, validBlocks []btBlock) error {
// to get constant lookup when verifying block headers by hash (some tests have many blocks)
bmap := make(map[common.Hash]btBlock, len(t.json.Blocks))
for _, b := range validBlocks {
Expand All @@ -371,8 +356,13 @@ func (t *BlockTest) validateImportedHeaders(cm *core.BlockChain, validBlocks []b
// block-by-block, so we can only validate imported headers after
// all blocks have been processed by BlockChain, as they may not
// be part of the longest chain until last block is imported.
for b := cm.CurrentBlock(); b != nil && b.Number.Uint64() != 0; b = cm.GetBlockByHash(b.ParentHash).Header() {
if err := validateHeader(bmap[b.Hash()].BlockHeader, b); err != nil {
for b := cb.Last(); b != nil && b.NumberU64() != 0; {
// ASK: Why we use parent hash here? Is it a bug in upstream?
pb, ok := cb.GetBlock(b.ParentHash(), b.NumberU64()-1)
if !ok {
return fmt.Errorf("block %x not found", b.ParentHash())
}
if err := validateHeader(bmap[pb.Hash()].BlockHeader, pb.Header()); err != nil {
return fmt.Errorf("imported block header validation failed: %v", err)
}
}
Expand Down
6 changes: 6 additions & 0 deletions saexec/saexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,9 @@ func (e *Executor) StateCache() state.Database {
func (e *Executor) LastExecuted() *blocks.Block {
return e.lastExecuted.Load()
}

// RefreshQuit replaces the quit channel with a new one. This is used to
// refresh the quit channel after a test has completed. Should only be used in tests.
func (e *Executor) RefreshQuit() {
e.quit = make(chan struct{})
}
Loading
Loading