From ee9c338587c3e2e6e867eca00a8eb80b95f7787e Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Thu, 13 Nov 2025 13:51:56 +0000 Subject: [PATCH 01/14] feat: `blockstest` and `saetest.Wallet` test helpers --- blocks/blockstest/blocks.go | 68 +++++++++++++++++++++++++++ blocks/blockstest/chain.go | 66 ++++++++++++++++++++++++++ go.mod | 29 ++++++++---- go.sum | 60 +++++++++++++++--------- saetest/wallet.go | 92 +++++++++++++++++++++++++++++++++++++ 5 files changed, 283 insertions(+), 32 deletions(-) create mode 100644 blocks/blockstest/blocks.go create mode 100644 blocks/blockstest/chain.go create mode 100644 saetest/wallet.go diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go new file mode 100644 index 0000000..1a0e8c7 --- /dev/null +++ b/blocks/blockstest/blocks.go @@ -0,0 +1,68 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package blockstest provides test helpers for constructing [Streaming +// Asynchronous Execution] (SAE) blocks. +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package blockstest + +import ( + "math/big" + "testing" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/state" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/triedb" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/saetest" +) + +// NewEthBlock constructs a raw Ethereum block with the given arguments. +func NewEthBlock(parent *types.Block, time uint64, txs types.Transactions) *types.Block { + hdr := &types.Header{ + Number: new(big.Int).Add(parent.Number(), big.NewInt(1)), + Time: time, + ParentHash: parent.Hash(), + } + return types.NewBlock(hdr, txs, nil, nil, saetest.TrieHasher()) +} + +// NewBlock constructs an SAE block, wrapping the raw Ethereum block. +func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block) *blocks.Block { + tb.Helper() + b, err := blocks.New(eth, parent, lastSettled, saetest.NewTBLogger(tb, logging.Warn)) + require.NoError(tb, err, "New()") + return b +} + +// 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]. The block is marked as both executed and synchronous. +func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, alloc types.GenesisAlloc) *blocks.Block { + tb.Helper() + var _ *triedb.Config = nil // protect the import to allow linked function comment + + gen := &core.Genesis{ + Config: config, + Alloc: alloc, + } + + tdb := state.NewDatabase(db).TrieDB() + _, hash, err := core.SetupGenesisBlock(db, tdb, gen) + require.NoError(tb, err, "core.SetupGenesisBlock()") + require.NoErrorf(tb, tdb.Commit(hash, true), "%T.Commit(core.SetupGenesisBlock(...))", tdb) + + b := NewBlock(tb, gen.ToBlock(), nil, nil) + require.NoErrorf(tb, b.MarkExecuted(db, gastime.New(0, 1, 0), time.Time{}, new(big.Int), nil, b.SettledStateRoot()), "%T.MarkExecuted()", b) + require.NoErrorf(tb, b.MarkSynchronous(), "%T.MarkSynchronous()", b) + return b +} diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go new file mode 100644 index 0000000..67fcd2d --- /dev/null +++ b/blocks/blockstest/chain.go @@ -0,0 +1,66 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package blockstest provides test helpers for constructing [Streaming +// Asynchronous Execution] (SAE) blocks. +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package blockstest + +import ( + "math/big" + "testing" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + + "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/saetest" +) + +// A ChainBuilder builds a chain of blocks, maintaining necessary invariants. +type ChainBuilder struct { + chain []*blocks.Block + byHash map[common.Hash]*blocks.Block +} + +// NewChainBuilder returns a new ChainBuilder starting from the provided block, +// which MUST NOT be nil. +func NewChainBuilder(genesis *blocks.Block) *ChainBuilder { + return &ChainBuilder{ + chain: []*blocks.Block{genesis}, + byHash: map[common.Hash]*blocks.Block{ + genesis.Hash(): genesis, + }, + } +} + +// NewBlock constructs and returns a new block in the chain. +func (cb *ChainBuilder) NewBlock(tb testing.TB, txs ...*types.Transaction) *blocks.Block { + last := cb.Last() + hdr := &types.Header{ + Number: new(big.Int).Add(last.Number(), big.NewInt(1)), + ParentHash: last.Hash(), + BaseFee: big.NewInt(1), + } + eth := types.NewBlock(hdr, txs, nil, nil, saetest.TrieHasher()) + + cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil)) + return cb.Last() +} + +// 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 { + return cb.chain[len(cb.chain)-1] +} + +// GetBlock returns the block with the given hash and number. If either argument +// fails to match a known block then GetBlock returns `nil, false`. +func (cb *ChainBuilder) GetBlock(hash common.Hash, number uint64) (*blocks.Block, bool) { + b, ok := cb.byHash[hash] + if !ok || b == nil || b.NumberU64() != number { + return nil, false + } + return b, true +} diff --git a/go.mod b/go.mod index e85eefc..7830014 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ module github.com/ava-labs/strevm -go 1.24.7 +go 1.24.8 + +toolchain go1.24.10 require ( github.com/ava-labs/avalanchego v1.13.2 - github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 + github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f github.com/google/go-cmp v0.6.0 github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.10.0 @@ -12,6 +14,8 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -22,15 +26,18 @@ require ( github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect - github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + 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 v0.4.0 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // 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 github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -45,7 +52,9 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/tools v0.38.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -85,11 +94,11 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/go.sum b/go.sum index 6d4d354..38e13de 100644 --- a/go.sum +++ b/go.sum @@ -16,11 +16,13 @@ github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqH github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.13.2 h1:Kx/T2a8vqLlgHde3DWT5zMF5yBIh1rqEd6nJQMMzV/Y= github.com/ava-labs/avalanchego v1.13.2/go.mod h1:s7W/kim5L6hiD2PB1v/Ozy1ZZyoLQ4H6mxVO0aMnxng= -github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 h1:vBMYo+Iazw0rGTr+cwjkBdh5eadLPlv4ywI4lKye3CA= -github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f h1:pOSIbfEE9NmlGRWomkjMV04VO5I6ykiR9V7jQ9IBIkA= +github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f/go.mod h1:DqSotSn4Dx/UJV+d3svfW8raR+cH7+Ohl9BpsQ5HlGU= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -33,6 +35,7 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -66,8 +69,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= -github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= -github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -87,8 +90,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= -github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= 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= @@ -99,6 +102,8 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 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= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -170,6 +175,8 @@ github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qA github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= @@ -185,6 +192,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -196,6 +205,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -323,6 +334,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -342,6 +355,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -397,8 +412,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= @@ -410,8 +425,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -433,8 +448,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -444,8 +459,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -478,22 +493,23 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -509,8 +525,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/saetest/wallet.go b/saetest/wallet.go new file mode 100644 index 0000000..06ee209 --- /dev/null +++ b/saetest/wallet.go @@ -0,0 +1,92 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saetest + +import ( + "crypto/ecdsa" + "encoding/binary" + "testing" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/crypto" + "github.com/ava-labs/libevm/libevm/ethtest" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// A Wallet manages a set of private keys (suitable only for tests) and nonces +// to sign transactions. +type Wallet struct { + accounts []*account + signer types.Signer +} + +type account struct { + key *ecdsa.PrivateKey + nonce uint64 +} + +// NewUNSAFEWallet returns a new wallet with the specified number of accounts. +// Private keys are generated deterministically. +func NewUNSAFEWallet(tb testing.TB, accounts uint, signer types.Signer) *Wallet { + tb.Helper() + + w := &Wallet{ + accounts: make([]*account, accounts), + signer: signer, + } + for i := range accounts { + seed := binary.BigEndian.AppendUint64(nil, uint64(i)) + key := ethtest.UNSAFEDeterministicPrivateKey(tb, seed) + w.accounts[i] = &account{key: key} + } + return w +} + +// Addresses returns all addresses managed by the wallet. +func (w *Wallet) Addresses() []common.Address { + addrs := make([]common.Address, len(w.accounts)) + for i, a := range w.accounts { + addrs[i] = crypto.PubkeyToAddress(a.key.PublicKey) + } + return addrs +} + +// SetNonceAndSign overrides the nonce with the next one for the account, before +// signing and returning the transaction. +func (w *Wallet) SetNonceAndSign(tb testing.TB, account int, data types.TxData) *types.Transaction { + tb.Helper() + + acc := w.accounts[account] + + switch d := data.(type) { + case *types.LegacyTx: + d.Nonce = acc.nonce + case *types.AccessListTx: + d.Nonce = acc.nonce + case *types.DynamicFeeTx: + d.Nonce = acc.nonce + default: + tb.Fatalf("Unsupported transaction type: %T", d) + } + + tx, err := types.SignNewTx(acc.key, w.signer, data) + require.NoError(tb, err, "types.SignNewTx(...)") + + acc.nonce++ + return tx +} + +// MaxAllocFor returns a genesis allocation with [MaxUint256] as the balance for +// all addresses provided. +func MaxAllocFor(addrs ...common.Address) types.GenesisAlloc { + alloc := make(types.GenesisAlloc) + for _, a := range addrs { + alloc[a] = types.Account{ + Balance: new(uint256.Int).SetAllOne().ToBig(), + } + } + return alloc +} From a83e33dbb4bf4309d285561c07a9bbebe96803e0 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Thu, 13 Nov 2025 16:39:02 +0000 Subject: [PATCH 02/14] test: all functionality from last commit --- blocks/blockstest/blocks.go | 17 ++++- blocks/blockstest/chain.go | 23 +----- blocks/blockstest/chain_test.go | 127 ++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 blocks/blockstest/chain_test.go diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index 1a0e8c7..72cc29c 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/libevm/core/state" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/libevm/options" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/triedb" "github.com/stretchr/testify/require" @@ -26,16 +27,28 @@ import ( "github.com/ava-labs/strevm/saetest" ) +// An EthBlockOption configures the [types.Header] created by [NewEthBlock]. It +// SHOULD NOT modify the `Number` and `ParentHash`, but MAY modify any other +// field. +type EthBlockOption = options.Option[types.Header] + // NewEthBlock constructs a raw Ethereum block with the given arguments. -func NewEthBlock(parent *types.Block, time uint64, txs types.Transactions) *types.Block { +func NewEthBlock(parent *types.Block, txs types.Transactions, opts ...EthBlockOption) *types.Block { hdr := &types.Header{ Number: new(big.Int).Add(parent.Number(), big.NewInt(1)), - Time: time, ParentHash: parent.Hash(), + BaseFee: big.NewInt(0), } + hdr = options.ApplyTo(hdr, opts...) return types.NewBlock(hdr, txs, nil, nil, saetest.TrieHasher()) } +// ModifyHeader returns an option to modify the [types.Header] constructed by +// [NewEthBlock]. See [EthBlockOption] for caveats. +func ModifyHeader(fn func(*types.Header)) EthBlockOption { + return options.Func[types.Header](fn) +} + // NewBlock constructs an SAE block, wrapping the raw Ethereum block. func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block) *blocks.Block { tb.Helper() diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 67fcd2d..6fda2a9 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -8,14 +8,12 @@ package blockstest import ( - "math/big" "testing" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/strevm/blocks" - "github.com/ava-labs/strevm/saetest" ) // A ChainBuilder builds a chain of blocks, maintaining necessary invariants. @@ -36,15 +34,10 @@ func NewChainBuilder(genesis *blocks.Block) *ChainBuilder { } // NewBlock constructs and returns a new block in the chain. -func (cb *ChainBuilder) NewBlock(tb testing.TB, txs ...*types.Transaction) *blocks.Block { +func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts ...EthBlockOption) *blocks.Block { + tb.Helper() last := cb.Last() - hdr := &types.Header{ - Number: new(big.Int).Add(last.Number(), big.NewInt(1)), - ParentHash: last.Hash(), - BaseFee: big.NewInt(1), - } - eth := types.NewBlock(hdr, txs, nil, nil, saetest.TrieHasher()) - + eth := NewEthBlock(last.EthBlock(), txs, opts...) cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil)) return cb.Last() } @@ -54,13 +47,3 @@ func (cb *ChainBuilder) NewBlock(tb testing.TB, txs ...*types.Transaction) *bloc func (cb *ChainBuilder) Last() *blocks.Block { return cb.chain[len(cb.chain)-1] } - -// GetBlock returns the block with the given hash and number. If either argument -// fails to match a known block then GetBlock returns `nil, false`. -func (cb *ChainBuilder) GetBlock(hash common.Hash, number uint64) (*blocks.Block, bool) { - b, ok := cb.byHash[hash] - if !ok || b == nil || b.NumberU64() != number { - return nil, false - } - return b, true -} diff --git a/blocks/blockstest/chain_test.go b/blocks/blockstest/chain_test.go new file mode 100644 index 0000000..61753f9 --- /dev/null +++ b/blocks/blockstest/chain_test.go @@ -0,0 +1,127 @@ +package blockstest + +import ( + "math/big" + "testing" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/consensus" + "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/types" + "github.com/ava-labs/libevm/core/vm" + "github.com/ava-labs/libevm/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/saetest" +) + +func TestChainBuilder(t *testing.T) { + const ( + numAccounts = 2 + numBlocks = 3 + txsPerAccountPerBlock = 3 + ) + + config := params.AllDevChainProtocolChanges + wallet := saetest.NewUNSAFEWallet(t, numAccounts, types.LatestSigner(config)) + alloc := saetest.MaxAllocFor(wallet.Addresses()...) + + db := rawdb.NewMemoryDatabase() + + // Although the point of SAE is to replace [core.BlockChain], it remains the + // ground truth for correct block and transaction processing. If we were to + // test [ChainBuilder] with an SAE executor, which is itself going to be + // tested with the builder, then we would have a circular argument for + // correctness. + bc, err := core.NewBlockChain( + db, nil, + &core.Genesis{ + Config: config, + Alloc: alloc, + }, + nil, engine{}, vm.Config{}, + func(*types.Header) bool { return true }, + nil, + ) + require.NoError(t, err, "core.NewBlockChain()") + stateProc := core.NewStateProcessor(config, bc, engine{}) + + sdb, err := state.New(bc.Genesis().Root(), state.NewDatabase(db), nil) + require.NoError(t, err, "state.New(%T.Genesis().Root())", bc) + + build := NewChainBuilder(NewBlock(t, bc.Genesis(), nil, nil)) + dest := common.Address{'d', 'e', 's', 't'} + for range numBlocks { + var txs types.Transactions + for range txsPerAccountPerBlock { + for i := range numAccounts { + tx := wallet.SetNonceAndSign(t, i, &types.LegacyTx{ + To: &dest, + Value: big.NewInt(1), + Gas: params.TxGas, + GasPrice: big.NewInt(1), + }) + txs = append(txs, tx) + } + } + b := build.NewBlock(t, txs, ModifyHeader(func(h *types.Header) { + h.GasLimit = 100e6 + })) + + receipts, _, _, err := stateProc.Process(b.EthBlock(), sdb, *bc.GetVMConfig()) + require.NoError(t, err, "%T.Process(%T.NewBlock().EthBlock()...)", stateProc, build) + for _, r := range receipts { + assert.Equal(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r) + } + } + + t.Run("balance_of_recipient", func(t *testing.T) { + bal := sdb.GetBalance(dest) + require.True(t, bal.IsUint64(), "%T.GetBalance(...).IsUint64()", sdb) + require.Equal(t, uint64(numAccounts*numBlocks*txsPerAccountPerBlock), bal.Uint64()) + }) +} + +// engine is a fake [consensus.Engine], implementing the minimum number of +// methods to avoid a panic. +type engine struct { + consensus.Engine +} + +func (engine) VerifyHeader(consensus.ChainHeaderReader, *types.Header) error { + return nil +} + +func (engine) Author(*types.Header) (common.Address, error) { + return common.Address{'a', 'u', 't', 'h'}, nil +} + +func (engine) Finalize(consensus.ChainHeaderReader, *types.Header, *state.StateDB, []*types.Transaction, []*types.Header, []*types.Withdrawal) { +} + +func TestNewGenesis(t *testing.T) { + config := params.AllDevChainProtocolChanges + signer := types.LatestSigner(config) + wallet := saetest.NewUNSAFEWallet(t, 10, signer) + alloc := saetest.MaxAllocFor(wallet.Addresses()...) + + db := rawdb.NewMemoryDatabase() + gen := NewGenesis(t, db, config, alloc) + + assert.True(t, gen.Executed(), "genesis.Executed()") + assert.NoError(t, gen.WaitUntilSettled(t.Context()), "genesis.WaitUntilSettled()") + assert.Equal(t, gen.Hash(), gen.LastSettled().Hash(), "genesis.LastSettled().Hash() is self") + + t.Run("alloc", func(t *testing.T) { + sdb, err := state.New(gen.SettledStateRoot(), state.NewDatabase(db), nil) + require.NoError(t, err, "state.New(genesis.SettledStateRoot())") + for i, addr := range wallet.Addresses() { + want := new(uint256.Int).SetAllOne() + assert.Truef(t, sdb.GetBalance(addr).Eq(want), "%T.GetBalance(%T.Addresses()[%d]) is max uint256", sdb, wallet, i) + } + }) +} From d06d0a84751eddf930895c7a2f9a59ba3d43e056 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Thu, 13 Nov 2025 16:41:54 +0000 Subject: [PATCH 03/14] chore: placate the linter --- blocks/blockstest/chain_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blocks/blockstest/chain_test.go b/blocks/blockstest/chain_test.go index 61753f9..5a1491f 100644 --- a/blocks/blockstest/chain_test.go +++ b/blocks/blockstest/chain_test.go @@ -1,3 +1,6 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + package blockstest import ( From b00e0446bf4d84802ae55b11581d0a8f158131da Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 08:40:09 +0000 Subject: [PATCH 04/14] refactor: remove unused `ChainBuilder.byHash` --- blocks/blockstest/chain.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 6fda2a9..031fd93 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -10,7 +10,6 @@ package blockstest import ( "testing" - "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/strevm/blocks" @@ -18,8 +17,7 @@ import ( // A ChainBuilder builds a chain of blocks, maintaining necessary invariants. type ChainBuilder struct { - chain []*blocks.Block - byHash map[common.Hash]*blocks.Block + chain []*blocks.Block } // NewChainBuilder returns a new ChainBuilder starting from the provided block, @@ -27,9 +25,6 @@ type ChainBuilder struct { func NewChainBuilder(genesis *blocks.Block) *ChainBuilder { return &ChainBuilder{ chain: []*blocks.Block{genesis}, - byHash: map[common.Hash]*blocks.Block{ - genesis.Hash(): genesis, - }, } } From 4ffdfe0a115a6cfa3488adef8cdb07ba9d369b0a Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 08:41:39 +0000 Subject: [PATCH 05/14] fix: mark genesis block executed at correct Unix time --- blocks/blockstest/blocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index 72cc29c..4b6c39f 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -75,7 +75,7 @@ func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, al require.NoErrorf(tb, tdb.Commit(hash, true), "%T.Commit(core.SetupGenesisBlock(...))", tdb) b := NewBlock(tb, gen.ToBlock(), nil, nil) - require.NoErrorf(tb, b.MarkExecuted(db, gastime.New(0, 1, 0), time.Time{}, new(big.Int), nil, b.SettledStateRoot()), "%T.MarkExecuted()", b) + require.NoErrorf(tb, b.MarkExecuted(db, gastime.New(gen.Timestamp, 1, 0), time.Time{}, new(big.Int), nil, b.SettledStateRoot()), "%T.MarkExecuted()", b) require.NoErrorf(tb, b.MarkSynchronous(), "%T.MarkSynchronous()", b) return b } From 1b146b754858997116a5e2bfa7e1654610035474 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 09:12:00 +0000 Subject: [PATCH 06/14] doc: clarify `Wallet.SetNonceAndSign` comment --- saetest/wallet.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/saetest/wallet.go b/saetest/wallet.go index 06ee209..5525cbc 100644 --- a/saetest/wallet.go +++ b/saetest/wallet.go @@ -54,8 +54,10 @@ func (w *Wallet) Addresses() []common.Address { return addrs } -// SetNonceAndSign overrides the nonce with the next one for the account, before -// signing and returning the transaction. +// SetNonceAndSign overrides the nonce in the `data` with the next one for the +// account, then signs and returns the transaction. The wallet's record of the +// account nonce begins at zero and increments after every successful call to +// this method. func (w *Wallet) SetNonceAndSign(tb testing.TB, account int, data types.TxData) *types.Transaction { tb.Helper() From bf3f56657020eb09367beb096502ab5b5d04741c Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:08:21 +0000 Subject: [PATCH 07/14] refactor: clearer error message if block construction fails Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com> Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- blocks/blockstest/blocks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index 4b6c39f..db03e5c 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -53,7 +53,7 @@ func ModifyHeader(fn func(*types.Header)) EthBlockOption { func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block) *blocks.Block { tb.Helper() b, err := blocks.New(eth, parent, lastSettled, saetest.NewTBLogger(tb, logging.Warn)) - require.NoError(tb, err, "New()") + require.NoError(tb, err, "blocks.New()") return b } From de291e46d1aeaa5f382a10dd5371e93f3843020d Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 15:18:08 +0000 Subject: [PATCH 08/14] feat: allow override of `triedb.Config` in `NewGenesis()` --- blocks/blockstest/blocks.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index db03e5c..7fa0a69 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -59,17 +59,18 @@ func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block // 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]. The block is marked as both executed and synchronous. -func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, alloc types.GenesisAlloc) *blocks.Block { +// [triedb.Config] unless overridden by a [WithTrieDBConfig]. The block is +// marked as both executed and synchronous. +func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, alloc types.GenesisAlloc, opts ...GenesisOption) *blocks.Block { tb.Helper() - var _ *triedb.Config = nil // protect the import to allow linked function comment + conf := options.ApplyTo(&genesisConfig{}, opts...) gen := &core.Genesis{ Config: config, Alloc: alloc, } - tdb := state.NewDatabase(db).TrieDB() + tdb := state.NewDatabaseWithConfig(db, conf.tdbConfig).TrieDB() _, hash, err := core.SetupGenesisBlock(db, tdb, gen) require.NoError(tb, err, "core.SetupGenesisBlock()") require.NoErrorf(tb, tdb.Commit(hash, true), "%T.Commit(core.SetupGenesisBlock(...))", tdb) @@ -79,3 +80,17 @@ func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, al require.NoErrorf(tb, b.MarkSynchronous(), "%T.MarkSynchronous()", b) return b } + +type genesisConfig struct { + tdbConfig *triedb.Config +} + +// A GenesisOption configures [NewGenesis]. +type GenesisOption = options.Option[genesisConfig] + +// WithTrieDBConfig override the [triedb.Config] used by [NewGenesis]. +func WithTrieDBConfig(tc *triedb.Config) GenesisOption { + return options.Func[genesisConfig](func(gc *genesisConfig) { + gc.tdbConfig = tc + }) +} From eb954b27de113ad299c08e589940dbff2487a7a6 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 15:26:38 +0000 Subject: [PATCH 09/14] chore: remove `toolchain` from `go.mod` --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 7830014..0fd7b42 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/ava-labs/strevm go 1.24.8 -toolchain go1.24.10 - require ( github.com/ava-labs/avalanchego v1.13.2 github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f From 30a332f60ef81af8154d519bdaf6e48d36c990ce Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 21:35:48 +0000 Subject: [PATCH 10/14] chore: rename `chain_test.go` to `blocks_test.go` --- blocks/blockstest/{chain_test.go => blocks_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename blocks/blockstest/{chain_test.go => blocks_test.go} (100%) diff --git a/blocks/blockstest/chain_test.go b/blocks/blockstest/blocks_test.go similarity index 100% rename from blocks/blockstest/chain_test.go rename to blocks/blockstest/blocks_test.go From 58e1f284f18f2ddd9df1d793c08421b0a64ae4e3 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 21:39:21 +0000 Subject: [PATCH 11/14] test: assert block number in receipts --- blocks/blockstest/blocks_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blocks/blockstest/blocks_test.go b/blocks/blockstest/blocks_test.go index 5a1491f..2f1eec4 100644 --- a/blocks/blockstest/blocks_test.go +++ b/blocks/blockstest/blocks_test.go @@ -22,7 +22,7 @@ import ( "github.com/ava-labs/strevm/saetest" ) -func TestChainBuilder(t *testing.T) { +func TestIntegration(t *testing.T) { const ( numAccounts = 2 numBlocks = 3 @@ -58,7 +58,9 @@ func TestChainBuilder(t *testing.T) { build := NewChainBuilder(NewBlock(t, bc.Genesis(), nil, nil)) dest := common.Address{'d', 'e', 's', 't'} - for range numBlocks { + for i := range numBlocks { + blockNum := uint64(i + 1) // Genesis is block 0 + var txs types.Transactions for range txsPerAccountPerBlock { for i := range numAccounts { @@ -79,6 +81,7 @@ func TestChainBuilder(t *testing.T) { require.NoError(t, err, "%T.Process(%T.NewBlock().EthBlock()...)", stateProc, build) for _, r := range receipts { assert.Equal(t, types.ReceiptStatusSuccessful, r.Status, "%T.Status", r) + assert.Equal(t, blockNum, r.BlockNumber.Uint64(), "%T.BlockNumber", r) } } From 5a976181c5fd774d30bcdbf1a815ba48dcf7977b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 21:49:49 +0000 Subject: [PATCH 12/14] feat: `NewEthBlock` option to set receipts --- blocks/blockstest/blocks.go | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index 7fa0a69..73360e0 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -9,6 +9,7 @@ package blockstest import ( "math/big" + "slices" "testing" "time" @@ -27,26 +28,43 @@ import ( "github.com/ava-labs/strevm/saetest" ) -// An EthBlockOption configures the [types.Header] created by [NewEthBlock]. It -// SHOULD NOT modify the `Number` and `ParentHash`, but MAY modify any other -// field. -type EthBlockOption = options.Option[types.Header] +// An EthBlockOption configures the default block properties created by +// [NewEthBlock]. +type EthBlockOption = options.Option[ethBlockProperties] // NewEthBlock constructs a raw Ethereum block with the given arguments. func NewEthBlock(parent *types.Block, txs types.Transactions, opts ...EthBlockOption) *types.Block { - hdr := &types.Header{ - Number: new(big.Int).Add(parent.Number(), big.NewInt(1)), - ParentHash: parent.Hash(), - BaseFee: big.NewInt(0), + props := ðBlockProperties{ + header: &types.Header{ + Number: new(big.Int).Add(parent.Number(), big.NewInt(1)), + ParentHash: parent.Hash(), + BaseFee: big.NewInt(0), + }, } - hdr = options.ApplyTo(hdr, opts...) - return types.NewBlock(hdr, txs, nil, nil, saetest.TrieHasher()) + props = options.ApplyTo(props, opts...) + return types.NewBlock(props.header, txs, nil, props.receipts, saetest.TrieHasher()) +} + +type ethBlockProperties struct { + header *types.Header + receipts types.Receipts } // ModifyHeader returns an option to modify the [types.Header] constructed by -// [NewEthBlock]. See [EthBlockOption] for caveats. +// [NewEthBlock]. It SHOULD NOT modify the `Number` and `ParentHash`, but MAY +// modify any other field. func ModifyHeader(fn func(*types.Header)) EthBlockOption { - return options.Func[types.Header](fn) + return options.Func[ethBlockProperties](func(p *ethBlockProperties) { + fn(p.header) + }) +} + +// WithReceipts returns an option to set the receipts of a block constructed by +// [NewEthBlock]. +func WithReceipts(rs types.Receipts) EthBlockOption { + return options.Func[ethBlockProperties](func(p *ethBlockProperties) { + p.receipts = slices.Clone(rs) + }) } // NewBlock constructs an SAE block, wrapping the raw Ethereum block. From 341217fb10554e9fbee27f26465705086773df7a Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 21:52:42 +0000 Subject: [PATCH 13/14] chore: TODO for `ChainBuilder` to support last-settled blocks --- blocks/blockstest/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 031fd93..ef555bd 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -33,7 +33,7 @@ func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts . tb.Helper() last := cb.Last() eth := NewEthBlock(last.EthBlock(), txs, opts...) - cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil)) + cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil)) // TODO(arr4n) support last-settled blocks return cb.Last() } From 471f0c326b90112c4cce32e286e8bbbfd49833d0 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 14 Nov 2025 21:53:41 +0000 Subject: [PATCH 14/14] chore: placate the linter --- blocks/blockstest/blocks_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blocks/blockstest/blocks_test.go b/blocks/blockstest/blocks_test.go index 2f1eec4..53ef35f 100644 --- a/blocks/blockstest/blocks_test.go +++ b/blocks/blockstest/blocks_test.go @@ -59,7 +59,8 @@ func TestIntegration(t *testing.T) { build := NewChainBuilder(NewBlock(t, bc.Genesis(), nil, nil)) dest := common.Address{'d', 'e', 's', 't'} for i := range numBlocks { - blockNum := uint64(i + 1) // Genesis is block 0 + // Genesis is block 0 + blockNum := uint64(i + 1) //nolint:gosec // Known to not overflow var txs types.Transactions for range txsPerAccountPerBlock {