From 171cd6f58c78ec3ac0b8e06b19b681eb5cbf97df Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 26 Sep 2025 10:29:21 +0100 Subject: [PATCH 01/52] feat: `saexec` package --- blocks/blockstest/blockstest.go | 38 +++ blocks/export.go | 6 + cmputils/types.go | 35 +++ dummy/dummy.go | 35 +++ gastime/gastime.go | 14 +- go.mod | 10 +- go.sum | 20 +- hook/hook.go | 44 +++ params/params.go | 12 + saetest/genesis.go | 65 ++++ saetest/saetest.go | 78 +++++ saetest/weth/weth.go | 41 +++ saexec/execution.go | 184 +++++++++++ saexec/saexec.go | 139 +++++++++ saexec/saexec_test.go | 528 ++++++++++++++++++++++++++++++++ saexec/subscription.go | 43 +++ 16 files changed, 1284 insertions(+), 8 deletions(-) create mode 100644 blocks/blockstest/blockstest.go create mode 100644 cmputils/types.go create mode 100644 dummy/dummy.go create mode 100644 hook/hook.go create mode 100644 params/params.go create mode 100644 saetest/genesis.go create mode 100644 saetest/weth/weth.go create mode 100644 saexec/execution.go create mode 100644 saexec/saexec.go create mode 100644 saexec/saexec_test.go create mode 100644 saexec/subscription.go diff --git a/blocks/blockstest/blockstest.go b/blocks/blockstest/blockstest.go new file mode 100644 index 0000000..8ff2e8c --- /dev/null +++ b/blocks/blockstest/blockstest.go @@ -0,0 +1,38 @@ +// 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/avalanchego/utils/logging" + "github.com/ava-labs/libevm/core/types" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/blocks" + "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 +} diff --git a/blocks/export.go b/blocks/export.go index 80541e7..1f4e4c6 100644 --- a/blocks/export.go +++ b/blocks/export.go @@ -37,6 +37,9 @@ func (b *Block) BuildTime() uint64 { return b.b.Time() } // Hash returns [types.Block.Hash] from the wrapped [types.Block]. func (b *Block) Hash() common.Hash { return b.b.Hash() } +// Header returns [types.Block.Header] from the wrapped [types.Block]. +func (b *Block) Header() *types.Header { return b.b.Header() } + // ParentHash returns [types.Block.ParentHash] from the wrapped [types.Block]. func (b *Block) ParentHash() common.Hash { return b.b.ParentHash() } @@ -45,3 +48,6 @@ func (b *Block) NumberU64() uint64 { return b.b.NumberU64() } // Number returns [types.Block.Number] from the wrapped [types.Block]. func (b *Block) Number() *big.Int { return b.b.Number() } + +// Transactions returns [types.Block.Transactions] from the wrapped [types.Block]. +func (b *Block) Transactions() types.Transactions { return b.b.Transactions() } diff --git a/cmputils/types.go b/cmputils/types.go new file mode 100644 index 0000000..d82ae07 --- /dev/null +++ b/cmputils/types.go @@ -0,0 +1,35 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package cmputils + +import ( + "math/big" + + "github.com/ava-labs/libevm/core/types" + "github.com/google/go-cmp/cmp" +) + +// BigInts returns a [cmp.Comparer] for [big.Int] pointers. A nil pointer is not +// equal to zero. +func BigInts() cmp.Option { + return ComparerWithNilCheck(func(a, b *big.Int) bool { + return a.Cmp(b) == 0 + }) +} + +// BlocksByHash returns a [cmp.Comparer] for [types.Block] pointers, equating +// them by hash alone. +func BlocksByHash() cmp.Option { + return ComparerWithNilCheck(func(b, c *types.Block) bool { + return b.Hash() == c.Hash() + }) +} + +// ReceiptsByTxHash returns a [cmp.Comparer] for [types.Receipt] pointers, +// equating them by transaction hash alone. +func ReceiptsByTxHash() cmp.Option { + return ComparerWithNilCheck(func(r, s *types.Receipt) bool { + return r.TxHash == s.TxHash + }) +} diff --git a/dummy/dummy.go b/dummy/dummy.go new file mode 100644 index 0000000..8a2aed5 --- /dev/null +++ b/dummy/dummy.go @@ -0,0 +1,35 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package dummy provides dummy implementations of interfaces required, but not +// actually used by Ethereum transaction execution. Although these are used in +// production and aren't test doubles, they effectively fill the same role as a +// dummy as described in https://martinfowler.com/bliki/TestDouble.html. +package dummy + +import ( + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/consensus" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/types" +) + +// ChainContext returns a dummy that returns [Engine] when its Engine() method +// is called, and panics when its GetHeader() method is called. +func ChainContext() core.ChainContext { + return chainContext{} +} + +// Engine returns a dummy that panics when its Author() method is called. +func Engine() consensus.Engine { + return engine{} +} + +type ( + chainContext struct{} + engine struct{ consensus.Engine } +) + +func (chainContext) Engine() consensus.Engine { return engine{} } +func (chainContext) GetHeader(common.Hash, uint64) *types.Header { panic("unimplemented") } +func (engine) Author(h *types.Header) (common.Address, error) { panic("unimplemented") } diff --git a/gastime/gastime.go b/gastime/gastime.go index cbb2144..9ed9a5f 100644 --- a/gastime/gastime.go +++ b/gastime/gastime.go @@ -58,6 +58,13 @@ func New(unixSeconds uint64, target, startingExcess gas.Gas) *Time { // TargetToRate is the ratio between [Time.Target] and [proxytime.Time.Rate]. const TargetToRate = 2 +// TargetToExcessScaling is the ratio between [Time.Target] and the reciprocal +// of the [Time.Excess] coefficient used in calculating [Time.Price]. In +// [ACP-176] this is the K variable. +// +// [ACP-176]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/176-dynamic-evm-gas-limit-and-price-discovery-updates +const TargetToExcessScaling = 87 + // MaxTarget is the maximum allowable [Time.Target] to avoid overflows of the // associated [proxytime.Time.Rate]. Values above this are silently clamped. const MaxTarget = gas.Gas(math.MaxUint64 / TargetToRate) @@ -91,14 +98,11 @@ func (tm *Time) Price() gas.Price { // excessScalingFactor returns the K variable of ACP-103/176, i.e. 87*T, capped // at [math.MaxUint64]. func (tm *Time) excessScalingFactor() gas.Gas { - const ( - targetToK = 87 - overflowThreshold = math.MaxUint64 / targetToK - ) + const overflowThreshold = math.MaxUint64 / TargetToExcessScaling if tm.target > overflowThreshold { return math.MaxUint64 } - return targetToK * tm.target + return TargetToExcessScaling * tm.target } // BaseFee is equivalent to [Time.Price], returning the result as a uint256 for diff --git a/go.mod b/go.mod index e85eefc..d0d045b 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,16 @@ go 1.24.7 require ( github.com/ava-labs/avalanchego v1.13.2 github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.10.0 + go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) 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 @@ -23,6 +26,7 @@ require ( 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/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/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect @@ -31,6 +35,8 @@ require ( 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 +51,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/mod v0.25.0 // indirect golang.org/x/sync v0.15.0 // indirect + golang.org/x/tools v0.34.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 6d4d354..29b2706 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ 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= @@ -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= @@ -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= @@ -161,8 +166,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -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= @@ -478,6 +493,7 @@ 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= diff --git a/hook/hook.go b/hook/hook.go new file mode 100644 index 0000000..edccc4b --- /dev/null +++ b/hook/hook.go @@ -0,0 +1,44 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package hook defines points in an SAE block's lifecycle at which common or +// user-injected behaviour needs to be performed. Functions in this package +// SHOULD be called by all code dealing with a block at the respective point in +// its lifecycle, be that during validation, execution, or otherwise. +package hook + +import ( + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/params" + + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/intmath" + saeparams "github.com/ava-labs/strevm/params" +) + +// Points define user-injected hook points. +type Points interface { + GasTarget(parent *types.Block) gas.Gas +} + +// BeforeBlock is intended to be called before processing a block, with the gas +// target sourced from [Points]. +func BeforeBlock(clock *gastime.Time, block *types.Header, target gas.Gas) error { + clock.FastForwardTo(block.Time) + return clock.SetTarget(target) +} + +// AfterBlock is intended to be called after processing a block, with the gas +// sourced from [types.Block.GasUsed] or equivalent. +func AfterBlock(clock *gastime.Time, used gas.Gas) { + clock.Tick(used) +} + +// MinimumGasConsumption MUST be used as the implementation for the respective +// method on [params.RulesHooks]. The concrete type implementing the hooks MUST +// propagate incoming and return arguments unchanged. +func MinimumGasConsumption(txLimit uint64) uint64 { + _ = (params.RulesHooks)(nil) // keep the import to allow [] doc links + return intmath.CeilDiv(txLimit, saeparams.Lambda) +} diff --git a/params/params.go b/params/params.go new file mode 100644 index 0000000..6b650a6 --- /dev/null +++ b/params/params.go @@ -0,0 +1,12 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package params declares [Streaming Asynchronous Execution] (SAE) parameters. +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package params + +// Lambda is the denominator for computing the minimum gas consumed per +// transaction. For a transaction with gas limit `g`, the minimum consumption is +// ceil(g/Lambda). +const Lambda = 2 diff --git a/saetest/genesis.go b/saetest/genesis.go new file mode 100644 index 0000000..76bafae --- /dev/null +++ b/saetest/genesis.go @@ -0,0 +1,65 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saetest + +import ( + "crypto/ecdsa" + "testing" + + "github.com/ava-labs/libevm/common" + "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/crypto" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/triedb" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// Genesis constructs a new [core.Genesis], writes it to the database, and +// returns [core.Genesis.ToBlock]. It assumes a nil [triedb.Config]. +func Genesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, alloc types.GenesisAlloc) *types.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.SetubGenesisBlock(...))", tdb) + + return gen.ToBlock() +} + +// MaxUint256 returns 2^256-1. +func MaxUint256() *uint256.Int { + return new(uint256.Int).SetAllOne() +} + +// 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: MaxUint256().ToBig(), + } + } + return alloc +} + +// KeyWithMaxAlloc generates a new [crypto.S256] key and returns it along with +// a genesis alloc providing the key's address with a balance of [MaxUint256]. +func KeyWithMaxAlloc(tb testing.TB) (*ecdsa.PrivateKey, types.GenesisAlloc) { + tb.Helper() + key, err := crypto.GenerateKey() + require.NoError(tb, err, "crypto.GenerateKey()") + return key, MaxAllocFor(crypto.PubkeyToAddress(key.PublicKey)) +} diff --git a/saetest/saetest.go b/saetest/saetest.go index b0cc7d6..941b163 100644 --- a/saetest/saetest.go +++ b/saetest/saetest.go @@ -8,9 +8,16 @@ package saetest import ( + "slices" + "sync" + + "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/event" "github.com/ava-labs/libevm/trie" "github.com/google/go-cmp/cmp" + + "github.com/ava-labs/strevm/hook" ) // TrieHasher returns an arbitrary trie hasher. @@ -27,3 +34,74 @@ func MerkleRootsEqual[T types.DerivableList](a, b T) bool { func CmpByMerkleRoots[T types.DerivableList]() cmp.Option { return cmp.Comparer(MerkleRootsEqual[T]) } + +// An EventCollector collects all events received from an [event.Subscription]. +// All methods are safe for concurrent use. +type EventCollector[T any] struct { + ch chan T + done chan struct{} + sub event.Subscription + + all []T + cond *sync.Cond +} + +// NewEventCollector returns a new [EventCollector], subscribing via the +// provided function. [EventCollector.Unsubscribe] must be called to release +// resources. +func NewEventCollector[T any](subscribe func(chan<- T) event.Subscription) *EventCollector[T] { + c := &EventCollector[T]{ + ch: make(chan T), + done: make(chan struct{}), + cond: sync.NewCond(&sync.Mutex{}), + } + c.sub = subscribe(c.ch) + go c.collect() + return c +} + +func (c *EventCollector[T]) collect() { + defer close(c.done) + for x := range c.ch { + c.cond.L.Lock() + c.all = append(c.all, x) + c.cond.L.Unlock() + c.cond.Broadcast() + } +} + +// All returns all events received thus far. +func (c *EventCollector[T]) All() []T { + c.cond.L.Lock() + defer c.cond.L.Unlock() + return slices.Clone(c.all) +} + +// Unsubscribe unsubscribes from the subscription and returns the error, +// possibly nil, received on [event.Subscription.Err]. +func (c *EventCollector[T]) Unsubscribe() error { + c.sub.Unsubscribe() + err := <-c.sub.Err() + close(c.ch) + <-c.done + return err +} + +// WaitForAtLeast blocks until at least `n` events have been received. +func (c *EventCollector[T]) WaitForAtLeast(n int) { + c.cond.L.Lock() + for len(c.all) < n { + c.cond.Wait() + } + c.cond.L.Unlock() +} + +// HookStub implements [hook.Points]. +type HookStub struct { + Target gas.Gas +} + +var _ hook.Points = (*HookStub)(nil) + +// GasTarget ignores its argument and always returns [HookStub.Target]. +func (s *HookStub) GasTarget(*types.Block) gas.Gas { return s.Target } diff --git a/saetest/weth/weth.go b/saetest/weth/weth.go new file mode 100644 index 0000000..8e11071 --- /dev/null +++ b/saetest/weth/weth.go @@ -0,0 +1,41 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// The above copyright and licensing exclude the original WETH9 contract and +// compiled artefacts, which are licensed under the following: +// +// Copyright (C) 2015, 2016, 2017 Dapphub +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +// Package weth provides bytecode and ABI bindings for the WETH9 contract, as +// deployed to 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 on Ethereum mainnet. +package weth + +import "github.com/ava-labs/libevm/common" + +const ( + creation = "0x60606040526040805190810160405280600d81526020017f57726170706564204574686572000000000000000000000000000000000000008152506000908051906020019061004f9291906100c8565b506040805190810160405280600481526020017f57455448000000000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100c8565b506012600260006101000a81548160ff021916908360ff16021790555034156100c357600080fd5b61016d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010957805160ff1916838001178555610137565b82800160010185558215610137579182015b8281111561013657825182559160200191906001019061011b565b5b5090506101449190610148565b5090565b61016a91905b8082111561016657600081600090555060010161014e565b5090565b90565b610c348061017c6000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029" + deployed = "0x6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029" +) + +// CreationCode returns the EVM bytecode for deploying the WETH9 contract. +func CreationCode() []byte { + return common.FromHex(creation) +} + +// ByteCode returns the deployed EVM bytecode of the WETH9 contract. +func ByteCode() []byte { + return common.FromHex(deployed) +} diff --git a/saexec/execution.go b/saexec/execution.go new file mode 100644 index 0000000..5cc8ed9 --- /dev/null +++ b/saexec/execution.go @@ -0,0 +1,184 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saexec + +import ( + "context" + "errors" + "fmt" + "math" + "runtime" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core" + "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" + "go.uber.org/zap" + + "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/dummy" + "github.com/ava-labs/strevm/hook" +) + +var errExecutorClosed = errors.New("saexec.Executor closed") + +// Enqueue pushes a new block to the FIFO queue. If [Executor.Close] is called +// before [blocks.Block.Executed] returns true then there is no guarantee that +// the block will be executed. +func (e *Executor) Enqueue(ctx context.Context, block *blocks.Block) error { + for { + select { + case e.queue <- block: + return nil + case <-e.quit: + return errExecutorClosed + case <-ctx.Done(): + return ctx.Err() + default: + // If this happens then increase the channel's buffer size. + e.log.Warn("Execution queue buffer too small") + runtime.Gosched() + } + } +} + +func (e *Executor) processQueue() { + defer close(e.done) + + for { + select { + case <-e.quit: + return + + case block := <-e.queue: + logger := e.log.With( + zap.Uint64("block_height", block.Height()), + zap.Uint64("block_time", block.BuildTime()), + zap.Any("block_hash", block.Hash()), + zap.Int("tx_count", len(block.Transactions())), + ) + + if err := e.execute(block, logger); err != nil { + logger.Fatal("Block execution failed", zap.Error(err)) + return + } + } + } +} + +type executionScratchSpace struct { + snaps *snapshot.Tree + statedb *state.StateDB +} + +func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { + logger.Debug("Executing block") + + // If the VM were to encounter an error after enqueuing the block, we would + // receive the same block twice for execution should consensus retry + // acceptance. + if last, curr := e.lastExecuted.Load().Height(), b.Height(); curr != last+1 { + return fmt.Errorf("executing blocks out of order: %d then %d", last, curr) + } + + target := e.hooks.GasTarget(b.ParentBlock().EthBlock()) + if err := hook.BeforeBlock(e.gasClock, b.Header(), target); err != nil { + return fmt.Errorf("before-block hook: %v", err) + } + perTxClock := e.gasClock.Time.Clone() + + header := types.CopyHeader(b.Header()) + header.BaseFee = e.gasClock.BaseFee().ToBig() + + gasPool := core.GasPool(math.MaxUint64) // required by geth but irrelevant so max it out + var blockGasConsumed gas.Gas + + scratch := &e.executeScratchSpace + receipts := make(types.Receipts, len(b.Transactions())) + for ti, tx := range b.Transactions() { + scratch.statedb.SetTxContext(tx.Hash(), ti) + + receipt, err := core.ApplyTransaction( + e.chainConfig, + dummy.ChainContext(), + &header.Coinbase, + &gasPool, + scratch.statedb, + header, + tx, + (*uint64)(&blockGasConsumed), + vm.Config{}, + ) + if err != nil { + return fmt.Errorf("tx[%d]: %w", ti, err) + } + + perTxClock.Tick(gas.Gas(receipt.GasUsed)) + b.SetInterimExecutionTime(perTxClock) + // TODO(arr4n) investigate calling the same method on pending blocks in + // the queue. It's only worth it if [blocks.LastToSettleAt] regularly + // returns false, meaning that execution is blocking consensus. + + // The [types.Header] that we pass to [core.ApplyTransaction] is + // modified to reduce gas price from the worst-case value agreed by + // consensus. This changes the hash, which is what is copied to receipts + // and logs. + receipt.BlockHash = b.Hash() + for _, l := range receipt.Logs { + l.BlockHash = b.Hash() + } + + // TODO(arr4n) add a receipt cache to the [executor] to allow API calls + // to access them before the end of the block. + receipts[ti] = receipt + } + endTime := time.Now() + hook.AfterBlock(e.gasClock, blockGasConsumed) + if e.gasClock.Time.Compare(perTxClock) != 0 { + return fmt.Errorf("broken invariant: block-resolution clock @ %s does not match tx-resolution clock @ %s", e.gasClock.String(), perTxClock.String()) + } + + logger.Debug( + "Block execution complete", + zap.Uint64("gas_consumed", uint64(blockGasConsumed)), + zap.Time("gas_time", e.gasClock.AsTime()), + zap.Time("wall_time", endTime), + ) + + root, err := e.commitState(scratch, b.NumberU64()) + if err != nil { + return err + } + // The strict ordering of the next 3 calls guarantees invariants that MUST + // NOT be broken: + // + // 1. [blocks.Block.MarkExecuted] guarantees disk then in-memory changes. + // 2. Internal indicator of last executed MUST follow in-memory change. + // 3. External indicator of last executed MUST follow internal indicator. + if err := b.MarkExecuted(e.db, e.gasClock.Clone(), endTime, header.BaseFee, receipts, root); err != nil { + return err + } + e.lastExecuted.Store(b) // (2) + e.sendPostExecutionEvents(b.EthBlock(), receipts) // (3) + return nil +} + +func (e *Executor) commitState(scratch *executionScratchSpace, blockNum uint64) (common.Hash, error) { + root, err := scratch.statedb.Commit(blockNum, true) + if err != nil { + return common.Hash{}, fmt.Errorf("%T.Commit() at end of block %d: %w", scratch.statedb, blockNum, err) + } + + db, err := state.New(root, e.stateCache, scratch.snaps) + if err != nil { + return common.Hash{}, err + } + scratch.statedb = db + return root, nil +} diff --git a/saexec/saexec.go b/saexec/saexec.go new file mode 100644 index 0000000..a417641 --- /dev/null +++ b/saexec/saexec.go @@ -0,0 +1,139 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package saexec provides the execution module of [Streaming Asynchronous +// Execution] (SAE). +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package saexec + +import ( + "sync/atomic" + + "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/state/snapshot" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/event" + "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/triedb" + + "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/hook" +) + +// An Executor accepts and executes a [blocks.Block] FIFO queue. +type Executor struct { + quit, done chan struct{} + log logging.Logger + hooks hook.Points + + gasClock *gastime.Time + queue chan *blocks.Block + lastExecuted atomic.Pointer[blocks.Block] + + headEvents event.FeedOf[core.ChainHeadEvent] + chainEvents event.FeedOf[core.ChainEvent] + logEvents event.FeedOf[[]*types.Log] + + chainConfig *params.ChainConfig + db ethdb.Database + stateCache state.Database + // executeScratchSpace MUST NOT be accessed by any methods other than + // [Executor.init], [Executor.execute], and [Executor.Close]. + executeScratchSpace executionScratchSpace +} + +// New constructs and starts a new [Executor]. Call [Executor.Close] to release +// resources created by this constructor. +// +// The last-executed block MAY be the genesis block for an always-SAE chain, the +// last pre-SAE synchronous block during transition, or the last asynchronously +// executed block after shutdown and recovery. +func New( + lastExecuted *blocks.Block, + chainConfig *params.ChainConfig, + db ethdb.Database, + triedbConfig *triedb.Config, + hooks hook.Points, + log logging.Logger, +) (*Executor, error) { + e := &Executor{ + quit: make(chan struct{}), // closed by [Executor.Close] + done: make(chan struct{}), // closed by [Executor.processQueue] after `quit` is closed + log: log, + hooks: hooks, + queue: make(chan *blocks.Block, 4096), // arbitrarily sized + chainConfig: chainConfig, + db: db, + stateCache: state.NewDatabaseWithConfig(db, triedbConfig), + } + e.lastExecuted.Store(lastExecuted) + if err := e.init(); err != nil { + return nil, err + } + + go e.processQueue() + return e, nil +} + +func (e *Executor) init() error { + last := e.lastExecuted.Load() + e.gasClock = last.ExecutedByGasTime().Clone() + + root := last.PostExecutionStateRoot() + snapConf := snapshot.Config{ + CacheSize: 128, // MB + AsyncBuild: true, + } + snaps, err := snapshot.New(snapConf, e.db, e.stateCache.TrieDB(), root) + if err != nil { + return err + } + statedb, err := state.New(root, e.stateCache, snaps) + if err != nil { + return err + } + + e.executeScratchSpace = executionScratchSpace{ + snaps: snaps, + statedb: statedb, + } + return nil +} + +// Close shuts down the [Executor], waits for the currently executing block +// to complete, and then releases all resources. +func (e *Executor) Close() { + close(e.quit) + <-e.done + + snaps := e.executeScratchSpace.snaps + snaps.Disable() + snaps.Release() +} + +// ChainConfig returns the config originally passed to [New]. +func (e *Executor) ChainConfig() *params.ChainConfig { + return e.chainConfig +} + +// StateCache returns caching database underpinning execution. +func (e *Executor) StateCache() state.Database { + return e.stateCache +} + +// LastExecuted returns the last-executed block in a threadsafe manner. +func (e *Executor) LastExecuted() *blocks.Block { + return e.lastExecuted.Load() +} + +// TimeNotThreadsafe returns a clone of the gas clock that times execution. It +// is only safe to call when all blocks passed to [Executor.Enqueue] +// have been executed, and is only intended for use in tests. +func (e *Executor) TimeNotThreadsafe() *gastime.Time { + return e.gasClock.Clone() +} diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go new file mode 100644 index 0000000..a24ae2e --- /dev/null +++ b/saexec/saexec_test.go @@ -0,0 +1,528 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saexec + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math" + "math/big" + "math/rand/v2" + "testing" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/common" + "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/crypto" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/libevm" + "github.com/ava-labs/libevm/libevm/hookstest" + "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/triedb" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/blocks/blockstest" + "github.com/ava-labs/strevm/cmputils" + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/hook" + "github.com/ava-labs/strevm/proxytime" + "github.com/ava-labs/strevm/saetest" + "github.com/ava-labs/strevm/saetest/weth" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain( + m, + goleak.IgnoreCurrent(), + // Despite the call to [snapshot.Tree.Disable] in [Executor.Close], this + // still leaks at shutdown. This is acceptable as we only ever have one + // [Executor], which we expect to be running for the entire life of the + // process. + goleak.IgnoreTopFunction("github.com/ava-labs/libevm/core/state/snapshot.(*diskLayer).generate"), + ) +} + +func newExec(tb testing.TB, db ethdb.Database, hooks hook.Points, alloc types.GenesisAlloc) *Executor { + tb.Helper() + + config := params.AllDevChainProtocolChanges + ethGenesis := saetest.Genesis(tb, db, config, alloc) + genesis := blockstest.NewBlock(tb, ethGenesis, nil, nil) + require.NoErrorf(tb, genesis.MarkExecuted(db, gastime.New(0, 1, 0), time.Time{}, new(big.Int), nil, ethGenesis.Root()), "%T.MarkExecuted()", genesis) + require.NoErrorf(tb, genesis.MarkSynchronous(), "%T.MarkSynchronous()", genesis) + + e, err := New(genesis, config, db, (*triedb.Config)(nil), hooks, saetest.NewTBLogger(tb, logging.Warn)) + require.NoError(tb, err, "New()") + tb.Cleanup(e.Close) + return e +} + +func newExecWithMaxAlloc(tb testing.TB, db ethdb.Database, hooks hook.Points) (*Executor, *ecdsa.PrivateKey) { + tb.Helper() + key, alloc := saetest.KeyWithMaxAlloc(tb) + return newExec(tb, db, hooks, alloc), key +} + +func defaultHooks() *saetest.HookStub { + return &saetest.HookStub{Target: 1e6} +} + +func TestImmediateShutdownNonBlocking(t *testing.T) { + newExec(t, rawdb.NewMemoryDatabase(), defaultHooks(), nil) // calls [Executor.Close] in test cleanup +} + +func TestExecutionSynchronisation(t *testing.T) { + ctx := context.Background() + e := newExec(t, rawdb.NewMemoryDatabase(), defaultHooks(), nil) + + var chain []*blocks.Block + parent := e.LastExecuted() // genesis + for tm := range uint64(10) { + ethB := blockstest.NewEthBlock(parent.EthBlock(), tm, nil) + b := blockstest.NewBlock(t, ethB, parent, nil) + chain = append(chain, b) + parent = b + } + + for _, b := range chain { + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + } + + final := chain[len(chain)-1] + require.NoErrorf(t, final.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted() on last-enqueued block", final) + assert.Equal(t, final.NumberU64(), e.LastExecuted().NumberU64(), "Last-executed atomic pointer holds last-enqueued block") + + for _, b := range chain { + assert.Truef(t, b.Executed(), "%T[%d].Executed()", b, b.NumberU64()) + } +} + +func TestReceiptPropagation(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + e, key := newExecWithMaxAlloc(t, rawdb.NewMemoryDatabase(), defaultHooks()) + signer := types.LatestSigner(e.ChainConfig()) + + var ( + chain []*blocks.Block + want []types.Receipts + nonce uint64 + ) + parent := e.LastExecuted() + for range 10 { + var ( + txs types.Transactions + receipts types.Receipts + ) + + for range 5 { + tx := types.MustSignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + Gas: 1e5, + GasPrice: big.NewInt(1), + }) + nonce++ + txs = append(txs, tx) + receipts = append(receipts, &types.Receipt{TxHash: tx.Hash()}) + } + want = append(want, receipts) + + ethB := blockstest.NewEthBlock(parent.EthBlock(), 0 /*time*/, txs) + b := blockstest.NewBlock(t, ethB, parent, nil) + chain = append(chain, b) + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + parent = b + } + require.NoErrorf(t, parent.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted() on last-enqueued block", parent) + + var got []types.Receipts + for _, b := range chain { + got = append(got, b.Receipts()) + } + if diff := cmp.Diff(want, got, cmputils.ReceiptsByTxHash()); diff != "" { + t.Errorf("%T diff (-want +got):\n%s", got, diff) + } +} + +func TestSubscriptions(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + e, key := newExecWithMaxAlloc(t, rawdb.NewMemoryDatabase(), defaultHooks()) + signer := types.LatestSigner(e.ChainConfig()) + + precompile := common.Address{'p', 'r', 'e'} + stub := &hookstest.Stub{ + PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{ + precompile: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) { + env.StateDB().AddLog(&types.Log{ + Address: precompile, + }) + return nil, nil + }), + }, + } + stub.Register(t) + + gotChainHeadEvents := saetest.NewEventCollector(e.SubscribeChainHeadEvent) + gotChainEvents := saetest.NewEventCollector(e.SubscribeChainEvent) + gotLogsEvents := saetest.NewEventCollector(e.SubscribeLogsEvent) + var ( + wantChainHeadEvents []core.ChainHeadEvent + wantChainEvents []core.ChainEvent + wantLogsEvents [][]*types.Log + ) + + parent := e.LastExecuted() + for nonce := uint64(0); nonce < 10; nonce++ { + tx := types.MustSignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + To: &precompile, + GasPrice: big.NewInt(1), + Gas: 1e6, + }) + + ethB := blockstest.NewEthBlock(parent.EthBlock(), 0 /*time*/, types.Transactions{tx}) + b := blockstest.NewBlock(t, ethB, parent, nil) + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + parent = b + + wantChainHeadEvents = append(wantChainHeadEvents, core.ChainHeadEvent{ + Block: ethB, + }) + logs := []*types.Log{{ + Address: precompile, + BlockNumber: b.NumberU64(), + TxHash: tx.Hash(), + BlockHash: b.Hash(), + }} + wantChainEvents = append(wantChainEvents, core.ChainEvent{ + Block: ethB, + Hash: b.Hash(), + Logs: logs, + }) + wantLogsEvents = append(wantLogsEvents, logs) + } + + opt := cmputils.BlocksByHash() + t.Run("ChainHeadEvents", func(t *testing.T) { + testEvents(t, gotChainHeadEvents, wantChainHeadEvents, opt) + }) + t.Run("ChainEvents", func(t *testing.T) { + testEvents(t, gotChainEvents, wantChainEvents, opt) + }) + t.Run("LogsEvents", func(t *testing.T) { + testEvents(t, gotLogsEvents, wantLogsEvents) + }) +} + +func testEvents[T any](tb testing.TB, got *saetest.EventCollector[T], want []T, opts ...cmp.Option) { + tb.Helper() + // There is an invariant that stipulates [blocks.Block.MarkExecuted] MUST + // occur before sending external events, which means that we can't rely on + // [blocks.Block.WaitUntilExecuted] to avoid races. + got.WaitForAtLeast(len(want)) + + require.NoError(tb, got.Unsubscribe()) + if diff := cmp.Diff(want, got.All(), opts...); diff != "" { + tb.Errorf("Collecting %T from event.Subscription; diff (-want +got):\n%s", want, diff) + } +} + +func TestExecution(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + e, key := newExecWithMaxAlloc(t, rawdb.NewMemoryDatabase(), defaultHooks()) + eoa := crypto.PubkeyToAddress(key.PublicKey) + signer := types.LatestSigner(e.ChainConfig()) + + var ( + txs types.Transactions + want types.Receipts + ) + deploy := types.MustSignNewTx(key, signer, &types.LegacyTx{ + Nonce: 0, + Data: weth.CreationCode(), + GasPrice: big.NewInt(1), + Gas: 1e7, + }) + contract := crypto.CreateAddress(eoa, 0) + txs = append(txs, deploy) + want = append(want, &types.Receipt{ + TxHash: deploy.Hash(), + ContractAddress: contract, + }) + + var eoaAsHash common.Hash + copy(eoaAsHash[12:], eoa[:]) + + rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful for tests + var wantWethBalance uint64 + for nonce := uint64(1); nonce < 10; nonce++ { + val := rng.Uint64N(100_000) + tx := types.MustSignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + To: &contract, + Value: new(big.Int).SetUint64(val), + GasPrice: big.NewInt(1), + Gas: 1e6, + }) + wantWethBalance += val + t.Logf("Depositing %d", val) + + txs = append(txs, tx) + want = append(want, &types.Receipt{ + TxHash: tx.Hash(), + Logs: []*types.Log{{ + Address: contract, + TxHash: tx.Hash(), + Topics: []common.Hash{ + crypto.Keccak256Hash([]byte("Deposit(address,uint256)")), + eoaAsHash, + }, + Data: tx.Value().FillBytes(make([]byte, 32)), + }}, + }) + } + + genesis := e.LastExecuted() + ethB := blockstest.NewEthBlock(genesis.EthBlock(), 0, txs) + b := blockstest.NewBlock(t, ethB, genesis, nil) + + var logIndex uint + for i, r := range want { + ui := uint(i) //nolint:gosec // Known to not overflow + + r.Status = 1 + r.TransactionIndex = ui + r.BlockHash = b.Hash() + r.BlockNumber = big.NewInt(1) + + for _, l := range r.Logs { + l.TxIndex = ui + l.BlockHash = b.Hash() + l.BlockNumber = 1 + l.Index = logIndex + logIndex++ + } + } + + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) + + opts := cmp.Options{ + cmpopts.IgnoreFields( + types.Receipt{}, + "GasUsed", "CumulativeGasUsed", + "Bloom", + ), + cmputils.BigInts(), + } + if diff := cmp.Diff(want, b.Receipts(), opts); diff != "" { + t.Errorf("%T.Receipts() diff (-want +got):\n%s", b, diff) + } + + t.Run("committed_state", func(t *testing.T) { + sdb, err := state.New(b.PostExecutionStateRoot(), e.StateCache(), nil) + require.NoErrorf(t, err, "state.New(%T.PostExecutionStateRoot(), %T.StateCache(), nil)", b, e) + + if got, want := sdb.GetBalance(contract).ToBig(), new(big.Int).SetUint64(wantWethBalance); got.Cmp(want) != 0 { + t.Errorf("After WETH deposits, got contract balance %v; want %v", got, want) + } + + callData := append( + crypto.Keccak256([]byte("balanceOf(address)"))[:4], + eoaAsHash[:]..., + ) + evm := vm.NewEVM(vm.BlockContext{Transfer: core.Transfer}, vm.TxContext{}, sdb, e.ChainConfig(), vm.Config{}) + got, _, err := evm.Call(vm.AccountRef(eoa), contract, callData, 1e6, uint256.NewInt(0)) + require.NoErrorf(t, err, "%T.Call([weth contract], [balanceOf(eoa)])", evm) + if got, want := new(uint256.Int).SetBytes(got), uint256.NewInt(wantWethBalance); !got.Eq(want) { + t.Errorf("WETH9.balanceOf([eoa]) got %v; want %v", got, want) + } + }) +} + +func TestGasAccounting(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + hooks := &saetest.HookStub{} + e, key := newExecWithMaxAlloc(t, rawdb.NewMemoryDatabase(), hooks) + signer := types.LatestSigner(e.ChainConfig()) + + const gasPerTx = gas.Gas(params.TxGas) + at := func(blockTime, txs uint64, rate gas.Gas) *proxytime.Time[gas.Gas] { + tm := proxytime.New[gas.Gas](blockTime, rate) + tm.Tick(gas.Gas(txs) * gasPerTx) + return tm + } + + // If this fails then all of the tests need to be adjusted. This is cleaner + // than polluting the test cases with a repetitive identifier. + require.Equal(t, 2, gastime.TargetToRate, "gastime.TargetToRate assumption") + + // Steps are _not_ independent, so the execution time of one is the starting + // time of the next. + steps := []struct { + target gas.Gas + blockTime uint64 + numTxs int + wantExecutedBy *proxytime.Time[gas.Gas] + // Because of the 2:1 ratio between Rate and Target, gas consumption + // increases excess by half of the amount consumed, while + // fast-forwarding reduces excess by half of the amount skipped. + wantExcessAfter gas.Gas + wantPriceAfter gas.Price + }{ + { + target: 5 * gasPerTx, + blockTime: 2, + numTxs: 3, + wantExecutedBy: at(2, 3, 10*gasPerTx), + wantExcessAfter: 3 * gasPerTx / 2, + wantPriceAfter: 1, // Excess isn't high enough so price is effectively e^0 + }, + { + target: 5 * gasPerTx, + blockTime: 3, // fast-forward + numTxs: 12, + wantExecutedBy: at(4, 2, 10*gasPerTx), + wantExcessAfter: 12 * gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 5 * gasPerTx, + blockTime: 4, // no fast-forward so starts at last execution time + numTxs: 20, + wantExecutedBy: at(6, 2, 10*gasPerTx), + wantExcessAfter: (12 + 20) * gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 5 * gasPerTx, + blockTime: 7, // fast-forward equivalent of 8 txs + numTxs: 16, + wantExecutedBy: at(8, 6, 10*gasPerTx), + wantExcessAfter: (12 + 20 - 8 + 16) * gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 10 * gasPerTx, // double gas/block --> halve ticking rate + blockTime: 8, // no fast-forward + numTxs: 4, + wantExecutedBy: at(8, (6*2)+4, 20*gasPerTx), // starting point scales + wantExcessAfter: (2*(12+20-8+16) + 4) * gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 5 * gasPerTx, // back to original + blockTime: 8, + numTxs: 5, + wantExecutedBy: at(8, 6+(4/2)+5, 10*gasPerTx), + wantExcessAfter: ((12 + 20 - 8 + 16) + 4/2 + 5) * gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 5 * gasPerTx, + blockTime: 20, // more than double the last executed-by time, reduces excess to 0 + numTxs: 1, + wantExecutedBy: at(20, 1, 10*gasPerTx), + wantExcessAfter: gasPerTx / 2, + wantPriceAfter: 1, + }, + { + target: 5 * gasPerTx, + blockTime: 21, // fast-forward so excess is 0 + numTxs: 30 * gastime.TargetToExcessScaling, // deliberate, see below + wantExecutedBy: at(21, 30*gastime.TargetToExcessScaling, 10*gasPerTx), + wantExcessAfter: 3 * ((5 * gasPerTx /*T*/) * gastime.TargetToExcessScaling /* == K */), + // Excess is now 3·K so the price is e^3 = 20.09 + wantPriceAfter: 20, + }, + { + target: 5 * gasPerTx, + blockTime: 22, // no fast-forward + numTxs: 10 * gastime.TargetToExcessScaling, + wantExecutedBy: at(21, 40*gastime.TargetToExcessScaling, 10*gasPerTx), + wantExcessAfter: 4 * ((5 * gasPerTx /*T*/) * gastime.TargetToExcessScaling /* == K */), + wantPriceAfter: gas.Price(math.Floor(math.Pow(math.E, 4 /* <----- NB */))), + }, + } + + parent := e.LastExecuted() + var nonce uint64 + for i, step := range steps { + hooks.Target = step.target + + txs := make(types.Transactions, step.numTxs) + for i := range txs { + txs[i] = types.MustSignNewTx(key, signer, &types.DynamicFeeTx{ + Nonce: nonce, + To: &common.Address{}, + Gas: params.TxGas, + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(100), + }) + nonce++ + } + + ethB := blockstest.NewEthBlock(parent.EthBlock(), step.blockTime, txs) + b := blockstest.NewBlock(t, ethB, parent, nil) + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) + parent = b + + for desc, got := range map[string]*gastime.Time{ + fmt.Sprintf("%T.ExecutedByGasTime()", b): b.ExecutedByGasTime(), + fmt.Sprintf("%T.TimeNotThreadSafe()", e): e.TimeNotThreadsafe(), + } { + opt := proxytime.CmpOpt[gas.Gas](proxytime.IgnoreRateInvariants) + if diff := cmp.Diff(step.wantExecutedBy, got.Time, opt); diff != "" { + t.Errorf("%s diff (-want +got):\n%s", desc, diff) + } + } + + t.Run("CumulativeGasUsed", func(t *testing.T) { + for i, r := range b.Receipts() { + ui := uint64(i + 1) //nolint:gosec // Known to not overflow + assert.Equalf(t, ui*params.TxGas, r.CumulativeGasUsed, "%T.Receipts()[%d]", b, i) + } + }) + + if t.Failed() { + // Future steps / tests may be corrupted and false-positive errors + // aren't helpful. + break + } + + t.Run("gas_price", func(t *testing.T) { + tm := e.TimeNotThreadsafe() + assert.Equalf(t, step.wantExcessAfter, tm.Excess(), "%T.Excess()", tm) + assert.Equalf(t, step.wantPriceAfter, tm.Price(), "%T.Price()", tm) + + wantBaseFee := gas.Price(1) + if i > 0 { + wantBaseFee = steps[i-1].wantPriceAfter + } + require.Truef(t, b.BaseFee().IsUint64(), "%T.BaseFee().IsUint64()", b) + assert.Equalf(t, wantBaseFee, gas.Price(b.BaseFee().Uint64()), "%T.BaseFee().Uint64()", b) + }) + } +} diff --git a/saexec/subscription.go b/saexec/subscription.go new file mode 100644 index 0000000..24e6052 --- /dev/null +++ b/saexec/subscription.go @@ -0,0 +1,43 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saexec + +import ( + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/event" +) + +func (e *Executor) sendPostExecutionEvents(b *types.Block, receipts types.Receipts) { + e.headEvents.Send(core.ChainHeadEvent{Block: b}) + + var logs []*types.Log + for _, r := range receipts { + logs = append(logs, r.Logs...) + } + e.chainEvents.Send(core.ChainEvent{ + Block: b, + Hash: b.Hash(), + Logs: logs, + }) + e.logEvents.Send(logs) +} + +// SubscribeChainHeadEvent returns a new subscription for each +// [core.ChainHeadEvent] emitted after execution of a [blocks.Block]. +func (e *Executor) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return e.headEvents.Subscribe(ch) +} + +// SubscribeChainEvent returns a new subscription for each [core.ChainEvent] +// emitted after execution of a [blocks.Block]. +func (e *Executor) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return e.chainEvents.Subscribe(ch) +} + +// SubscribeLogsEvent returns a new subscription for logs emitted after +// execution of a [blocks.Block]. +func (e *Executor) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return e.logEvents.Subscribe(ch) +} From 0b59c0e6e10a31a70de12ecb519082b2f12d0c62 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 11 Nov 2025 15:34:47 +0000 Subject: [PATCH 02/52] feat: `txgossip` package --- blocks/blockstest/blockstest.go | 62 +++++++ go.mod | 110 ++++++------ go.sum | 228 ++++++++++++------------ saetest/wallet.go | 79 +++++++++ saexec/execution.go | 2 + saexec/saexec.go | 20 ++- saexec/saexec_test.go | 5 +- saexec/subscription.go | 6 + txgossip/blockchain.go | 111 ++++++++++++ txgossip/priority.go | 54 ++++++ txgossip/txgossip.go | 134 ++++++++++++++ txgossip/txgossip_test.go | 303 ++++++++++++++++++++++++++++++++ 12 files changed, 935 insertions(+), 179 deletions(-) create mode 100644 saetest/wallet.go create mode 100644 txgossip/blockchain.go create mode 100644 txgossip/priority.go create mode 100644 txgossip/txgossip.go create mode 100644 txgossip/txgossip_test.go diff --git a/blocks/blockstest/blockstest.go b/blocks/blockstest/blockstest.go index 8ff2e8c..f1262bc 100644 --- a/blocks/blockstest/blockstest.go +++ b/blocks/blockstest/blockstest.go @@ -10,12 +10,17 @@ package blockstest import ( "math/big" "testing" + "time" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/params" "github.com/stretchr/testify/require" "github.com/ava-labs/strevm/blocks" + "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/saetest" ) @@ -36,3 +41,60 @@ func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block require.NoError(tb, err, "New()") return b } + +// NewGenesis is a convenience wrapper around [saetest.Genesis] and [NewBlock], +// also marking the returned block as both executed and synchronous. +func NewGenesis(tb testing.TB, db ethdb.Database, config *params.ChainConfig, alloc types.GenesisAlloc) *blocks.Block { + tb.Helper() + b := NewBlock(tb, saetest.Genesis(tb, db, config, alloc), 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 +} + +// 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 d0d045b..ae49a42 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/ava-labs/strevm -go 1.24.7 +go 1.24.9 + +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/avalanchego v1.14.0 + github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f github.com/google/go-cmp v0.7.0 github.com/holiman/uint256 v1.2.4 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) @@ -15,94 +17,94 @@ 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/bits-and-blooms/bitset v1.20.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/consensys/gnark-crypto v0.18.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.1.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/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.9.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/getsentry/sentry-go v0.35.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/google/uuid v1.6.0 // 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/klauspost/compress v1.18.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect - 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/mod v0.25.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/tools v0.34.0 // indirect - rsc.io/tmplfunc v0.0.3 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.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 ) require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect - github.com/StephenButtolph/canoto v0.17.1 + github.com/StephenButtolph/canoto v0.17.3 github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/rpc v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mr-tron/base58 v1.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/supranational/blst v0.3.14 // indirect - go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.22.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.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 - 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 - google.golang.org/grpc v1.66.0 // indirect - google.golang.org/protobuf v1.35.2 // indirect + go.uber.org/zap v1.27.0 + 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.16.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 29b2706..edc2904 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/StephenButtolph/canoto v0.17.1 h1:WnN5czIHHALq7pwc+Z2F1sCsKJCDhxlq0zL0YK1etHc= -github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= +github.com/StephenButtolph/canoto v0.17.3 h1:lvsnYD4b96vD1knnmp1xCmZqfYpY/jSeRozGdOfdvGI= +github.com/StephenButtolph/canoto v0.17.3/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= 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= @@ -21,19 +21,25 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax 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/avalanchego v1.14.0 h1:0j314N1fEwstKSymvyhvvxi8Hr752xc6MQvjq6kGIJY= +github.com/ava-labs/avalanchego v1.14.0/go.mod h1:7sYTcQknONY5x5qzS+GrN+UtyB8kX7Q5ClHhGj1DgXg= +github.com/ava-labs/libevm v1.13.15-0.20251016142715-1bccf4f2ddb2 h1:hQ15IJxY7WOKqeJqCXawsiXh0NZTzmoQOemkWHz7rr4= +github.com/ava-labs/libevm v1.13.15-0.20251016142715-1bccf4f2ddb2/go.mod h1:DqSotSn4Dx/UJV+d3svfW8raR+cH7+Ohl9BpsQ5HlGU= +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= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= +github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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= @@ -58,29 +64,28 @@ github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= -github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= -github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= +github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -90,8 +95,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,16 +104,16 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY= +github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -116,8 +121,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -138,13 +143,10 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= -github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -173,7 +175,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= 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= @@ -183,8 +184,8 @@ github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36j github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -220,8 +221,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -235,8 +236,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -250,15 +251,10 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -266,6 +262,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -294,24 +292,25 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -327,12 +326,12 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/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= @@ -343,18 +342,18 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8= github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -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/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 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= @@ -378,32 +377,36 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -412,8 +415,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= @@ -425,8 +428,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= @@ -448,19 +451,18 @@ 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= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -494,22 +496,20 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc 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= @@ -525,15 +525,15 @@ 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -541,18 +541,18 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -564,8 +564,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -593,5 +593,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/saetest/wallet.go b/saetest/wallet.go new file mode 100644 index 0000000..2eb2733 --- /dev/null +++ b/saetest/wallet.go @@ -0,0 +1,79 @@ +// 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/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 +} diff --git a/saexec/execution.go b/saexec/execution.go index 5cc8ed9..743a5fd 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -35,6 +35,8 @@ func (e *Executor) Enqueue(ctx context.Context, block *blocks.Block) error { for { select { case e.queue <- block: + e.lastEnqueued.Store(block) + e.enqueueEvents.Send(block.EthBlock()) return nil case <-e.quit: return errExecutorClosed diff --git a/saexec/saexec.go b/saexec/saexec.go index a417641..a84d2d7 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -31,13 +31,15 @@ type Executor struct { log logging.Logger hooks hook.Points - gasClock *gastime.Time - queue chan *blocks.Block - lastExecuted atomic.Pointer[blocks.Block] + gasClock *gastime.Time + queue chan *blocks.Block - headEvents event.FeedOf[core.ChainHeadEvent] - chainEvents event.FeedOf[core.ChainEvent] - logEvents event.FeedOf[[]*types.Log] + lastEnqueued, lastExecuted atomic.Pointer[blocks.Block] + + enqueueEvents event.FeedOf[*types.Block] + headEvents event.FeedOf[core.ChainHeadEvent] + chainEvents event.FeedOf[core.ChainEvent] + logEvents event.FeedOf[[]*types.Log] chainConfig *params.ChainConfig db ethdb.Database @@ -71,6 +73,7 @@ func New( db: db, stateCache: state.NewDatabaseWithConfig(db, triedbConfig), } + e.lastEnqueued.Store(lastExecuted) e.lastExecuted.Store(lastExecuted) if err := e.init(); err != nil { return nil, err @@ -131,6 +134,11 @@ func (e *Executor) LastExecuted() *blocks.Block { return e.lastExecuted.Load() } +// LastEnqueued returns the last-enqueued block in a threadsafe manner. +func (e *Executor) LastEnqueued() *blocks.Block { + return e.lastEnqueued.Load() +} + // TimeNotThreadsafe returns a clone of the gas clock that times execution. It // is only safe to call when all blocks passed to [Executor.Enqueue] // have been executed, and is only intended for use in tests. diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index a24ae2e..f8701c1 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -60,10 +60,7 @@ func newExec(tb testing.TB, db ethdb.Database, hooks hook.Points, alloc types.Ge tb.Helper() config := params.AllDevChainProtocolChanges - ethGenesis := saetest.Genesis(tb, db, config, alloc) - genesis := blockstest.NewBlock(tb, ethGenesis, nil, nil) - require.NoErrorf(tb, genesis.MarkExecuted(db, gastime.New(0, 1, 0), time.Time{}, new(big.Int), nil, ethGenesis.Root()), "%T.MarkExecuted()", genesis) - require.NoErrorf(tb, genesis.MarkSynchronous(), "%T.MarkSynchronous()", genesis) + genesis := blockstest.NewGenesis(tb, db, config, alloc) e, err := New(genesis, config, db, (*triedb.Config)(nil), hooks, saetest.NewTBLogger(tb, logging.Warn)) require.NoError(tb, err, "New()") diff --git a/saexec/subscription.go b/saexec/subscription.go index 24e6052..bf2fd70 100644 --- a/saexec/subscription.go +++ b/saexec/subscription.go @@ -24,6 +24,12 @@ func (e *Executor) sendPostExecutionEvents(b *types.Block, receipts types.Receip e.logEvents.Send(logs) } +// SubscribeBlockEnqueueEvent returns a new subscription for each block queued +// [Executor.Enqueue]. +func (e *Executor) SubscribeBlockEnqueueEvent(ch chan<- *types.Block) event.Subscription { + return e.enqueueEvents.Subscribe(ch) +} + // SubscribeChainHeadEvent returns a new subscription for each // [core.ChainHeadEvent] emitted after execution of a [blocks.Block]. func (e *Executor) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { diff --git a/txgossip/blockchain.go b/txgossip/blockchain.go new file mode 100644 index 0000000..45507a6 --- /dev/null +++ b/txgossip/blockchain.go @@ -0,0 +1,111 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txgossip + +import ( + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/state" + "github.com/ava-labs/libevm/core/txpool" + "github.com/ava-labs/libevm/core/txpool/legacypool" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/event" + "github.com/ava-labs/libevm/params" + + "github.com/ava-labs/strevm/saexec" +) + +// A BlockSource returns a block that matches both a hash and number, or nil +// if not found. +type BlockSource func(common.Hash, uint64) *types.Block + +// A BlockChain is the union of [txpool.BlockChain] and [legacypool.BlockChain]. +type BlockChain interface { + txpool.BlockChain + legacypool.BlockChain +} + +// NewBlockChain wraps an [saexec.Executor] to be compatible with a +// non-blob-transaction mempool. +func NewBlockChain(exec *saexec.Executor, blocks BlockSource) BlockChain { + return &blockchain{ + exec: exec, + blocks: blocks, + } +} + +type blockchain struct { + exec *saexec.Executor + blocks BlockSource +} + +func (bc *blockchain) Config() *params.ChainConfig { + return bc.exec.ChainConfig() +} + +func (bc *blockchain) CurrentBlock() *types.Header { + return bc.exec.LastEnqueued().Header() +} + +func (bc *blockchain) GetBlock(hash common.Hash, number uint64) *types.Block { + return bc.blocks(hash, number) +} + +func (bc *blockchain) StateAt(root common.Hash) (*state.StateDB, error) { + return state.New(root, bc.exec.StateCache(), nil) +} + +// SubscribeChainHeadEvent subscribes to block enqueueing, NOT to regular head +// events as these only occur after execution. Enqueuing is equivalent to block +// acceptance, which is when a transaction SHOULD be removed from the mempool. +func (bc *blockchain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + bCh := make(chan *types.Block) + sub := bc.exec.SubscribeBlockEnqueueEvent(bCh) + + p := &pipe{ + in: bCh, + out: ch, + sub: sub, + quit: make(chan struct{}), + done: make(chan struct{}), + } + go p.loop() + return p +} + +// A pipe is an [event.Subscription] that converts from a [types.Block] channel +// to a [core.ChainHeadEvent] one. +type pipe struct { + in <-chan *types.Block + out chan<- core.ChainHeadEvent + sub event.Subscription + quit, done chan struct{} +} + +func (p *pipe) loop() { + defer close(p.done) + for { + select { + case b := <-p.in: + select { + case p.out <- core.ChainHeadEvent{Block: b}: + + case <-p.quit: + return + } + case <-p.quit: + return + } + } +} + +func (p *pipe) Err() <-chan error { + return p.sub.Err() +} + +func (p *pipe) Unsubscribe() { + p.sub.Unsubscribe() + close(p.quit) + <-p.done +} diff --git a/txgossip/priority.go b/txgossip/priority.go new file mode 100644 index 0000000..3a246ed --- /dev/null +++ b/txgossip/priority.go @@ -0,0 +1,54 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txgossip + +import ( + "slices" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/txpool" +) + +// A LazyTransaction couples a [txpool.LazyTransaction] with its sender. +type LazyTransaction struct { + *txpool.LazyTransaction + Sender common.Address +} + +// TransactionsByPriority calls [txpool.TxPool.Pending] with the given filter, +// collapses the results into a slice, and sorts said slice by decreasing gas +// tip then chronologically. Transactions from the same sender are merely sorted +// by increasing nonce. +func (s *Set) TransactionsByPriority(filter txpool.PendingFilter) []*LazyTransaction { + pending := s.Pool.Pending(filter) + var n int + for _, txs := range pending { + n += len(txs) + } + + all := make([]*LazyTransaction, n) + var i int + for from, txs := range pending { + for _, tx := range txs { + all[i] = &LazyTransaction{ + LazyTransaction: tx, + Sender: from, + } + i++ + } + } + + slices.SortStableFunc(all, func(a, b *LazyTransaction) int { + if a.Sender == b.Sender { + // [txpool.TxPool.Pending] already returns each slice in nonce order + // and we're performing a stable sort. + return 0 + } + if tip := a.GasTipCap.Cmp(b.GasTipCap); tip != 0 { + return -tip // Higher tips first + } + return a.Time.Compare(b.Time) + }) + return all +} diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go new file mode 100644 index 0000000..8a9b5aa --- /dev/null +++ b/txgossip/txgossip.go @@ -0,0 +1,134 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package txgossip provides a mempool for [Streaming Asynchronous Execution], +// which is also compatible with AvalancheGo's [gossip] mechanism. +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package txgossip + +import ( + "errors" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/txpool" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/rlp" + "go.uber.org/zap" +) + +var ( + _ gossip.Gossipable = Transaction{} + _ gossip.Marshaller[Transaction] = Marshaller{} + _ gossip.Set[Transaction] = (*Set)(nil) +) + +// A Transaction is a [gossip.Gossipable] wrapper for a [types.Transaction]. +type Transaction struct { + *types.Transaction +} + +// GossipID returns the transaction hash. +func (tx Transaction) GossipID() ids.ID { + return ids.ID(tx.Hash()) +} + +// A Marshaller implements [gossip.Marshaller] for [Transaction], based on RLP +// encoding. +type Marshaller struct{} + +// MarshalGossip returns the [rlp] encoding of the underlying +// [types.Transaction]. +func (Marshaller) MarshalGossip(tx Transaction) ([]byte, error) { + return rlp.EncodeToBytes(tx.Transaction) +} + +// UnmarshalGossip [rlp] decodes the buffer into a [types.Transaction]. +func (Marshaller) UnmarshalGossip(buf []byte) (Transaction, error) { + tx := Transaction{new(types.Transaction)} + if err := rlp.DecodeBytes(buf, tx.Transaction); err != nil { + return Transaction{}, err + } + return tx, nil +} + +// A Set is a [gossip.Set] wrapping a [txpool.TxPool]. Transactions MAY be added +// to the pool directly, or via [Set.Add]. +type Set struct { + Pool *txpool.TxPool + bloom *gossip.BloomFilter +} + +// NewSet returns a new [gossip.Set] and a cleanup function that MUST be called +// to release resources. +func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter, targetBloomElements int) (*Set, func() error) { + txs := make(chan core.NewTxsEvent) + sub := pool.SubscribeTransactions(txs, false) + done := make(chan struct{}) + go maintainBloomFilter(logger, bloom, txs, sub.Err(), done, targetBloomElements) + + return &Set{pool, bloom}, func() error { + sub.Unsubscribe() + <-done + return pool.Close() + } +} + +func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs <-chan core.NewTxsEvent, errs <-chan error, done chan<- struct{}, targetBloomElements int) { + defer close(done) + + for { + select { + case ev := <-txs: + if _, err := gossip.ResetBloomFilterIfNeeded(bloom, targetBloomElements); err != nil { + logger.Error("Resetting mempool bloom filter", zap.Error(err)) + } + for _, tx := range ev.Txs { + bloom.Add(Transaction{tx}) + } + + case err, ok := <-errs: + if !ok { + return + } + logger.Error("TxPool subscription", zap.Error(err)) + } + } +} + +// Add is a wrapper around [txpool.TxPool.Add], exposed to accept transactions +// over [gossip]. It MAY be bypassed, and the pool's method accessed directly. +func (s *Set) Add(tx Transaction) error { + errs := s.Pool.Add([]*types.Transaction{tx.Transaction}, false, false) + return errors.Join(errs...) +} + +// Has returns [txpool.TxPool.Has]. +func (s *Set) Has(id ids.ID) bool { + return s.Pool.Has(common.Hash(id)) +} + +// Iterate calls `fn` for every pending and queued transaction returned by +// [txpool.TxPool.Content] +func (s *Set) Iterate(fn func(Transaction) bool) { + pending, queued := s.Pool.Content() + for _, group := range []map[common.Address][]*types.Transaction{pending, queued} { + for _, txs := range group { + for _, tx := range txs { + if !fn(Transaction{tx}) { + return + } + } + } + } +} + +// GetFilter returns [gossip.BloomFilter.Marshal] for a Bloom filter of the +// transactions in the pool. +func (s *Set) GetFilter() ([]byte, []byte) { + return s.bloom.Marshal() +} diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go new file mode 100644 index 0000000..0800b68 --- /dev/null +++ b/txgossip/txgossip_test.go @@ -0,0 +1,303 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txgossip + +import ( + "context" + "math/big" + "math/rand/v2" + "path/filepath" + "runtime" + "slices" + "testing" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/network/p2p/p2ptest" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/txpool" + "github.com/ava-labs/libevm/core/txpool/legacypool" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/params" + "github.com/google/go-cmp/cmp" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/ava-labs/strevm/blocks/blockstest" + "github.com/ava-labs/strevm/saetest" + "github.com/ava-labs/strevm/saexec" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain( + m, + goleak.IgnoreCurrent(), + goleak.IgnoreTopFunction("github.com/ava-labs/libevm/core/state/snapshot.(*diskLayer).generate"), + // Even with a call to [txpool.TxPool.Close], this leaks. We don't + // expect to open and close multiple pools, so it's ok to ignore. + goleak.IgnoreTopFunction("github.com/ava-labs/libevm/core/txpool.(*TxPool).loop.func2"), + ) +} + +// sut is the system under test, primarily the [Set]. +type sut struct { + *Set + chain *blockstest.ChainBuilder + wallet *saetest.Wallet + exec *saexec.Executor +} + +func newSUT(t *testing.T, numAccounts uint) sut { + logger := saetest.NewTBLogger(t, logging.Warn) + + config := params.AllDevChainProtocolChanges + signer := types.LatestSigner(config) + wallet := saetest.NewUNSAFEWallet(t, numAccounts, signer) + + db := rawdb.NewMemoryDatabase() + genesis := blockstest.NewGenesis(t, db, config, saetest.MaxAllocFor(wallet.Addresses()...)) + + exec, err := saexec.New(genesis, config, db, nil, &saetest.HookStub{Target: 1e6}, logger) + require.NoError(t, err, "saexec.New()") + t.Cleanup(exec.Close) + + bloom, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", 1, 1e-9, 1e-9) + require.NoError(t, err, "gossip.NewBloomFilter([1 in a billion FP])") + + chain := blockstest.NewChainBuilder(genesis) + bc := NewBlockChain(exec, func(h common.Hash, n uint64) *types.Block { + b, ok := chain.GetBlock(h, n) + if !ok { + return nil + } + return b.EthBlock() + }) + + set, cleanup := NewSet(logger, newTxPool(t, bc), bloom, 1) + t.Cleanup(func() { + require.NoError(t, cleanup(), "cleanup function returned by NewSet()") + }) + + return sut{ + Set: set, + chain: chain, + wallet: wallet, + exec: exec, + } +} + +func newTxPool(t *testing.T, bc BlockChain) *txpool.TxPool { + t.Helper() + + config := legacypool.DefaultConfig // copies + config.Journal = filepath.Join(t.TempDir(), "transactions.rlp") + subs := []txpool.SubPool{legacypool.New(config, bc)} + + p, err := txpool.New(1, bc, subs) + require.NoError(t, err, "txpool.New()") + return p +} + +func TestExecutorIntegration(t *testing.T) { + ctx := t.Context() + + const numAccounts = 3 + s := newSUT(t, numAccounts) + + rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful in tests + + const txPerAccount = 5 + const numTxs = numAccounts * txPerAccount + for range txPerAccount { + for i := range numAccounts { + tx := s.wallet.SetNonceAndSign(t, i, &types.DynamicFeeTx{ + To: &common.Address{}, + Gas: params.TxGas, + GasFeeCap: big.NewInt(100), + GasTipCap: big.NewInt(1 + rng.Int64N(3)), + }) + require.NoErrorf(t, s.Add(Transaction{tx}), "%T.Add()", s.Set) + } + } + + t.Run("Iterate_after_Add", func(t *testing.T) { + // Note that calls to [txpool.TxPool] are only necessary in tests, and MUST + // NOT be replicated in production. + require.NoErrorf(t, s.Pool.Sync(), "%T.Sync()", s.Pool) + require.Lenf(t, slices.Collect(s.Iterate), numTxs, "slices.Collect(%T.Iterate)", s.Set) + }) + if t.Failed() { + t.FailNow() + } + + var ( + txs types.Transactions + last *LazyTransaction + ) + for _, tx := range s.TransactionsByPriority(txpool.PendingFilter{}) { + txs = append(txs, tx.Resolve()) + + t.Run("priority_ordering", func(t *testing.T) { + defer func() { last = tx }() + + switch { + case last == nil: + case tx.Sender == last.Sender: + require.Equal(t, last.Tx.Nonce()+1, tx.Tx.Nonce()) + case tx.GasTipCap.Eq(last.GasTipCap): + require.True(t, last.Time.Before(tx.Time)) + default: + require.GreaterOrEqual(t, last.GasTipCap.Uint64(), tx.GasTipCap.Uint64()) + } + }) + } + require.Lenf(t, txs, numTxs, "%T.TransactionsByPriority()", s.Set) + + b := s.chain.NewBlock(t, txs...) + require.NoErrorf(t, s.exec.Enqueue(ctx, b), "%T.Enqueue([txs from %T.TransactionsByPriority()])", s.exec, s.Set) + + assert.EventuallyWithTf( + t, func(c *assert.CollectT) { + require.NoErrorf(c, s.Pool.Sync(), "%T.Sync()", s.Pool) + assert.Emptyf(c, slices.Collect(s.Iterate), "slices.Collect(%T.Iterate)", s.Set) + for _, tx := range txs { + assert.Falsef(c, s.Has(ids.ID(tx.Hash())), "%T.Has(%#x)", s.Set, tx.Hash()) + } + }, + 2*time.Second, 10*time.Millisecond, + "empty %T after transactions included in a block", s.Set, + ) + + t.Run("block_execution", func(t *testing.T) { + // Although not strictly necessary, this is a secondary check for the + // maintenance of nonce ordering. It is more reliable than our manual + // confirmation earlier, so worth the 3 lines. + require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) + for i, r := range b.Receipts() { + assert.Equalf(t, types.ReceiptStatusSuccessful, r.Status, "%T[%d].Status", r, i) + } + }) +} + +func TestP2PIntegration(t *testing.T) { + ctx := t.Context() + + reg := prometheus.NewRegistry() + metrics, err := gossip.NewMetrics(reg, "") + require.NoError(t, err, "gossip.NewMetrics()") + + tests := []struct { + name string + gossiper func(_ logging.Logger, sendID ids.NodeID, send *Set, recvID ids.NodeID, recv *Set) (gossip.Gossiper, error) + }{ + { + name: "push", + gossiper: func(l logging.Logger, sendID ids.NodeID, send *Set, recvID ids.NodeID, recv *Set) (gossip.Gossiper, error) { + c := p2ptest.NewClient( + t, ctx, + sendID, p2p.NoOpHandler{}, + recvID, gossip.NewHandler(l, Marshaller{}, recv, metrics, 0), + ) + branch := gossip.BranchingFactor{Peers: 1} + return gossip.NewPushGossiper( + Marshaller{}, + send, + &stubPeers{[]ids.NodeID{recvID}}, + c, + metrics, + branch, branch, + 0, 1<<20, time.Millisecond, + ) + }, + }, + { + name: "pull", + gossiper: func(l logging.Logger, sendID ids.NodeID, send *Set, recvID ids.NodeID, recv *Set) (gossip.Gossiper, error) { + c := p2ptest.NewClient( + t, ctx, + recvID, gossip.NewHandler(l, Marshaller{}, recv, metrics, 0), + sendID, gossip.NewHandler(l, Marshaller{}, send, metrics, 0), + ) + return gossip.NewPullGossiper(l, Marshaller{}, recv, c, metrics, 1), nil + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + logger := saetest.NewTBLogger(t, logging.Debug) + + sendID := ids.GenerateTestNodeID() + recvID := ids.GenerateTestNodeID() + send := newSUT(t, 1) + // Although the receiving mempool doesn't need to sign transactions, create + // the same (deterministic) account so it has non-zero balance otherwise the + // mempool will reject it. + recv := newSUT(t, 1) + + tx := Transaction{ + Transaction: send.wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ + To: &common.Address{}, + Gas: params.TxGas, + GasPrice: big.NewInt(1), + }), + } + require.NoErrorf(t, send.Add(tx), "%T.Add()", send.Set) + require.NoErrorf(t, send.Pool.Sync(), "sender %T.Sync()", send.Pool) + + gossiper, err := tt.gossiper(logger, sendID, send.Set, recvID, recv.Set) + require.NoError(t, err) + if push, ok := gossiper.(*gossip.PushGossiper[Transaction]); ok { + push.Add(tx) + } + + // Instrumenting the receiving [Set] or intercepting the p2p layer + // is overkill when we can just retry and spin. + for ; !recv.Has(tx.GossipID()); runtime.Gosched() { + require.NoErrorf(t, gossiper.Gossip(ctx), "%T.Gossip()", gossiper) + require.NoErrorf(t, recv.Pool.Sync(), "receiver %T.Sync()", recv.Pool) + } + + want := slices.Collect(send.Iterate) + got := slices.Collect(recv.Iterate) + opt := cmp.Comparer(func(a, b Transaction) bool { + return a.Hash() == b.Hash() + }) + if diff := cmp.Diff(want, got, opt); diff != "" { + t.Errorf("slices.Collect(%T.Iterate) diff (-sender +receiver):\n%s", send.Set, diff) + } + + require.Eventuallyf( + t, func() bool { + return recv.bloom.Has(tx) + }, + 2*time.Second, 50*time.Millisecond, + "Receiving %T.bloom.Has(tx)", recv.Set, + ) + }) + } +} + +type stubPeers struct { + ids []ids.NodeID +} + +var _ interface { + p2p.NodeSampler + p2p.ValidatorSubset +} = (*stubPeers)(nil) + +func (p *stubPeers) Sample(context.Context, int) []ids.NodeID { + return p.ids +} + +func (p *stubPeers) Top(context.Context, float64) []ids.NodeID { + return p.ids +} From 1fd6ecfafb1a0ec7b59f675225f0028db36823b2 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 11:54:48 +0000 Subject: [PATCH 03/52] refactor!: use MIT contract for testing --- saetest/escrow/escrow.go | 28 +++++++++++++++++++++++++++ saetest/weth/weth.go | 41 ---------------------------------------- saexec/saexec_test.go | 40 +++++++++++++++++++++++++-------------- 3 files changed, 54 insertions(+), 55 deletions(-) create mode 100644 saetest/escrow/escrow.go delete mode 100644 saetest/weth/weth.go diff --git a/saetest/escrow/escrow.go b/saetest/escrow/escrow.go new file mode 100644 index 0000000..19effb4 --- /dev/null +++ b/saetest/escrow/escrow.go @@ -0,0 +1,28 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// The above copyright and licensing exclude the original Escrow.sol contract +// and compiled artefacts, which are licensed under the following: +// +// Copyright 2024 Divergence Tech Ltd. + +// Package escrow provides bytecode for the Escrow.sol contract deployed to +// 0x370F21541173E8B773571c135e3b5617d7f38C54 on Ethereum mainnet. +package escrow + +import "github.com/ava-labs/libevm/common" + +const ( + creation = "0x6080806040523460155761029e908161001a8239f35b5f80fdfe6040608081526004361015610012575f80fd5b5f3560e01c80633ccfd60b1461017757806351cff8d914610148578063837b2d1d1461010e578063e3d670d7146100d35763f340fa0114610051575f80fd5b60203660031901126100cf576004356001600160a01b03811691908290036100cf57815f525f602052805f209182543481018091116100bb577fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c93558151908152346020820152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b50346100cf5760203660031901126100cf576004356001600160a01b03811691908290036100cf576020915f525f8252805f20549051908152f35b50346100cf575f3660031901126100cf57602090517fe3f9c77ea5446c989d214acc27cefc902862791ee093b44540c8790a484451828152f35b346100cf5760203660031901126100cf576004356001600160a01b03811681036100cf576101759061018c565b005b346100cf575f3660031901126100cf57610175335b60018060a01b0316805f525f602052604090815f2054801561027a57815f525f6020525f83812055804710610263575f80808084865af13d1561025e5767ffffffffffffffff3d81811161024a57855191601f8201601f19908116603f011683019081118382101761024a57865281525f60203d92013e5b1561023957825191825260208201527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659190a1565b8251630a12f52160e11b8152600490fd5b634e487b7160e01b5f52604160045260245ffd5b610204565b825163cd78605960e01b8152306004820152602490fd5b5060249151906316b4356760e31b82526004820152fdfea164736f6c6343000819000a" + deployed = "0x6040608081526004361015610012575f80fd5b5f3560e01c80633ccfd60b1461017757806351cff8d914610148578063837b2d1d1461010e578063e3d670d7146100d35763f340fa0114610051575f80fd5b60203660031901126100cf576004356001600160a01b03811691908290036100cf57815f525f602052805f209182543481018091116100bb577fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c93558151908152346020820152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b50346100cf5760203660031901126100cf576004356001600160a01b03811691908290036100cf576020915f525f8252805f20549051908152f35b50346100cf575f3660031901126100cf57602090517fe3f9c77ea5446c989d214acc27cefc902862791ee093b44540c8790a484451828152f35b346100cf5760203660031901126100cf576004356001600160a01b03811681036100cf576101759061018c565b005b346100cf575f3660031901126100cf57610175335b60018060a01b0316805f525f602052604090815f2054801561027a57815f525f6020525f83812055804710610263575f80808084865af13d1561025e5767ffffffffffffffff3d81811161024a57855191601f8201601f19908116603f011683019081118382101761024a57865281525f60203d92013e5b1561023957825191825260208201527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659190a1565b8251630a12f52160e11b8152600490fd5b634e487b7160e01b5f52604160045260245ffd5b610204565b825163cd78605960e01b8152306004820152602490fd5b5060249151906316b4356760e31b82526004820152fdfea164736f6c6343000819000a" +) + +// CreationCode returns the EVM bytecode for deploying the Escrow.sol contract. +func CreationCode() []byte { + return common.FromHex(creation) +} + +// ByteCode returns the deployed EVM bytecode of the Escrow.sol contract. +func ByteCode() []byte { + return common.FromHex(deployed) +} diff --git a/saetest/weth/weth.go b/saetest/weth/weth.go deleted file mode 100644 index 8e11071..0000000 --- a/saetest/weth/weth.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// The above copyright and licensing exclude the original WETH9 contract and -// compiled artefacts, which are licensed under the following: -// -// Copyright (C) 2015, 2016, 2017 Dapphub -// -// This program is free software: you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free Software -// Foundation, either version 3 of the License, or (at your option) any later -// version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -// details. -// -// You should have received a copy of the GNU General Public License along with -// this program. If not, see . - -// Package weth provides bytecode and ABI bindings for the WETH9 contract, as -// deployed to 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 on Ethereum mainnet. -package weth - -import "github.com/ava-labs/libevm/common" - -const ( - creation = "0x60606040526040805190810160405280600d81526020017f57726170706564204574686572000000000000000000000000000000000000008152506000908051906020019061004f9291906100c8565b506040805190810160405280600481526020017f57455448000000000000000000000000000000000000000000000000000000008152506001908051906020019061009b9291906100c8565b506012600260006101000a81548160ff021916908360ff16021790555034156100c357600080fd5b61016d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061010957805160ff1916838001178555610137565b82800160010185558215610137579182015b8281111561013657825182559160200191906001019061011b565b5b5090506101449190610148565b5090565b61016a91905b8082111561016657600081600090555060010161014e565b5090565b90565b610c348061017c6000396000f3006060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029" - deployed = "0x6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b9578063095ea7b31461014757806318160ddd146101a157806323b872dd146101ca5780632e1a7d4d14610243578063313ce5671461026657806370a082311461029557806395d89b41146102e2578063a9059cbb14610370578063d0e30db0146103ca578063dd62ed3e146103d4575b6100b7610440565b005b34156100c457600080fd5b6100cc6104dd565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010c5780820151818401526020810190506100f1565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610187600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061057b565b604051808215151515815260200191505060405180910390f35b34156101ac57600080fd5b6101b461066d565b6040518082815260200191505060405180910390f35b34156101d557600080fd5b610229600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061068c565b604051808215151515815260200191505060405180910390f35b341561024e57600080fd5b61026460048080359060200190919050506109d9565b005b341561027157600080fd5b610279610b05565b604051808260ff1660ff16815260200191505060405180910390f35b34156102a057600080fd5b6102cc600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b18565b6040518082815260200191505060405180910390f35b34156102ed57600080fd5b6102f5610b30565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561033557808201518184015260208101905061031a565b50505050905090810190601f1680156103625780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037b57600080fd5b6103b0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610bce565b604051808215151515815260200191505060405180910390f35b6103d2610440565b005b34156103df57600080fd5b61042a600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610be3565b6040518082815260200191505060405180910390f35b34600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040518082815260200191505060405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105735780601f1061054857610100808354040283529160200191610573565b820191906000526020600020905b81548152906001019060200180831161055657829003601f168201915b505050505081565b600081600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60003073ffffffffffffffffffffffffffffffffffffffff1631905090565b600081600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515156106dc57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156107b457507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205414155b156108cf5781600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561084457600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b81600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610a2757600080fd5b80600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501515610ab457600080fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040518082815260200191505060405180910390a250565b600260009054906101000a900460ff1681565b60036020528060005260406000206000915090505481565b60018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610bc65780601f10610b9b57610100808354040283529160200191610bc6565b820191906000526020600020905b815481529060010190602001808311610ba957829003601f168201915b505050505081565b6000610bdb33848461068c565b905092915050565b60046020528160005260406000206020528060005260406000206000915091505054815600a165627a7a72305820deb4c2ccab3c2fdca32ab3f46728389c2fe2c165d5fafa07661e4e004f6c344a0029" -) - -// CreationCode returns the EVM bytecode for deploying the WETH9 contract. -func CreationCode() []byte { - return common.FromHex(creation) -} - -// ByteCode returns the deployed EVM bytecode of the WETH9 contract. -func ByteCode() []byte { - return common.FromHex(deployed) -} diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index f8700ef..1574d63 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -39,7 +39,7 @@ import ( "github.com/ava-labs/strevm/hook" "github.com/ava-labs/strevm/proxytime" "github.com/ava-labs/strevm/saetest" - "github.com/ava-labs/strevm/saetest/weth" + "github.com/ava-labs/strevm/saetest/escrow" ) func TestMain(m *testing.M) { @@ -239,7 +239,7 @@ func TestExecution(t *testing.T) { ) deploy := wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ Nonce: 0, - Data: weth.CreationCode(), + Data: escrow.CreationCode(), GasPrice: big.NewInt(1), Gas: 1e7, }) @@ -254,7 +254,7 @@ func TestExecution(t *testing.T) { copy(eoaAsHash[12:], eoa[:]) rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful for tests - var wantWethBalance uint64 + var wantEscrowBalance uint64 for range 10 { val := rng.Uint64N(100_000) tx := wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ @@ -262,8 +262,12 @@ func TestExecution(t *testing.T) { Value: new(big.Int).SetUint64(val), GasPrice: big.NewInt(1), Gas: 1e6, + Data: append( + crypto.Keccak256([]byte("deposit(address)"))[:4], + eoaAsHash[:]..., + ), }) - wantWethBalance += val + wantEscrowBalance += val t.Logf("Depositing %d", val) txs = append(txs, tx) @@ -274,9 +278,11 @@ func TestExecution(t *testing.T) { TxHash: tx.Hash(), Topics: []common.Hash{ crypto.Keccak256Hash([]byte("Deposit(address,uint256)")), - eoaAsHash, }, - Data: tx.Value().FillBytes(make([]byte, 32)), + Data: append( + eoaAsHash[:], + tx.Value().FillBytes(make([]byte, 32))..., + ), }}, }) } @@ -321,19 +327,25 @@ func TestExecution(t *testing.T) { sdb, err := state.New(b.PostExecutionStateRoot(), e.StateCache(), nil) require.NoErrorf(t, err, "state.New(%T.PostExecutionStateRoot(), %T.StateCache(), nil)", b, e) - if got, want := sdb.GetBalance(contract).ToBig(), new(big.Int).SetUint64(wantWethBalance); got.Cmp(want) != 0 { - t.Errorf("After WETH deposits, got contract balance %v; want %v", got, want) + if got, want := sdb.GetBalance(contract).ToBig(), new(big.Int).SetUint64(wantEscrowBalance); got.Cmp(want) != 0 { + t.Errorf("After Escrow deposits, got contract balance %v; want %v", got, want) } + enablePUSH0 := vm.BlockContext{ + BlockNumber: big.NewInt(1), + Time: 1, + Random: &common.Hash{}, + } + evm := vm.NewEVM(enablePUSH0, vm.TxContext{}, sdb, e.ChainConfig(), vm.Config{}) + callData := append( - crypto.Keccak256([]byte("balanceOf(address)"))[:4], + crypto.Keccak256([]byte("balance(address)"))[:4], eoaAsHash[:]..., ) - evm := vm.NewEVM(vm.BlockContext{Transfer: core.Transfer}, vm.TxContext{}, sdb, e.ChainConfig(), vm.Config{}) - got, _, err := evm.Call(vm.AccountRef(eoa), contract, callData, 1e6, uint256.NewInt(0)) - require.NoErrorf(t, err, "%T.Call([weth contract], [balanceOf(eoa)])", evm) - if got, want := new(uint256.Int).SetBytes(got), uint256.NewInt(wantWethBalance); !got.Eq(want) { - t.Errorf("WETH9.balanceOf([eoa]) got %v; want %v", got, want) + got, _, err := evm.StaticCall(vm.AccountRef(eoa), contract, callData, 1e6) + require.NoErrorf(t, err, "%T.Call([Escrow contract], [balance(eoa)])", evm) + if got, want := new(uint256.Int).SetBytes(got), uint256.NewInt(wantEscrowBalance); !got.Eq(want) { + t.Errorf("Escrow.balance([eoa]) got %v; want %v", got, want) } }) } From bd0a0c3a0aa0eb03f9713db6f2c12b18909c3332 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 12:03:42 +0000 Subject: [PATCH 04/52] fix: log errored tx execution and continue --- saexec/execution.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index 5cc8ed9..9477d95 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -65,7 +65,7 @@ func (e *Executor) processQueue() { ) if err := e.execute(block, logger); err != nil { - logger.Fatal("Block execution failed", zap.Error(err)) + logger.Error("Block execution failed", zap.Error(err)) return } } @@ -116,7 +116,17 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { vm.Config{}, ) if err != nil { - return fmt.Errorf("tx[%d]: %w", ti, err) + // This almost certainly means that the worst-case block inclusion + // has a bug. + logger.Error( + "Transaction execution errored (not reverted)", + zap.Stringer("block_hash", b.Hash()), + zap.Uint64("block_height", b.Height()), + zap.Int("tx_index", ti), + zap.Stringer("tx_hash", tx.Hash()), + zap.Error(err), + ) + continue } perTxClock.Tick(gas.Gas(receipt.GasUsed)) From f3ca5e78b403de2ca1b90c57afa90ad612dec55b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 12:21:46 +0000 Subject: [PATCH 05/52] doc: fix misspelling --- 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 7de2362..c66cdd6 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -44,7 +44,7 @@ func (cb *ChainBuilder) Last() *blocks.Block { return cb.chain[len(cb.chain)-1] } -// AllBlocks returns all blocks, including the genesis past to +// AllBlocks returns all blocks, including the genesis passed to // [NewChainBuilder]. func (cb *ChainBuilder) AllBlocks() []*blocks.Block { return slices.Clone(cb.chain) From d04027f28cfc0f3e1456ed8a1e22e83bcf3931f1 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 12:39:43 +0000 Subject: [PATCH 06/52] refactor: `escrow` package provides contract helpers --- saetest/escrow/escrow.go | 44 +++++++++++++++++++++++++++++++++++++--- saexec/saexec_test.go | 29 ++++++-------------------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/saetest/escrow/escrow.go b/saetest/escrow/escrow.go index 19effb4..e15e6e9 100644 --- a/saetest/escrow/escrow.go +++ b/saetest/escrow/escrow.go @@ -6,11 +6,18 @@ // // Copyright 2024 Divergence Tech Ltd. -// Package escrow provides bytecode for the Escrow.sol contract deployed to -// 0x370F21541173E8B773571c135e3b5617d7f38C54 on Ethereum mainnet. +// Package escrow provides bytecode and helpers for the Escrow.sol contract +// deployed to 0x370F21541173E8B773571c135e3b5617d7f38C54 on Ethereum mainnet. package escrow -import "github.com/ava-labs/libevm/common" +import ( + "slices" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/crypto" + "github.com/holiman/uint256" +) const ( creation = "0x6080806040523460155761029e908161001a8239f35b5f80fdfe6040608081526004361015610012575f80fd5b5f3560e01c80633ccfd60b1461017757806351cff8d914610148578063837b2d1d1461010e578063e3d670d7146100d35763f340fa0114610051575f80fd5b60203660031901126100cf576004356001600160a01b03811691908290036100cf57815f525f602052805f209182543481018091116100bb577fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c93558151908152346020820152a1005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b50346100cf5760203660031901126100cf576004356001600160a01b03811691908290036100cf576020915f525f8252805f20549051908152f35b50346100cf575f3660031901126100cf57602090517fe3f9c77ea5446c989d214acc27cefc902862791ee093b44540c8790a484451828152f35b346100cf5760203660031901126100cf576004356001600160a01b03811681036100cf576101759061018c565b005b346100cf575f3660031901126100cf57610175335b60018060a01b0316805f525f602052604090815f2054801561027a57815f525f6020525f83812055804710610263575f80808084865af13d1561025e5767ffffffffffffffff3d81811161024a57855191601f8201601f19908116603f011683019081118382101761024a57865281525f60203d92013e5b1561023957825191825260208201527f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659190a1565b8251630a12f52160e11b8152600490fd5b634e487b7160e01b5f52604160045260245ffd5b610204565b825163cd78605960e01b8152306004820152602490fd5b5060249151906316b4356760e31b82526004820152fdfea164736f6c6343000819000a" @@ -26,3 +33,34 @@ func CreationCode() []byte { func ByteCode() []byte { return common.FromHex(deployed) } + +// CallDataToDeposit returns the transaction call data to deposit native token +// for the given recipient. +func CallDataToDeposit(recipient common.Address) []byte { + return callDataWithAddr("deposit(address)", recipient) +} + +// CallDataForBalance returns the transaction call data to retrieve the balance +// in escrow for the given beneficiary. +func CallDataForBalance(beneficiary common.Address) []byte { + return callDataWithAddr("balance(address)", beneficiary) +} + +func callDataWithAddr(sig string, addr common.Address) []byte { + return slices.Concat( + crypto.Keccak256([]byte(sig))[:4], + make([]byte, 12), addr[:], + ) +} + +// DepositEvent returns the [types.Log] emitted by a successful transaction with +// [CallDataToDeposit] data. +func DepositEvent(recipient common.Address, amount *uint256.Int) *types.Log { + return &types.Log{ + Topics: []common.Hash{crypto.Keccak256Hash([]byte("Deposit(address,uint256)"))}, + Data: slices.Concat( + make([]byte, 12), recipient[:], + amount.PaddedBytes(32), + ), + } +} diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 1574d63..df02955 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -250,9 +250,6 @@ func TestExecution(t *testing.T) { ContractAddress: contract, }) - var eoaAsHash common.Hash - copy(eoaAsHash[12:], eoa[:]) - rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful for tests var wantEscrowBalance uint64 for range 10 { @@ -262,28 +259,18 @@ func TestExecution(t *testing.T) { Value: new(big.Int).SetUint64(val), GasPrice: big.NewInt(1), Gas: 1e6, - Data: append( - crypto.Keccak256([]byte("deposit(address)"))[:4], - eoaAsHash[:]..., - ), + Data: escrow.CallDataToDeposit(eoa), }) wantEscrowBalance += val t.Logf("Depositing %d", val) txs = append(txs, tx) + ev := escrow.DepositEvent(eoa, uint256.NewInt(val)) + ev.Address = contract + ev.TxHash = tx.Hash() want = append(want, &types.Receipt{ TxHash: tx.Hash(), - Logs: []*types.Log{{ - Address: contract, - TxHash: tx.Hash(), - Topics: []common.Hash{ - crypto.Keccak256Hash([]byte("Deposit(address,uint256)")), - }, - Data: append( - eoaAsHash[:], - tx.Value().FillBytes(make([]byte, 32))..., - ), - }}, + Logs: []*types.Log{ev}, }) } @@ -338,11 +325,7 @@ func TestExecution(t *testing.T) { } evm := vm.NewEVM(enablePUSH0, vm.TxContext{}, sdb, e.ChainConfig(), vm.Config{}) - callData := append( - crypto.Keccak256([]byte("balance(address)"))[:4], - eoaAsHash[:]..., - ) - got, _, err := evm.StaticCall(vm.AccountRef(eoa), contract, callData, 1e6) + got, _, err := evm.StaticCall(vm.AccountRef(eoa), contract, escrow.CallDataForBalance(eoa), 1e6) require.NoErrorf(t, err, "%T.Call([Escrow contract], [balance(eoa)])", evm) if got, want := new(uint256.Int).SetBytes(got), uint256.NewInt(wantEscrowBalance); !got.Eq(want) { t.Errorf("Escrow.balance([eoa]) got %v; want %v", got, want) From f442e7606eddd882120f7c36406b22f8688649ee Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 12:41:11 +0000 Subject: [PATCH 07/52] fix: remove duplicated log fields --- saexec/execution.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index 9477d95..2990d06 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -120,8 +120,6 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { // has a bug. logger.Error( "Transaction execution errored (not reverted)", - zap.Stringer("block_hash", b.Hash()), - zap.Uint64("block_height", b.Height()), zap.Int("tx_index", ti), zap.Stringer("tx_hash", tx.Hash()), zap.Error(err), From d74bccca46cee1d313064827c6875af37905f8a2 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 12:44:26 +0000 Subject: [PATCH 08/52] refactor: `newSUT()` constructs memory database --- saexec/saexec_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index df02955..f0b1010 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -21,7 +21,6 @@ import ( "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/crypto" - "github.com/ava-labs/libevm/ethdb" "github.com/ava-labs/libevm/libevm" "github.com/ava-labs/libevm/libevm/hookstest" "github.com/ava-labs/libevm/params" @@ -61,10 +60,12 @@ type SUT struct { wallet *saetest.Wallet } -func newSUT(tb testing.TB, db ethdb.Database, hooks hook.Points) SUT { +func newSUT(tb testing.TB, hooks hook.Points) SUT { tb.Helper() config := params.AllDevChainProtocolChanges + db := rawdb.NewMemoryDatabase() + wallet := saetest.NewUNSAFEWallet(tb, 1, types.LatestSigner(config)) alloc := saetest.MaxAllocFor(wallet.Addresses()...) genesis := blockstest.NewGenesis(tb, db, config, alloc) @@ -84,12 +85,12 @@ func defaultHooks() *saetest.HookStub { } func TestImmediateShutdownNonBlocking(t *testing.T) { - newSUT(t, rawdb.NewMemoryDatabase(), defaultHooks()) // calls [Executor.Close] in test cleanup + newSUT(t, defaultHooks()) // calls [Executor.Close] in test cleanup } func TestExecutionSynchronisation(t *testing.T) { ctx := context.Background() - sut := newSUT(t, rawdb.NewMemoryDatabase(), defaultHooks()) + sut := newSUT(t, defaultHooks()) e, chain := sut.Executor, sut.chain for range uint64(10) { @@ -110,7 +111,7 @@ func TestReceiptPropagation(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - sut := newSUT(t, rawdb.NewMemoryDatabase(), defaultHooks()) + sut := newSUT(t, defaultHooks()) e, chain, wallet := sut.Executor, sut.chain, sut.wallet var want [][]*types.Receipt @@ -148,7 +149,7 @@ func TestSubscriptions(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - sut := newSUT(t, rawdb.NewMemoryDatabase(), defaultHooks()) + sut := newSUT(t, defaultHooks()) e, chain, wallet := sut.Executor, sut.chain, sut.wallet precompile := common.Address{'p', 'r', 'e'} @@ -229,7 +230,7 @@ func TestExecution(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - sut := newSUT(t, rawdb.NewMemoryDatabase(), defaultHooks()) + sut := newSUT(t, defaultHooks()) wallet := sut.wallet eoa := wallet.Addresses()[0] @@ -437,7 +438,7 @@ func TestGasAccounting(t *testing.T) { } hooks := &saetest.HookStub{} - sut := newSUT(t, rawdb.NewMemoryDatabase(), hooks) + sut := newSUT(t, hooks) e, chain, wallet := sut.Executor, sut.chain, sut.wallet for i, step := range steps { From 90a49eb0bde18718c26612a57e551023eabc82a7 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 15:53:21 +0000 Subject: [PATCH 09/52] feat: `saetest.TBLogger` errors cancel context --- blocks/blockstest/blocks.go | 24 +++++++++++-- blocks/blockstest/blocks_test.go | 8 +++-- blocks/blockstest/chain.go | 49 +++++++++++++++++++++++++-- saetest/logging.go | 41 +++++++++++++++++----- saexec/saexec_test.go | 58 +++++++++++++++++--------------- 5 files changed, 137 insertions(+), 43 deletions(-) diff --git a/blocks/blockstest/blocks.go b/blocks/blockstest/blocks.go index 73360e0..fa952bd 100644 --- a/blocks/blockstest/blocks.go +++ b/blocks/blockstest/blocks.go @@ -67,14 +67,34 @@ func WithReceipts(rs types.Receipts) EthBlockOption { }) } +// A BlockOption configures the default block properties created by [NewBlock]. +type BlockOption = options.Option[blockProperties] + // NewBlock constructs an SAE block, wrapping the raw Ethereum block. -func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block) *blocks.Block { +func NewBlock(tb testing.TB, eth *types.Block, parent, lastSettled *blocks.Block, opts ...BlockOption) *blocks.Block { tb.Helper() - b, err := blocks.New(eth, parent, lastSettled, saetest.NewTBLogger(tb, logging.Warn)) + + 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 } +type blockProperties struct { + logger logging.Logger +} + +// WithLogger overrides the logger passed to [blocks.New] by [NewBlock]. +func WithLogger(l logging.Logger) BlockOption { + return options.Func[blockProperties](func(p *blockProperties) { + p.logger = l + }) +} + // 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 diff --git a/blocks/blockstest/blocks_test.go b/blocks/blockstest/blocks_test.go index 53ef35f..cdf7978 100644 --- a/blocks/blockstest/blocks_test.go +++ b/blocks/blockstest/blocks_test.go @@ -74,9 +74,11 @@ func TestIntegration(t *testing.T) { txs = append(txs, tx) } } - b := build.NewBlock(t, txs, ModifyHeader(func(h *types.Header) { - h.GasLimit = 100e6 - })) + b := build.NewBlock(t, txs, WithEthBlockOptions( + 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) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index c66cdd6..e215037 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/libevm/options" "github.com/ava-labs/strevm/blocks" ) @@ -19,6 +20,7 @@ import ( // A ChainBuilder builds a chain of blocks, maintaining necessary invariants. type ChainBuilder struct { chain []*blocks.Block + opts []ChainOption } // NewChainBuilder returns a new ChainBuilder starting from the provided block, @@ -29,12 +31,53 @@ func NewChainBuilder(genesis *blocks.Block) *ChainBuilder { } } +// A ChainOption configures [ChainBuilder.NewBlock]. +type ChainOption = options.Option[chainOptions] + +// SetDefaultOptions sets the default options upon which all +// additional options passed to [ChainBuilder.NewBlock] are appended. +func (cb *ChainBuilder) SetDefaultOptions(opts ...ChainOption) { + cb.opts = opts +} + +type chainOptions struct { + ethOpts []EthBlockOption + saeOpts []BlockOption +} + +// WithEthBlockOptions wraps the options that [ChainBuilder.NewBlock] propagates +// to [NewEthBlock]. +func WithEthBlockOptions(opts ...EthBlockOption) ChainOption { + return options.Func[chainOptions](func(co *chainOptions) { + co.ethOpts = append(co.ethOpts, opts...) + }) +} + +// WithBlockOptions wraps the options that [ChainBuilder.NewBlock] propagates to +// [NewBlock]. +func WithBlockOptions(opts ...BlockOption) ChainOption { + return options.Func[chainOptions](func(co *chainOptions) { + co.saeOpts = append(co.saeOpts, opts...) + }) +} + +func ethBlockOptions(opts []ChainOption) []EthBlockOption { + return options.ApplyTo(&chainOptions{}, opts...).ethOpts +} + +func blockOptions(opts []ChainOption) []BlockOption { + return options.ApplyTo(&chainOptions{}, opts...).saeOpts +} + // NewBlock constructs and returns a new block in the chain. -func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts ...EthBlockOption) *blocks.Block { +func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts ...ChainOption) *blocks.Block { tb.Helper() + opts = slices.Concat(cb.opts, opts) + last := cb.Last() - eth := NewEthBlock(last.EthBlock(), txs, opts...) - cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil)) // TODO(arr4n) support last-settled blocks + eth := NewEthBlock(last.EthBlock(), txs, ethBlockOptions(opts)...) + cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil, blockOptions(opts)...)) // TODO(arr4n) support last-settled blocks + return cb.Last() } diff --git a/saetest/logging.go b/saetest/logging.go index ca57e8b..eb7d2c8 100644 --- a/saetest/logging.go +++ b/saetest/logging.go @@ -4,6 +4,7 @@ package saetest import ( + "context" "runtime" "slices" "testing" @@ -104,24 +105,39 @@ func (l *LogRecorder) AtLeast(lvl logging.Level) []*LogRecord { return l.Filter(func(r *LogRecord) bool { return r.Level >= lvl }) } -// NewTBLogger constructs a logger that propagates logs to the [testing.TB]. -// WARNING and ERROR logs are sent to [testing.TB.Errorf] while FATAL is sent to +// NewTBLogger constructs a logger that propagates logs to [testing.TB]. WARNING +// and ERROR logs are sent to [testing.TB.Errorf] while FATAL is sent to // [testing.TB.Fatalf]. All other logs are sent to [testing.TB.Logf]. Although // the level can be configured, it is silently capped at [logging.Warn]. // //nolint:thelper // The outputs include the logging site while the TB site is most useful if here -func NewTBLogger(tb testing.TB, level logging.Level) logging.Logger { - return &logger{ +func NewTBLogger(tb testing.TB, level logging.Level) *TBLogger { + l := &TBLogger{tb: tb} + l.logger = &logger{ + handler: l, // TODO(arr4n) remove the recursion here and in [LogRecorder] level: min(level, logging.Warn), - handler: &tbLogger{tb: tb}, } + return l +} + +// TBLogger is a [logging.Logger] that propagates logs to [testing.TB]. +type TBLogger struct { + *logger + tb testing.TB + onError []context.CancelFunc } -type tbLogger struct { - tb testing.TB +// CancelOnError pipes `ctx` to and from [context.WithCancel], calling the +// [context.CancelFunc] after logs >= [logging.Error], and during [testing.TB] +// cleanup. +func (l *TBLogger) CancelOnError(ctx context.Context) context.Context { + ctx, cancel := context.WithCancel(ctx) + l.onError = append(l.onError, cancel) + l.tb.Cleanup(cancel) + return ctx } -func (l *tbLogger) log(lvl logging.Level, msg string, fields ...zap.Field) { +func (l *TBLogger) log(lvl logging.Level, msg string, fields ...zap.Field) { var to func(string, ...any) switch { case lvl == logging.Warn || lvl == logging.Error: // because @ARR4N says warnings in tests are errors @@ -132,6 +148,15 @@ func (l *tbLogger) log(lvl logging.Level, msg string, fields ...zap.Field) { to = l.tb.Logf } + defer func() { + if lvl < logging.Error { + return + } + for _, fn := range l.onError { + fn() + } + }() + enc := zapcore.NewMapObjectEncoder() for _, f := range fields { f.AddTo(enc) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index f0b1010..68dd20b 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -10,7 +10,6 @@ import ( "math/big" "math/rand/v2" "testing" - "time" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/gas" @@ -58,25 +57,40 @@ type SUT struct { *Executor chain *blockstest.ChainBuilder wallet *saetest.Wallet + logger logging.Logger } -func newSUT(tb testing.TB, hooks hook.Points) SUT { +// newSUT returns a new SUT. Any >= [logging.Error] on the logger will also +// cancel the returned context, which is useful when waiting for blocks that +// can never finish execution because of an error. +func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { tb.Helper() + logger := saetest.NewTBLogger(tb, logging.Warn) + ctx := logger.CancelOnError(tb.Context()) + config := params.AllDevChainProtocolChanges db := rawdb.NewMemoryDatabase() + tdbConfig := &triedb.Config{} wallet := saetest.NewUNSAFEWallet(tb, 1, types.LatestSigner(config)) alloc := saetest.MaxAllocFor(wallet.Addresses()...) - genesis := blockstest.NewGenesis(tb, db, config, alloc) + genesis := blockstest.NewGenesis(tb, db, config, alloc, blockstest.WithTrieDBConfig(tdbConfig)) - e, err := New(genesis, config, db, (*triedb.Config)(nil), hooks, saetest.NewTBLogger(tb, logging.Warn)) + e, err := New(genesis, config, db, tdbConfig, hooks, logger) require.NoError(tb, err, "New()") tb.Cleanup(e.Close) - return SUT{ + + chain := blockstest.NewChainBuilder(e.LastExecuted()) + chain.SetDefaultOptions(blockstest.WithBlockOptions( + blockstest.WithLogger(logger)), + ) + + return ctx, SUT{ Executor: e, - chain: blockstest.NewChainBuilder(e.LastExecuted()), + chain: chain, wallet: wallet, + logger: logger, } } @@ -89,8 +103,7 @@ func TestImmediateShutdownNonBlocking(t *testing.T) { } func TestExecutionSynchronisation(t *testing.T) { - ctx := context.Background() - sut := newSUT(t, defaultHooks()) + ctx, sut := newSUT(t, defaultHooks()) e, chain := sut.Executor, sut.chain for range uint64(10) { @@ -108,10 +121,7 @@ func TestExecutionSynchronisation(t *testing.T) { } func TestReceiptPropagation(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - sut := newSUT(t, defaultHooks()) + ctx, sut := newSUT(t, defaultHooks()) e, chain, wallet := sut.Executor, sut.chain, sut.wallet var want [][]*types.Receipt @@ -146,10 +156,7 @@ func TestReceiptPropagation(t *testing.T) { } func TestSubscriptions(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - sut := newSUT(t, defaultHooks()) + ctx, sut := newSUT(t, defaultHooks()) e, chain, wallet := sut.Executor, sut.chain, sut.wallet precompile := common.Address{'p', 'r', 'e'} @@ -227,10 +234,7 @@ func testEvents[T any](tb testing.TB, got *saetest.EventCollector[T], want []T, } func TestExecution(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - sut := newSUT(t, defaultHooks()) + ctx, sut := newSUT(t, defaultHooks()) wallet := sut.wallet eoa := wallet.Addresses()[0] @@ -335,8 +339,8 @@ func TestExecution(t *testing.T) { } func TestGasAccounting(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() + hooks := &saetest.HookStub{} + ctx, sut := newSUT(t, hooks) const gasPerTx = gas.Gas(params.TxGas) at := func(blockTime, txs uint64, rate gas.Gas) *proxytime.Time[gas.Gas] { @@ -437,8 +441,6 @@ func TestGasAccounting(t *testing.T) { }, } - hooks := &saetest.HookStub{} - sut := newSUT(t, hooks) e, chain, wallet := sut.Executor, sut.chain, sut.wallet for i, step := range steps { @@ -454,9 +456,11 @@ func TestGasAccounting(t *testing.T) { }) } - b := chain.NewBlock(t, txs, blockstest.ModifyHeader(func(h *types.Header) { - h.Time = step.blockTime - })) + b := chain.NewBlock(t, txs, blockstest.WithEthBlockOptions( + blockstest.ModifyHeader(func(h *types.Header) { + h.Time = step.blockTime + }), + )) require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) From 0245a1b16e36ff438ea1b81fe3fc5fd67bb615b9 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 16:12:46 +0000 Subject: [PATCH 10/52] feat: user-defined before- and after-block hooks --- hook/hook.go | 17 +++++++++++++---- saetest/saetest.go | 14 +++++++++++++- saexec/execution.go | 8 +++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/hook/hook.go b/hook/hook.go index edccc4b..c8e7dfd 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -8,7 +8,10 @@ package hook import ( + "fmt" + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/core/state" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/params" @@ -20,19 +23,25 @@ import ( // Points define user-injected hook points. type Points interface { GasTarget(parent *types.Block) gas.Gas + BeforeBlock(params.Rules, *state.StateDB, *types.Block) error + AfterBlock(*state.StateDB, *types.Block, types.Receipts) } // BeforeBlock is intended to be called before processing a block, with the gas // target sourced from [Points]. -func BeforeBlock(clock *gastime.Time, block *types.Header, target gas.Gas) error { - clock.FastForwardTo(block.Time) - return clock.SetTarget(target) +func BeforeBlock(pts Points, rules params.Rules, sdb *state.StateDB, b *types.Block, clock *gastime.Time, target gas.Gas) error { + clock.FastForwardTo(b.Time()) + if err := clock.SetTarget(target); err != nil { + return fmt.Errorf("%T.SetTarget() before block: %w", clock, err) + } + return pts.BeforeBlock(rules, sdb, b) } // AfterBlock is intended to be called after processing a block, with the gas // sourced from [types.Block.GasUsed] or equivalent. -func AfterBlock(clock *gastime.Time, used gas.Gas) { +func AfterBlock(pts Points, sdb *state.StateDB, b *types.Block, clock *gastime.Time, used gas.Gas, rs types.Receipts) { clock.Tick(used) + pts.AfterBlock(sdb, b, rs) } // MinimumGasConsumption MUST be used as the implementation for the respective diff --git a/saetest/saetest.go b/saetest/saetest.go index 941b163..845d007 100644 --- a/saetest/saetest.go +++ b/saetest/saetest.go @@ -12,8 +12,10 @@ import ( "sync" "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/core/state" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/event" + "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/trie" "github.com/google/go-cmp/cmp" @@ -104,4 +106,14 @@ type HookStub struct { var _ hook.Points = (*HookStub)(nil) // GasTarget ignores its argument and always returns [HookStub.Target]. -func (s *HookStub) GasTarget(*types.Block) gas.Gas { return s.Target } +func (s *HookStub) GasTarget(*types.Block) gas.Gas { + return s.Target +} + +// BeforeBlock is a no-op that always returns nil. +func (*HookStub) BeforeBlock(params.Rules, *state.StateDB, *types.Block) error { + return nil +} + +// AfterBlock is a no-op. +func (*HookStub) AfterBlock(*state.StateDB, *types.Block, types.Receipts) {} diff --git a/saexec/execution.go b/saexec/execution.go index 2990d06..8a72514 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -87,8 +87,11 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { return fmt.Errorf("executing blocks out of order: %d then %d", last, curr) } + scratch := &e.executeScratchSpace + rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) + target := e.hooks.GasTarget(b.ParentBlock().EthBlock()) - if err := hook.BeforeBlock(e.gasClock, b.Header(), target); err != nil { + if err := hook.BeforeBlock(e.hooks, rules, scratch.statedb, b.EthBlock(), e.gasClock, target); err != nil { return fmt.Errorf("before-block hook: %v", err) } perTxClock := e.gasClock.Time.Clone() @@ -99,7 +102,6 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { gasPool := core.GasPool(math.MaxUint64) // required by geth but irrelevant so max it out var blockGasConsumed gas.Gas - scratch := &e.executeScratchSpace receipts := make(types.Receipts, len(b.Transactions())) for ti, tx := range b.Transactions() { scratch.statedb.SetTxContext(tx.Hash(), ti) @@ -147,7 +149,7 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { receipts[ti] = receipt } endTime := time.Now() - hook.AfterBlock(e.gasClock, blockGasConsumed) + hook.AfterBlock(e.hooks, scratch.statedb, b.EthBlock(), e.gasClock, blockGasConsumed, receipts) if e.gasClock.Time.Compare(perTxClock) != 0 { return fmt.Errorf("broken invariant: block-resolution clock @ %s does not match tx-resolution clock @ %s", e.gasClock.String(), perTxClock.String()) } From 0183d17faf835118ead7d0c8fbf7c074580cfa92 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 20:11:46 +0000 Subject: [PATCH 11/52] feat: `hook.Points` supports sub-second block times --- hook/hook.go | 12 +++++++++--- hook/hookstest/stub.go | 39 +++++++++++++++++++++++++++++++++++++++ saetest/saetest.go | 25 ------------------------- saexec/execution.go | 3 +-- saexec/saexec_test.go | 11 ++++++----- 5 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 hook/hookstest/stub.go diff --git a/hook/hook.go b/hook/hook.go index c8e7dfd..aca21d8 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/params" + "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/intmath" saeparams "github.com/ava-labs/strevm/params" @@ -23,18 +24,23 @@ import ( // Points define user-injected hook points. type Points interface { GasTarget(parent *types.Block) gas.Gas + SubSecondBlockTime(*types.Block) gas.Gas BeforeBlock(params.Rules, *state.StateDB, *types.Block) error AfterBlock(*state.StateDB, *types.Block, types.Receipts) } // BeforeBlock is intended to be called before processing a block, with the gas // target sourced from [Points]. -func BeforeBlock(pts Points, rules params.Rules, sdb *state.StateDB, b *types.Block, clock *gastime.Time, target gas.Gas) error { - clock.FastForwardTo(b.Time()) +func BeforeBlock(pts Points, rules params.Rules, sdb *state.StateDB, b *blocks.Block, clock *gastime.Time) error { + clock.FastForwardTo( + b.BuildTime(), + pts.SubSecondBlockTime(b.EthBlock()), + ) + target := pts.GasTarget(b.ParentBlock().EthBlock()) if err := clock.SetTarget(target); err != nil { return fmt.Errorf("%T.SetTarget() before block: %w", clock, err) } - return pts.BeforeBlock(rules, sdb, b) + return pts.BeforeBlock(rules, sdb, b.EthBlock()) } // AfterBlock is intended to be called after processing a block, with the gas diff --git a/hook/hookstest/stub.go b/hook/hookstest/stub.go new file mode 100644 index 0000000..eeb481b --- /dev/null +++ b/hook/hookstest/stub.go @@ -0,0 +1,39 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package hookstest provides a test double for SAE's [hook] package. +package hookstest + +import ( + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/core/state" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/params" + + "github.com/ava-labs/strevm/hook" +) + +// Stub implements [hook.Points]. +type Stub struct { + Target gas.Gas +} + +var _ hook.Points = (*Stub)(nil) + +// GasTarget ignores its argument and always returns [Stub.Target]. +func (s *Stub) GasTarget(parent *types.Block) gas.Gas { + return s.Target +} + +// SubSecondBlock time ignores its argument and always returns 0. +func (*Stub) SubSecondBlockTime(*types.Block) gas.Gas { + return 0 +} + +// BeforeBlock is a no-op that always returns nil. +func (*Stub) BeforeBlock(params.Rules, *state.StateDB, *types.Block) error { + return nil +} + +// AfterBlock is a no-op. +func (*Stub) AfterBlock(*state.StateDB, *types.Block, types.Receipts) {} diff --git a/saetest/saetest.go b/saetest/saetest.go index 845d007..15856de 100644 --- a/saetest/saetest.go +++ b/saetest/saetest.go @@ -11,15 +11,10 @@ import ( "slices" "sync" - "github.com/ava-labs/avalanchego/vms/components/gas" - "github.com/ava-labs/libevm/core/state" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/event" - "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/trie" "github.com/google/go-cmp/cmp" - - "github.com/ava-labs/strevm/hook" ) // TrieHasher returns an arbitrary trie hasher. @@ -97,23 +92,3 @@ func (c *EventCollector[T]) WaitForAtLeast(n int) { } c.cond.L.Unlock() } - -// HookStub implements [hook.Points]. -type HookStub struct { - Target gas.Gas -} - -var _ hook.Points = (*HookStub)(nil) - -// GasTarget ignores its argument and always returns [HookStub.Target]. -func (s *HookStub) GasTarget(*types.Block) gas.Gas { - return s.Target -} - -// BeforeBlock is a no-op that always returns nil. -func (*HookStub) BeforeBlock(params.Rules, *state.StateDB, *types.Block) error { - return nil -} - -// AfterBlock is a no-op. -func (*HookStub) AfterBlock(*state.StateDB, *types.Block, types.Receipts) {} diff --git a/saexec/execution.go b/saexec/execution.go index 8a72514..0c4c406 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -90,8 +90,7 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { scratch := &e.executeScratchSpace rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) - target := e.hooks.GasTarget(b.ParentBlock().EthBlock()) - if err := hook.BeforeBlock(e.hooks, rules, scratch.statedb, b.EthBlock(), e.gasClock, target); err != nil { + if err := hook.BeforeBlock(e.hooks, rules, scratch.statedb, b, e.gasClock); err != nil { return fmt.Errorf("before-block hook: %v", err) } perTxClock := e.gasClock.Time.Clone() diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 68dd20b..2a4d5c3 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -21,7 +21,7 @@ import ( "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/crypto" "github.com/ava-labs/libevm/libevm" - "github.com/ava-labs/libevm/libevm/hookstest" + libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/triedb" "github.com/google/go-cmp/cmp" @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/hook" + saehookstest "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/proxytime" "github.com/ava-labs/strevm/saetest" "github.com/ava-labs/strevm/saetest/escrow" @@ -94,8 +95,8 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { } } -func defaultHooks() *saetest.HookStub { - return &saetest.HookStub{Target: 1e6} +func defaultHooks() *saehookstest.Stub { + return &saehookstest.Stub{Target: 1e6} } func TestImmediateShutdownNonBlocking(t *testing.T) { @@ -160,7 +161,7 @@ func TestSubscriptions(t *testing.T) { e, chain, wallet := sut.Executor, sut.chain, sut.wallet precompile := common.Address{'p', 'r', 'e'} - stub := &hookstest.Stub{ + stub := &libevmhookstest.Stub{ PrecompileOverrides: map[common.Address]libevm.PrecompiledContract{ precompile: vm.NewStatefulPrecompile(func(env vm.PrecompileEnvironment, input []byte) (ret []byte, err error) { env.StateDB().AddLog(&types.Log{ @@ -339,7 +340,7 @@ func TestExecution(t *testing.T) { } func TestGasAccounting(t *testing.T) { - hooks := &saetest.HookStub{} + hooks := &saehookstest.Stub{} ctx, sut := newSUT(t, hooks) const gasPerTx = gas.Gas(params.TxGas) From e7772cedffcf23aa72afcedee9cbc3ff1f2dfeb1 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 20:16:43 +0000 Subject: [PATCH 12/52] chore: placate the linter --- hook/hookstest/stub.go | 2 +- saexec/saexec_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hook/hookstest/stub.go b/hook/hookstest/stub.go index eeb481b..611c70f 100644 --- a/hook/hookstest/stub.go +++ b/hook/hookstest/stub.go @@ -25,7 +25,7 @@ func (s *Stub) GasTarget(parent *types.Block) gas.Gas { return s.Target } -// SubSecondBlock time ignores its argument and always returns 0. +// SubSecondBlockTime time ignores its argument and always returns 0. func (*Stub) SubSecondBlockTime(*types.Block) gas.Gas { return 0 } diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 2a4d5c3..fe6479d 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -21,7 +21,6 @@ import ( "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/crypto" "github.com/ava-labs/libevm/libevm" - libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/triedb" "github.com/google/go-cmp/cmp" @@ -35,10 +34,12 @@ import ( "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/hook" - saehookstest "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/proxytime" "github.com/ava-labs/strevm/saetest" "github.com/ava-labs/strevm/saetest/escrow" + + libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest" + saehookstest "github.com/ava-labs/strevm/hook/hookstest" ) func TestMain(m *testing.M) { From 03aa6394d470da78f7bcbd024d007d1496cb818b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 20:22:19 +0000 Subject: [PATCH 13/52] chore: if `golangci-lint` just said what it wanted then this would all be much easier for everyone --- .golangci.yml | 1 - saexec/saexec_test.go | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e221766..70ea23f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,7 +51,6 @@ linters-settings: - localmodule # The rest of these break developer expections, in increasing order of # divergence, so are at the end to increase the chance of being seen. - - alias - dot - blank goheader: diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index fe6479d..2a4d5c3 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/crypto" "github.com/ava-labs/libevm/libevm" + libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/triedb" "github.com/google/go-cmp/cmp" @@ -34,12 +35,10 @@ import ( "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/hook" + saehookstest "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/proxytime" "github.com/ava-labs/strevm/saetest" "github.com/ava-labs/strevm/saetest/escrow" - - libevmhookstest "github.com/ava-labs/libevm/libevm/hookstest" - saehookstest "github.com/ava-labs/strevm/hook/hookstest" ) func TestMain(m *testing.M) { From 4e46f0b2cb9ea09e38c517abb60a2d529ef17639 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 20:38:55 +0000 Subject: [PATCH 14/52] fix: `tsgossip` test --- blocks/blockstest/chain.go | 21 ++++++++++++++++++--- txgossip/txgossip_test.go | 5 +++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index e215037..fa3f37e 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -11,6 +11,7 @@ import ( "slices" "testing" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/libevm/options" @@ -19,15 +20,18 @@ import ( // A ChainBuilder builds a chain of blocks, maintaining necessary invariants. type ChainBuilder struct { - chain []*blocks.Block - opts []ChainOption + chain []*blocks.Block + byHash map[common.Hash]*blocks.Block + + opts []ChainOption } // 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}, + chain: []*blocks.Block{genesis}, + byHash: make(map[common.Hash]*blocks.Block), } } @@ -97,3 +101,14 @@ func (cb *ChainBuilder) AllBlocks() []*blocks.Block { func (cb *ChainBuilder) AllExceptGenesis() []*blocks.Block { return slices.Clone(cb.chain[1:]) } + +// GetBlock returns the block with specified hash and height, and a flag +// indicating if it was found. If either argument does not match, it returns +// `nil, false`. +func (cb *ChainBuilder) GetBlock(h common.Hash, num uint64) (*blocks.Block, bool) { + b, ok := cb.byHash[h] + if !ok || b.NumberU64() != num { + return nil, false + } + return b, true +} diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 0800b68..c4c680f 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -31,6 +31,7 @@ import ( "go.uber.org/goleak" "github.com/ava-labs/strevm/blocks/blockstest" + "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/saetest" "github.com/ava-labs/strevm/saexec" ) @@ -64,7 +65,7 @@ func newSUT(t *testing.T, numAccounts uint) sut { db := rawdb.NewMemoryDatabase() genesis := blockstest.NewGenesis(t, db, config, saetest.MaxAllocFor(wallet.Addresses()...)) - exec, err := saexec.New(genesis, config, db, nil, &saetest.HookStub{Target: 1e6}, logger) + exec, err := saexec.New(genesis, config, db, nil, &hookstest.Stub{Target: 1e6}, logger) require.NoError(t, err, "saexec.New()") t.Cleanup(exec.Close) @@ -160,7 +161,7 @@ func TestExecutorIntegration(t *testing.T) { } require.Lenf(t, txs, numTxs, "%T.TransactionsByPriority()", s.Set) - b := s.chain.NewBlock(t, txs...) + b := s.chain.NewBlock(t, txs) require.NoErrorf(t, s.exec.Enqueue(ctx, b), "%T.Enqueue([txs from %T.TransactionsByPriority()])", s.exec, s.Set) assert.EventuallyWithTf( From 60c8273e76330ffad5406b9ef1d8583fd896e619 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 17 Nov 2025 20:41:31 +0000 Subject: [PATCH 15/52] build: bump `crate-crypto/go-kzg-4844` --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89916a2..433bceb 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/gnark-crypto v0.18.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect - github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + github.com/crate-crypto/go-kzg-4844 v1.1.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 v1.0.0 // indirect diff --git a/go.sum b/go.sum index 53a543b..39485a8 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= -github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 472f9fbf04fc19beebb3ff0f73a13bdc62e3d93f Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 09:38:44 +0000 Subject: [PATCH 16/52] chore: Linty McLintface --- proxytime/proxytime_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxytime/proxytime_test.go b/proxytime/proxytime_test.go index eeb7523..8231bc7 100644 --- a/proxytime/proxytime_test.go +++ b/proxytime/proxytime_test.go @@ -10,10 +10,9 @@ import ( "testing" "time" + gocmp "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - gocmp "github.com/google/go-cmp/cmp" ) func frac(num, den uint64) FractionalSecond[uint64] { From 433494ff23b8a51f22da03047a97a7a4cb6fd6b7 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 12:16:43 +0000 Subject: [PATCH 17/52] fix: contextual opcodes, particularly `BLOCKHASH` --- blocks/blockstest/chain.go | 29 ++++-- dummy/dummy.go | 35 ------- saexec/context.go | 40 ++++++++ saexec/execution.go | 3 +- saexec/saexec.go | 25 ++--- saexec/saexec_test.go | 193 ++++++++++++++++++++++++++++++++++++- 6 files changed, 266 insertions(+), 59 deletions(-) delete mode 100644 dummy/dummy.go create mode 100644 saexec/context.go diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index e215037..2a7053b 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -11,6 +11,7 @@ import ( "slices" "testing" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/libevm/options" @@ -19,15 +20,18 @@ import ( // A ChainBuilder builds a chain of blocks, maintaining necessary invariants. type ChainBuilder struct { - chain []*blocks.Block - opts []ChainOption + chain []*blocks.Block + byHash map[common.Hash]*blocks.Block + + defaultOpts []ChainOption } // 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}, + chain: []*blocks.Block{genesis}, + byHash: make(map[common.Hash]*blocks.Block), } } @@ -37,7 +41,7 @@ type ChainOption = options.Option[chainOptions] // SetDefaultOptions sets the default options upon which all // additional options passed to [ChainBuilder.NewBlock] are appended. func (cb *ChainBuilder) SetDefaultOptions(opts ...ChainOption) { - cb.opts = opts + cb.defaultOpts = opts } type chainOptions struct { @@ -72,11 +76,13 @@ func blockOptions(opts []ChainOption) []BlockOption { // NewBlock constructs and returns a new block in the chain. func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts ...ChainOption) *blocks.Block { tb.Helper() - opts = slices.Concat(cb.opts, opts) + opts = slices.Concat(cb.defaultOpts, opts) last := cb.Last() eth := NewEthBlock(last.EthBlock(), txs, ethBlockOptions(opts)...) - cb.chain = append(cb.chain, NewBlock(tb, eth, last, nil, blockOptions(opts)...)) // TODO(arr4n) support last-settled blocks + b := NewBlock(tb, eth, last, nil, blockOptions(opts)...) // TODO(arr4n) support last-settled blocks + cb.chain = append(cb.chain, b) + cb.byHash[b.Hash()] = b return cb.Last() } @@ -97,3 +103,14 @@ func (cb *ChainBuilder) AllBlocks() []*blocks.Block { func (cb *ChainBuilder) AllExceptGenesis() []*blocks.Block { return slices.Clone(cb.chain[1:]) } + +// GetBlock returns the block with specified hash and height, and a flag +// indicating if it was found. If either argument does not match, it returns +// `nil, false`. +func (cb *ChainBuilder) GetBlock(h common.Hash, num uint64) (*blocks.Block, bool) { + b, ok := cb.byHash[h] + if !ok || b.NumberU64() != num { + return nil, false + } + return b, true +} diff --git a/dummy/dummy.go b/dummy/dummy.go deleted file mode 100644 index 8a2aed5..0000000 --- a/dummy/dummy.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Package dummy provides dummy implementations of interfaces required, but not -// actually used by Ethereum transaction execution. Although these are used in -// production and aren't test doubles, they effectively fill the same role as a -// dummy as described in https://martinfowler.com/bliki/TestDouble.html. -package dummy - -import ( - "github.com/ava-labs/libevm/common" - "github.com/ava-labs/libevm/consensus" - "github.com/ava-labs/libevm/core" - "github.com/ava-labs/libevm/core/types" -) - -// ChainContext returns a dummy that returns [Engine] when its Engine() method -// is called, and panics when its GetHeader() method is called. -func ChainContext() core.ChainContext { - return chainContext{} -} - -// Engine returns a dummy that panics when its Author() method is called. -func Engine() consensus.Engine { - return engine{} -} - -type ( - chainContext struct{} - engine struct{ consensus.Engine } -) - -func (chainContext) Engine() consensus.Engine { return engine{} } -func (chainContext) GetHeader(common.Hash, uint64) *types.Header { panic("unimplemented") } -func (engine) Author(h *types.Header) (common.Address, error) { panic("unimplemented") } diff --git a/saexec/context.go b/saexec/context.go new file mode 100644 index 0000000..ab462da --- /dev/null +++ b/saexec/context.go @@ -0,0 +1,40 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saexec + +import ( + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/consensus" + "github.com/ava-labs/libevm/core" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/strevm/blocks" +) + +// A BlockSource returns a block that matches both a hash and number, or nil +// if not found. +type BlockSource func(hash common.Hash, number uint64) *blocks.Block + +var _ core.ChainContext = (*chainContext)(nil) + +type chainContext struct { + BlockSource + log logging.Logger +} + +func (c *chainContext) GetHeader(h common.Hash, n uint64) *types.Header { + b := c.BlockSource(h, n) + if b == nil { + return nil + } + return b.Header() +} + +func (c *chainContext) Engine() consensus.Engine { + // This is serious enough that it needs to be investigated immediately, but + // not enough to be fatal. It will also cause tests to fail if ever called, + // so we can catch it early. + c.log.Error("ChainContext.Engine() called unexpectedly") + return struct{ consensus.Engine }{} +} diff --git a/saexec/execution.go b/saexec/execution.go index 0c4c406..fa7f91a 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -22,7 +22,6 @@ import ( "go.uber.org/zap" "github.com/ava-labs/strevm/blocks" - "github.com/ava-labs/strevm/dummy" "github.com/ava-labs/strevm/hook" ) @@ -107,7 +106,7 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { receipt, err := core.ApplyTransaction( e.chainConfig, - dummy.ChainContext(), + e.chainContext, &header.Coinbase, &gasPool, scratch.statedb, diff --git a/saexec/saexec.go b/saexec/saexec.go index a417641..90a4439 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -39,9 +39,10 @@ type Executor struct { chainEvents event.FeedOf[core.ChainEvent] logEvents event.FeedOf[[]*types.Log] - chainConfig *params.ChainConfig - db ethdb.Database - stateCache state.Database + chainContext core.ChainContext + chainConfig *params.ChainConfig + db ethdb.Database + stateCache state.Database // executeScratchSpace MUST NOT be accessed by any methods other than // [Executor.init], [Executor.execute], and [Executor.Close]. executeScratchSpace executionScratchSpace @@ -55,6 +56,7 @@ type Executor struct { // executed block after shutdown and recovery. func New( lastExecuted *blocks.Block, + blockSrc BlockSource, chainConfig *params.ChainConfig, db ethdb.Database, triedbConfig *triedb.Config, @@ -62,14 +64,15 @@ func New( log logging.Logger, ) (*Executor, error) { e := &Executor{ - quit: make(chan struct{}), // closed by [Executor.Close] - done: make(chan struct{}), // closed by [Executor.processQueue] after `quit` is closed - log: log, - hooks: hooks, - queue: make(chan *blocks.Block, 4096), // arbitrarily sized - chainConfig: chainConfig, - db: db, - stateCache: state.NewDatabaseWithConfig(db, triedbConfig), + quit: make(chan struct{}), // closed by [Executor.Close] + done: make(chan struct{}), // closed by [Executor.processQueue] after `quit` is closed + log: log, + hooks: hooks, + queue: make(chan *blocks.Block, 4096), // arbitrarily sized + chainContext: &chainContext{blockSrc, log}, + chainConfig: chainConfig, + db: db, + stateCache: state.NewDatabaseWithConfig(db, triedbConfig), } e.lastExecuted.Store(lastExecuted) if err := e.init(); err != nil { diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 2a4d5c3..7f81fb2 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -9,6 +9,7 @@ import ( "math" "math/big" "math/rand/v2" + "slices" "testing" "github.com/ava-labs/avalanchego/utils/logging" @@ -31,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" + "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/blocks/blockstest" "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/gastime" @@ -78,14 +80,21 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { alloc := saetest.MaxAllocFor(wallet.Addresses()...) genesis := blockstest.NewGenesis(tb, db, config, alloc, blockstest.WithTrieDBConfig(tdbConfig)) - e, err := New(genesis, config, db, tdbConfig, hooks, logger) - require.NoError(tb, err, "New()") - tb.Cleanup(e.Close) - - chain := blockstest.NewChainBuilder(e.LastExecuted()) + chain := blockstest.NewChainBuilder(genesis) chain.SetDefaultOptions(blockstest.WithBlockOptions( blockstest.WithLogger(logger)), ) + src := BlockSource(func(h common.Hash, n uint64) *blocks.Block { + b, ok := chain.GetBlock(h, n) + if !ok { + return nil + } + return b + }) + + e, err := New(genesis, src, config, db, tdbConfig, hooks, logger) + require.NoError(tb, err, "New()") + tb.Cleanup(e.Close) return ctx, SUT{ Executor: e, @@ -502,3 +511,177 @@ func TestGasAccounting(t *testing.T) { }) } } + +func TestContextualOpCodes(t *testing.T) { + ctx, sut := newSUT(t, defaultHooks()) + + chain := sut.chain + for range 5 { + // Historical blocks, required to already be in `chain`, for testing + // BLOCKHASH. + b := chain.NewBlock(t, nil) + require.NoErrorf(t, sut.Enqueue(ctx, b), "Enqueue([empty block])") + } + + // log1 returns contract bytecode that logs the value on the top of the + // stack after executing `pre`. + log1 := func(pre ...vm.OpCode) []vm.OpCode { + return slices.Concat(pre, []vm.OpCode{vm.PUSH0, vm.PUSH0, vm.LOG1}) + } + + bigToHash := func(b *big.Int) common.Hash { + return uint256.MustFromBig(b).Bytes32() + } + + // For specific tests. + const txValueSend = 42 + saveBlockNum := &blockNumSaver{} + + tests := []struct { + name string + code []vm.OpCode + header func(*types.Header) + wantTopic common.Hash + wantTopicFn func() common.Hash // if non-nil, overrides `wantTopic` + }{ + { + name: "BALANCE", + code: log1(vm.ADDRESS, vm.BALANCE), + wantTopic: common.Hash{31: txValueSend}, + }, + { + name: "CALLVALUE", + code: log1(vm.CALLVALUE), + wantTopic: common.Hash{31: txValueSend}, + }, + { + name: "SELFBALANCE", + code: log1(vm.SELFBALANCE), + wantTopic: common.Hash{31: txValueSend}, + }, + { + name: "ORIGIN", + code: log1(vm.ORIGIN), + wantTopic: common.BytesToHash( + sut.wallet.Addresses()[0].Bytes(), + ), + }, + { + name: "BLOCKHASH_genesis", + code: log1(vm.PUSH0, vm.BLOCKHASH), + wantTopic: chain.AllBlocks()[0].Hash(), + }, + { + name: "BLOCKHASH_arbitrary", + code: log1(vm.PUSH1, 3, vm.BLOCKHASH), + wantTopic: chain.AllBlocks()[3].Hash(), + }, + { + name: "NUMBER", + code: log1(vm.NUMBER), + header: saveBlockNum.store, + wantTopicFn: func() common.Hash { + return bigToHash(saveBlockNum.num) + }, + }, + { + name: "COINBASE_arbitrary", + code: log1(vm.COINBASE), + header: func(h *types.Header) { + h.Coinbase = common.Address{17: 0xC0, 18: 0xFF, 19: 0xEE} + }, + wantTopic: common.BytesToHash([]byte{0xC0, 0xFF, 0xEE}), + }, + { + name: "COINBASE_zero", + code: log1(vm.COINBASE), + }, + { + name: "TIMESTAMP", + code: log1(vm.TIMESTAMP), + header: func(h *types.Header) { + h.Time = 0xDECAFBAD + }, + wantTopic: common.BytesToHash([]byte{0xDE, 0xCA, 0xFB, 0xAD}), + }, + { + name: "PREVRANDAO", + code: log1(vm.PREVRANDAO), + }, + { + name: "CHAINID", + code: log1(vm.CHAINID), + wantTopic: bigToHash(sut.ChainConfig().ChainID), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := make([]byte, len(tt.code)) + for i, op := range tt.code { + data[i] = byte(op) + } + tx := sut.wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ + To: nil, // contract creation runs the call data (one sneaky trick blockchain developers don't want you to know) + GasPrice: big.NewInt(1), + Gas: 100e6, + Data: data, + Value: big.NewInt(txValueSend), + }) + + var opts []blockstest.ChainOption + if tt.header != nil { + opts = append(opts, blockstest.WithEthBlockOptions( + blockstest.ModifyHeader(tt.header), + )) + } + + b := sut.chain.NewBlock(t, types.Transactions{tx}, opts...) + require.NoError(t, sut.Enqueue(ctx, b), "Enqueue()") + require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) + require.Lenf(t, b.Receipts(), 1, "%T.Receipts()", b) + + got := b.Receipts()[0] + diffopts := cmp.Options{ + cmpopts.IgnoreFields( + types.Receipt{}, + "Bloom", "ContractAddress", "CumulativeGasUsed", "GasUsed", + ), + cmpopts.IgnoreFields( + types.Log{}, + "Address", + ), + cmputils.BigInts(), + } + wantTopic := tt.wantTopic + if tt.wantTopicFn != nil { + wantTopic = tt.wantTopicFn() + } + want := &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + BlockHash: b.Hash(), + BlockNumber: b.Number(), + TxHash: tx.Hash(), + Logs: []*types.Log{{ + Topics: []common.Hash{wantTopic}, + BlockHash: b.Hash(), + BlockNumber: b.NumberU64(), + TxHash: tx.Hash(), + }}, + } + if diff := cmp.Diff(want, got, diffopts); diff != "" { + t.Errorf("%T diff (-want +got):\n%s", got, diff) + } + }) + } +} + +type blockNumSaver struct { + num *big.Int +} + +var _ = blockstest.ModifyHeader((*blockNumSaver)(nil).store) + +func (e *blockNumSaver) store(h *types.Header) { + e.num = new(big.Int).Set(h.Number) +} From d56614deb122a358ade99cb0adfc9d9d1b683c96 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:30:48 +0000 Subject: [PATCH 18/52] refactor: `defer` unlocking `CondVar.L` Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saetest/saetest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saetest/saetest.go b/saetest/saetest.go index 15856de..e7125c9 100644 --- a/saetest/saetest.go +++ b/saetest/saetest.go @@ -87,8 +87,8 @@ func (c *EventCollector[T]) Unsubscribe() error { // WaitForAtLeast blocks until at least `n` events have been received. func (c *EventCollector[T]) WaitForAtLeast(n int) { c.cond.L.Lock() + defer c.cond.L.Unlock() for len(c.all) < n { c.cond.Wait() } - c.cond.L.Unlock() } From f7bcfb1225775ab945a94d41498a290232d06c6f Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 12:27:35 +0000 Subject: [PATCH 19/52] chore: no points for guessing --- saexec/context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/saexec/context.go b/saexec/context.go index ab462da..8754cec 100644 --- a/saexec/context.go +++ b/saexec/context.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/libevm/consensus" "github.com/ava-labs/libevm/core" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/strevm/blocks" ) From 476e73c20f193a62edda0dfca34988477533e74b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:32:56 +0000 Subject: [PATCH 20/52] refactor: use `zap.Stringer` for block hash Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/execution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saexec/execution.go b/saexec/execution.go index fa7f91a..2c834d3 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -59,7 +59,7 @@ func (e *Executor) processQueue() { logger := e.log.With( zap.Uint64("block_height", block.Height()), zap.Uint64("block_time", block.BuildTime()), - zap.Any("block_hash", block.Hash()), + zap.Stringer("block_hash", block.Hash()), zap.Int("tx_count", len(block.Transactions())), ) From 18ee4b50cf5814e2b08682655c03bd6cefc479cf Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 12:52:45 +0000 Subject: [PATCH 21/52] refactor: move `TimeNotThreadsafe` to test file and un-export --- saexec/saexec.go | 7 ------- saexec/saexec_test.go | 11 +++++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/saexec/saexec.go b/saexec/saexec.go index 90a4439..8feca0d 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -133,10 +133,3 @@ func (e *Executor) StateCache() state.Database { func (e *Executor) LastExecuted() *blocks.Block { return e.lastExecuted.Load() } - -// TimeNotThreadsafe returns a clone of the gas clock that times execution. It -// is only safe to call when all blocks passed to [Executor.Enqueue] -// have been executed, and is only intended for use in tests. -func (e *Executor) TimeNotThreadsafe() *gastime.Time { - return e.gasClock.Clone() -} diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 7f81fb2..af373a7 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -104,6 +104,13 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { } } +// timeNotThreadsafe returns a clone of the gas clock that times execution. It +// is only safe to call when all blocks passed to [Executor.Enqueue] +// have been executed. +func (e *Executor) timeNotThreadsafe() *gastime.Time { + return e.gasClock.Clone() +} + func defaultHooks() *saehookstest.Stub { return &saehookstest.Stub{Target: 1e6} } @@ -476,7 +483,7 @@ func TestGasAccounting(t *testing.T) { for desc, got := range map[string]*gastime.Time{ fmt.Sprintf("%T.ExecutedByGasTime()", b): b.ExecutedByGasTime(), - fmt.Sprintf("%T.TimeNotThreadSafe()", e): e.TimeNotThreadsafe(), + fmt.Sprintf("%T.TimeNotThreadSafe()", e): e.timeNotThreadsafe(), } { opt := proxytime.CmpOpt[gas.Gas](proxytime.IgnoreRateInvariants) if diff := cmp.Diff(step.wantExecutedBy, got.Time, opt); diff != "" { @@ -498,7 +505,7 @@ func TestGasAccounting(t *testing.T) { } t.Run("gas_price", func(t *testing.T) { - tm := e.TimeNotThreadsafe() + tm := e.timeNotThreadsafe() assert.Equalf(t, step.wantExcessAfter, tm.Excess(), "%T.Excess()", tm) assert.Equalf(t, step.wantPriceAfter, tm.Price(), "%T.Price()", tm) From 6feac2aef476a8c350dab571041e681de8e2cb2f Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 13:02:19 +0000 Subject: [PATCH 22/52] refactor: simplify `ChainBuilder.NewBlock` options handling --- blocks/blockstest/chain.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 2a7053b..7ecb2c4 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -45,15 +45,15 @@ func (cb *ChainBuilder) SetDefaultOptions(opts ...ChainOption) { } type chainOptions struct { - ethOpts []EthBlockOption - saeOpts []BlockOption + eth []EthBlockOption + sae []BlockOption } // WithEthBlockOptions wraps the options that [ChainBuilder.NewBlock] propagates // to [NewEthBlock]. func WithEthBlockOptions(opts ...EthBlockOption) ChainOption { return options.Func[chainOptions](func(co *chainOptions) { - co.ethOpts = append(co.ethOpts, opts...) + co.eth = append(co.eth, opts...) }) } @@ -61,26 +61,21 @@ func WithEthBlockOptions(opts ...EthBlockOption) ChainOption { // [NewBlock]. func WithBlockOptions(opts ...BlockOption) ChainOption { return options.Func[chainOptions](func(co *chainOptions) { - co.saeOpts = append(co.saeOpts, opts...) + co.sae = append(co.sae, opts...) }) } -func ethBlockOptions(opts []ChainOption) []EthBlockOption { - return options.ApplyTo(&chainOptions{}, opts...).ethOpts -} - -func blockOptions(opts []ChainOption) []BlockOption { - return options.ApplyTo(&chainOptions{}, opts...).saeOpts -} - // NewBlock constructs and returns a new block in the chain. func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts ...ChainOption) *blocks.Block { tb.Helper() - opts = slices.Concat(cb.defaultOpts, opts) + + allOpts := new(chainOptions) + options.ApplyTo(allOpts, cb.defaultOpts...) + options.ApplyTo(allOpts, opts...) last := cb.Last() - eth := NewEthBlock(last.EthBlock(), txs, ethBlockOptions(opts)...) - b := NewBlock(tb, eth, last, nil, blockOptions(opts)...) // TODO(arr4n) support last-settled blocks + eth := NewEthBlock(last.EthBlock(), txs, allOpts.eth...) + b := NewBlock(tb, eth, last, nil, allOpts.sae...) // TODO(arr4n) support last-settled blocks cb.chain = append(cb.chain, b) cb.byHash[b.Hash()] = b From eb40cdd923f52d8571e2eff3d367e16db2f7344d Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 14:17:06 +0000 Subject: [PATCH 23/52] test: `BASEFEE` op code --- saexec/saexec_test.go | 78 ++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index af373a7..453c451 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -5,6 +5,7 @@ package saexec import ( "context" + "encoding/binary" "fmt" "math" "math/big" @@ -517,6 +518,45 @@ func TestGasAccounting(t *testing.T) { assert.Equalf(t, wantBaseFee, gas.Price(b.BaseFee().Uint64()), "%T.BaseFee().Uint64()", b) }) } + + t.Run("BASEFEE_op_code", func(t *testing.T) { + if t.Failed() { + t.Skip("Chain in unexpected state") + } + + finalPrice := uint64(steps[len(steps)-1].wantPriceAfter) + + tx := wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ + To: nil, // runs call data as a constructor + Gas: 100e6, + GasPrice: new(big.Int).SetUint64(finalPrice), + Data: asBytes(logTopOfStackAfter(vm.BASEFEE)...), + }) + + b := chain.NewBlock(t, types.Transactions{tx}) + require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") + require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) + require.Lenf(t, b.Receipts(), 1, "%T.Receipts()", b) + require.Lenf(t, b.Receipts()[0].Logs, 1, "%T.Receipts()[0].Logs", b) + + got := b.Receipts()[0].Logs[0].Topics[0] + want := common.BytesToHash(binary.BigEndian.AppendUint64(nil, finalPrice)) + assert.Equal(t, want, got) + }) +} + +// logTopOfStackAfter returns contract bytecode that logs the value on the top +// of the stack after executing `pre`. +func logTopOfStackAfter(pre ...vm.OpCode) []vm.OpCode { + return slices.Concat(pre, []vm.OpCode{vm.PUSH0, vm.PUSH0, vm.LOG1}) +} + +func asBytes(ops ...vm.OpCode) []byte { + buf := make([]byte, len(ops)) + for i, op := range ops { + buf[i] = byte(op) + } + return buf } func TestContextualOpCodes(t *testing.T) { @@ -530,12 +570,6 @@ func TestContextualOpCodes(t *testing.T) { require.NoErrorf(t, sut.Enqueue(ctx, b), "Enqueue([empty block])") } - // log1 returns contract bytecode that logs the value on the top of the - // stack after executing `pre`. - log1 := func(pre ...vm.OpCode) []vm.OpCode { - return slices.Concat(pre, []vm.OpCode{vm.PUSH0, vm.PUSH0, vm.LOG1}) - } - bigToHash := func(b *big.Int) common.Hash { return uint256.MustFromBig(b).Bytes32() } @@ -553,39 +587,39 @@ func TestContextualOpCodes(t *testing.T) { }{ { name: "BALANCE", - code: log1(vm.ADDRESS, vm.BALANCE), + code: logTopOfStackAfter(vm.ADDRESS, vm.BALANCE), wantTopic: common.Hash{31: txValueSend}, }, { name: "CALLVALUE", - code: log1(vm.CALLVALUE), + code: logTopOfStackAfter(vm.CALLVALUE), wantTopic: common.Hash{31: txValueSend}, }, { name: "SELFBALANCE", - code: log1(vm.SELFBALANCE), + code: logTopOfStackAfter(vm.SELFBALANCE), wantTopic: common.Hash{31: txValueSend}, }, { name: "ORIGIN", - code: log1(vm.ORIGIN), + code: logTopOfStackAfter(vm.ORIGIN), wantTopic: common.BytesToHash( sut.wallet.Addresses()[0].Bytes(), ), }, { name: "BLOCKHASH_genesis", - code: log1(vm.PUSH0, vm.BLOCKHASH), + code: logTopOfStackAfter(vm.PUSH0, vm.BLOCKHASH), wantTopic: chain.AllBlocks()[0].Hash(), }, { name: "BLOCKHASH_arbitrary", - code: log1(vm.PUSH1, 3, vm.BLOCKHASH), + code: logTopOfStackAfter(vm.PUSH1, 3, vm.BLOCKHASH), wantTopic: chain.AllBlocks()[3].Hash(), }, { name: "NUMBER", - code: log1(vm.NUMBER), + code: logTopOfStackAfter(vm.NUMBER), header: saveBlockNum.store, wantTopicFn: func() common.Hash { return bigToHash(saveBlockNum.num) @@ -593,7 +627,7 @@ func TestContextualOpCodes(t *testing.T) { }, { name: "COINBASE_arbitrary", - code: log1(vm.COINBASE), + code: logTopOfStackAfter(vm.COINBASE), header: func(h *types.Header) { h.Coinbase = common.Address{17: 0xC0, 18: 0xFF, 19: 0xEE} }, @@ -601,11 +635,11 @@ func TestContextualOpCodes(t *testing.T) { }, { name: "COINBASE_zero", - code: log1(vm.COINBASE), + code: logTopOfStackAfter(vm.COINBASE), }, { name: "TIMESTAMP", - code: log1(vm.TIMESTAMP), + code: logTopOfStackAfter(vm.TIMESTAMP), header: func(h *types.Header) { h.Time = 0xDECAFBAD }, @@ -613,26 +647,24 @@ func TestContextualOpCodes(t *testing.T) { }, { name: "PREVRANDAO", - code: log1(vm.PREVRANDAO), + code: logTopOfStackAfter(vm.PREVRANDAO), }, { name: "CHAINID", - code: log1(vm.CHAINID), + code: logTopOfStackAfter(vm.CHAINID), wantTopic: bigToHash(sut.ChainConfig().ChainID), }, + // BASEFEE is tested in [TestGasAccounting] because getting the clock + // excess to a specific value is complicated. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - data := make([]byte, len(tt.code)) - for i, op := range tt.code { - data[i] = byte(op) - } tx := sut.wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ To: nil, // contract creation runs the call data (one sneaky trick blockchain developers don't want you to know) GasPrice: big.NewInt(1), Gas: 100e6, - Data: data, + Data: asBytes(tt.code...), Value: big.NewInt(txValueSend), }) From 9e314c42365ea4d31af2bb0283490e30617bceb5 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 14:25:43 +0000 Subject: [PATCH 24/52] test: additional contextual op codes --- saexec/saexec_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 453c451..f01ab62 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -586,7 +586,7 @@ func TestContextualOpCodes(t *testing.T) { wantTopicFn func() common.Hash // if non-nil, overrides `wantTopic` }{ { - name: "BALANCE", + name: "BALANCE_of_ADDRESS", code: logTopOfStackAfter(vm.ADDRESS, vm.BALANCE), wantTopic: common.Hash{31: txValueSend}, }, @@ -607,6 +607,13 @@ func TestContextualOpCodes(t *testing.T) { sut.wallet.Addresses()[0].Bytes(), ), }, + { + name: "CALLER", + code: logTopOfStackAfter(vm.CALLER), + wantTopic: common.BytesToHash( + sut.wallet.Addresses()[0].Bytes(), + ), + }, { name: "BLOCKHASH_genesis", code: logTopOfStackAfter(vm.PUSH0, vm.BLOCKHASH), @@ -649,6 +656,14 @@ func TestContextualOpCodes(t *testing.T) { name: "PREVRANDAO", code: logTopOfStackAfter(vm.PREVRANDAO), }, + { + name: "GASLIMIT", + code: logTopOfStackAfter(vm.GASLIMIT), + header: func(h *types.Header) { + h.GasLimit = 0xA11CEB0B + }, + wantTopic: common.BytesToHash([]byte{0xA1, 0x1C, 0xEB, 0x0B}), + }, { name: "CHAINID", code: logTopOfStackAfter(vm.CHAINID), From dcccec9da74cf1027b16decd130c02d673135958 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 17:08:15 +0000 Subject: [PATCH 25/52] fix: push-gossiper test --- txgossip/txgossip_test.go | 44 +++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index c4c680f..d2a2434 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -8,7 +8,6 @@ import ( "math/big" "math/rand/v2" "path/filepath" - "runtime" "slices" "testing" "time" @@ -30,6 +29,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" + "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/blocks/blockstest" "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/saetest" @@ -64,15 +64,22 @@ func newSUT(t *testing.T, numAccounts uint) sut { db := rawdb.NewMemoryDatabase() genesis := blockstest.NewGenesis(t, db, config, saetest.MaxAllocFor(wallet.Addresses()...)) + chain := blockstest.NewChainBuilder(genesis) + blockSrc := func(h common.Hash, num uint64) *blocks.Block { + b, ok := chain.GetBlock(h, num) + if !ok { + return nil + } + return b + } - exec, err := saexec.New(genesis, config, db, nil, &hookstest.Stub{Target: 1e6}, logger) + exec, err := saexec.New(genesis, blockSrc, config, db, nil, &hookstest.Stub{Target: 1e6}, logger) require.NoError(t, err, "saexec.New()") t.Cleanup(exec.Close) bloom, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", 1, 1e-9, 1e-9) require.NoError(t, err, "gossip.NewBloomFilter([1 in a billion FP])") - chain := blockstest.NewChainBuilder(genesis) bc := NewBlockChain(exec, func(h common.Hash, n uint64) *types.Block { b, ok := chain.GetBlock(h, n) if !ok { @@ -223,8 +230,8 @@ func TestP2PIntegration(t *testing.T) { gossiper: func(l logging.Logger, sendID ids.NodeID, send *Set, recvID ids.NodeID, recv *Set) (gossip.Gossiper, error) { c := p2ptest.NewClient( t, ctx, - recvID, gossip.NewHandler(l, Marshaller{}, recv, metrics, 0), sendID, gossip.NewHandler(l, Marshaller{}, send, metrics, 0), + recvID, gossip.NewHandler(l, Marshaller{}, recv, metrics, 0), ) return gossip.NewPullGossiper(l, Marshaller{}, recv, c, metrics, 1), nil }, @@ -234,6 +241,7 @@ func TestP2PIntegration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { logger := saetest.NewTBLogger(t, logging.Debug) + ctx = logger.CancelOnError(ctx) sendID := ids.GenerateTestNodeID() recvID := ids.GenerateTestNodeID() @@ -258,13 +266,21 @@ func TestP2PIntegration(t *testing.T) { if push, ok := gossiper.(*gossip.PushGossiper[Transaction]); ok { push.Add(tx) } + require.NoErrorf(t, gossiper.Gossip(ctx), "%T.Gossip()", gossiper) - // Instrumenting the receiving [Set] or intercepting the p2p layer - // is overkill when we can just retry and spin. - for ; !recv.Has(tx.GossipID()); runtime.Gosched() { - require.NoErrorf(t, gossiper.Gossip(ctx), "%T.Gossip()", gossiper) - require.NoErrorf(t, recv.Pool.Sync(), "receiver %T.Sync()", recv.Pool) - } + // The Bloom filter is the deepest in the stack of recipients, going + // [Set.Add] -> [txpool.TxPool] -> [legacypool.LegacyPool] -> + // [core.NewTxsEvent] -> [gossip.BloomFilter]. If it has it then + // everything upstream does too. + require.Eventuallyf( + t, func() bool { + return recv.bloom.Has(tx) + }, + 3*time.Second, 50*time.Millisecond, + "Receiving %T.bloom.Has(tx)", recv.Set, + ) + + assert.True(t, recv.Has(tx.GossipID()), "receiving %T.Has([tx])", recv.Set) want := slices.Collect(send.Iterate) got := slices.Collect(recv.Iterate) @@ -274,14 +290,6 @@ func TestP2PIntegration(t *testing.T) { if diff := cmp.Diff(want, got, opt); diff != "" { t.Errorf("slices.Collect(%T.Iterate) diff (-sender +receiver):\n%s", send.Set, diff) } - - require.Eventuallyf( - t, func() bool { - return recv.bloom.Has(tx) - }, - 2*time.Second, 50*time.Millisecond, - "Receiving %T.bloom.Has(tx)", recv.Set, - ) }) } } From 5b5304c5c45638f0a43e372c3263aef83c77a151 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 17:22:25 +0000 Subject: [PATCH 26/52] chore: placate the linter --- txgossip/txgossip_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index d2a2434..a7dd7b7 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -56,6 +56,7 @@ type sut struct { } func newSUT(t *testing.T, numAccounts uint) sut { + t.Helper() logger := saetest.NewTBLogger(t, logging.Warn) config := params.AllDevChainProtocolChanges From e042828df0dc82d1fd6df27779ee6fc316c27918 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 19:40:06 +0000 Subject: [PATCH 27/52] refactor: exponential backoff for `Enqueue()` wait warning --- saexec/execution.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index 2c834d3..f77e16d 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "math" - "runtime" "time" "github.com/ava-labs/avalanchego/utils/logging" @@ -31,6 +30,7 @@ var errExecutorClosed = errors.New("saexec.Executor closed") // before [blocks.Block.Executed] returns true then there is no guarantee that // the block will be executed. func (e *Executor) Enqueue(ctx context.Context, block *blocks.Block) error { + warnAfter := time.Millisecond for { select { case e.queue <- block: @@ -39,10 +39,14 @@ func (e *Executor) Enqueue(ctx context.Context, block *blocks.Block) error { return errExecutorClosed case <-ctx.Done(): return ctx.Err() - default: + case <-time.After(warnAfter): // If this happens then increase the channel's buffer size. - e.log.Warn("Execution queue buffer too small") - runtime.Gosched() + e.log.Warn( + "Execution queue buffer too small", + zap.Duration("wait", warnAfter), + zap.Uint64("block_height", block.Height()), + ) + warnAfter *= 2 } } } From 894b190de47f67c3517efa2be064f2e470114533 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 19:44:03 +0000 Subject: [PATCH 28/52] doc: permalink to `Escrow.sol` --- saetest/escrow/escrow.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/saetest/escrow/escrow.go b/saetest/escrow/escrow.go index e15e6e9..0f4efd1 100644 --- a/saetest/escrow/escrow.go +++ b/saetest/escrow/escrow.go @@ -6,8 +6,10 @@ // // Copyright 2024 Divergence Tech Ltd. -// Package escrow provides bytecode and helpers for the Escrow.sol contract +// Package escrow provides bytecode and helpers for the [Escrow.sol] contract // deployed to 0x370F21541173E8B773571c135e3b5617d7f38C54 on Ethereum mainnet. +// +// [Escrow.sol]: https://github.com/ARR4N/SWAP2/blob/fe724e87bdc998c3b497c16e35fed354e53dc3e9/src/Escrow.sol package escrow import ( From 88d07286d707ac1dd27769f6532178672fa82c39 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:48:21 +0000 Subject: [PATCH 29/52] refactor: return `consensus.Engine(nil)` Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saexec/context.go b/saexec/context.go index 8754cec..c9af510 100644 --- a/saexec/context.go +++ b/saexec/context.go @@ -37,5 +37,5 @@ func (c *chainContext) Engine() consensus.Engine { // not enough to be fatal. It will also cause tests to fail if ever called, // so we can catch it early. c.log.Error("ChainContext.Engine() called unexpectedly") - return struct{ consensus.Engine }{} + return nil } From f55b3b91b9ec053dbea8c7f88d1126a24dacbecf Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:03:59 +0000 Subject: [PATCH 30/52] refactor: correct parenthesis matching Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/saexec_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index f01ab62..4cd574d 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -83,8 +83,8 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { chain := blockstest.NewChainBuilder(genesis) chain.SetDefaultOptions(blockstest.WithBlockOptions( - blockstest.WithLogger(logger)), - ) + blockstest.WithLogger(logger), + )) src := BlockSource(func(h common.Hash, n uint64) *blocks.Block { b, ok := chain.GetBlock(h, n) if !ok { From 00e5417c9952e514c8e36b5ce044bb554598328a Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 19:50:06 +0000 Subject: [PATCH 31/52] refactor: stop embedding `BlockSource` in `chainContext` --- saexec/context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/saexec/context.go b/saexec/context.go index c9af510..eca9dd5 100644 --- a/saexec/context.go +++ b/saexec/context.go @@ -20,12 +20,12 @@ type BlockSource func(hash common.Hash, number uint64) *blocks.Block var _ core.ChainContext = (*chainContext)(nil) type chainContext struct { - BlockSource - log logging.Logger + blocks BlockSource + log logging.Logger } func (c *chainContext) GetHeader(h common.Hash, n uint64) *types.Header { - b := c.BlockSource(h, n) + b := c.blocks(h, n) if b == nil { return nil } From c1f12b87b5a48a31122299aca83f67502caa3c8b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 19:59:26 +0000 Subject: [PATCH 32/52] fix: `ChainBuilder` uses `sync.Map` for blocks stored by hash --- blocks/blockstest/chain.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 7ecb2c4..9a5a569 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -9,6 +9,7 @@ package blockstest import ( "slices" + "sync" "testing" "github.com/ava-labs/libevm/common" @@ -20,8 +21,8 @@ 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 + blocksByHash sync.Map defaultOpts []ChainOption } @@ -30,8 +31,7 @@ type ChainBuilder struct { // which MUST NOT be nil. func NewChainBuilder(genesis *blocks.Block) *ChainBuilder { return &ChainBuilder{ - chain: []*blocks.Block{genesis}, - byHash: make(map[common.Hash]*blocks.Block), + chain: []*blocks.Block{genesis}, } } @@ -77,9 +77,9 @@ func (cb *ChainBuilder) NewBlock(tb testing.TB, txs []*types.Transaction, opts . eth := NewEthBlock(last.EthBlock(), txs, allOpts.eth...) b := NewBlock(tb, eth, last, nil, allOpts.sae...) // TODO(arr4n) support last-settled blocks cb.chain = append(cb.chain, b) - cb.byHash[b.Hash()] = b + cb.blocksByHash.Store(b.Hash(), b) - return cb.Last() + return b } // Last returns the last block to be built by the builder, which MAY be the @@ -103,7 +103,8 @@ func (cb *ChainBuilder) AllExceptGenesis() []*blocks.Block { // indicating if it was found. If either argument does not match, it returns // `nil, false`. func (cb *ChainBuilder) GetBlock(h common.Hash, num uint64) (*blocks.Block, bool) { - b, ok := cb.byHash[h] + ifc, _ := cb.blocksByHash.Load(h) + b, ok := ifc.(*blocks.Block) if !ok || b.NumberU64() != num { return nil, false } From 4fe05c1a77b7261ddb0b0de05ef05fd66ef2be63 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:06:53 +0000 Subject: [PATCH 33/52] refactor: remove specific type from unindexed `for range` Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/saexec_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 4cd574d..aa2f189 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -124,7 +124,7 @@ func TestExecutionSynchronisation(t *testing.T) { ctx, sut := newSUT(t, defaultHooks()) e, chain := sut.Executor, sut.chain - for range uint64(10) { + for range 10 { b := chain.NewBlock(t, nil) require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") } From 0fafec482c3377dc0c824f93fabba0a9c0e426b2 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:08:25 +0000 Subject: [PATCH 34/52] refactor: determine contract address from `Nonce()` value of tx Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/saexec_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index aa2f189..69801d6 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -261,12 +261,11 @@ func TestExecution(t *testing.T) { want types.Receipts ) deploy := wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ - Nonce: 0, Data: escrow.CreationCode(), GasPrice: big.NewInt(1), Gas: 1e7, }) - contract := crypto.CreateAddress(eoa, 0) + contract := crypto.CreateAddress(eoa, deploy.Nonce()) txs = append(txs, deploy) want = append(want, &types.Receipt{ TxHash: deploy.Hash(), From e64de9c93002a64d5f74724bd56b87ebc001d7b6 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 20:12:14 +0000 Subject: [PATCH 35/52] refactor: use `math.Pow()` for calculating gas price in test --- saexec/saexec_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 69801d6..643cddf 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -445,8 +445,8 @@ func TestGasAccounting(t *testing.T) { numTxs: 30 * gastime.TargetToExcessScaling, // deliberate, see below wantExecutedBy: at(21, 30*gastime.TargetToExcessScaling, 10*gasPerTx), wantExcessAfter: 3 * ((5 * gasPerTx /*T*/) * gastime.TargetToExcessScaling /* == K */), - // Excess is now 3·K so the price is e^3 = 20.09 - wantPriceAfter: 20, + // Excess is now 3·K so the price is e^3 + wantPriceAfter: gas.Price(math.Floor(math.Pow(math.E, 3 /* <----- NB */))), }, { target: 5 * gasPerTx, From beed9cae104a7fa5c0be86b2c2fbf7c37b906c50 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 20:31:37 +0000 Subject: [PATCH 36/52] refactor: remove `executionScratchSpace` and `Executor.init()` --- saexec/execution.go | 39 ++++++++++----------------------- saexec/saexec.go | 53 +++++++++++++++------------------------------ 2 files changed, 29 insertions(+), 63 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index f77e16d..dd0b960 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -12,10 +12,8 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/gas" - "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core" "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" "go.uber.org/zap" @@ -75,11 +73,6 @@ func (e *Executor) processQueue() { } } -type executionScratchSpace struct { - snaps *snapshot.Tree - statedb *state.StateDB -} - func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { logger.Debug("Executing block") @@ -90,10 +83,14 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { return fmt.Errorf("executing blocks out of order: %d then %d", last, curr) } - scratch := &e.executeScratchSpace rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) - if err := hook.BeforeBlock(e.hooks, rules, scratch.statedb, b, e.gasClock); err != nil { + stateDB, err := state.New(b.ParentBlock().PostExecutionStateRoot(), e.stateCache, e.snaps) + if err != nil { + return fmt.Errorf("state.New(%#x, ...): %v", b.ParentBlock().PostExecutionStateRoot(), err) + } + + if err := hook.BeforeBlock(e.hooks, rules, stateDB, b, e.gasClock); err != nil { return fmt.Errorf("before-block hook: %v", err) } perTxClock := e.gasClock.Time.Clone() @@ -106,14 +103,14 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { receipts := make(types.Receipts, len(b.Transactions())) for ti, tx := range b.Transactions() { - scratch.statedb.SetTxContext(tx.Hash(), ti) + stateDB.SetTxContext(tx.Hash(), ti) receipt, err := core.ApplyTransaction( e.chainConfig, e.chainContext, &header.Coinbase, &gasPool, - scratch.statedb, + stateDB, header, tx, (*uint64)(&blockGasConsumed), @@ -151,7 +148,7 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { receipts[ti] = receipt } endTime := time.Now() - hook.AfterBlock(e.hooks, scratch.statedb, b.EthBlock(), e.gasClock, blockGasConsumed, receipts) + hook.AfterBlock(e.hooks, stateDB, b.EthBlock(), e.gasClock, blockGasConsumed, receipts) if e.gasClock.Time.Compare(perTxClock) != 0 { return fmt.Errorf("broken invariant: block-resolution clock @ %s does not match tx-resolution clock @ %s", e.gasClock.String(), perTxClock.String()) } @@ -163,9 +160,9 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { zap.Time("wall_time", endTime), ) - root, err := e.commitState(scratch, b.NumberU64()) + root, err := stateDB.Commit(b.NumberU64(), true) if err != nil { - return err + return fmt.Errorf("%T.Commit() at end of block %d: %w", stateDB, b.NumberU64(), err) } // The strict ordering of the next 3 calls guarantees invariants that MUST // NOT be broken: @@ -180,17 +177,3 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { e.sendPostExecutionEvents(b.EthBlock(), receipts) // (3) return nil } - -func (e *Executor) commitState(scratch *executionScratchSpace, blockNum uint64) (common.Hash, error) { - root, err := scratch.statedb.Commit(blockNum, true) - if err != nil { - return common.Hash{}, fmt.Errorf("%T.Commit() at end of block %d: %w", scratch.statedb, blockNum, err) - } - - db, err := state.New(root, e.stateCache, scratch.snaps) - if err != nil { - return common.Hash{}, err - } - scratch.statedb = db - return root, nil -} diff --git a/saexec/saexec.go b/saexec/saexec.go index 8feca0d..17219fa 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -43,9 +43,9 @@ type Executor struct { chainConfig *params.ChainConfig db ethdb.Database stateCache state.Database - // executeScratchSpace MUST NOT be accessed by any methods other than - // [Executor.init], [Executor.execute], and [Executor.Close]. - executeScratchSpace executionScratchSpace + // snaps MUST NOT be accessed by any methods other than [Executor.execute] + // and [Executor.Close]. + snaps *snapshot.Tree } // New constructs and starts a new [Executor]. Call [Executor.Close] to release @@ -63,60 +63,43 @@ func New( hooks hook.Points, log logging.Logger, ) (*Executor, error) { + cache := state.NewDatabaseWithConfig(db, triedbConfig) + snapConf := snapshot.Config{ + CacheSize: 128, // MB + AsyncBuild: true, + } + snaps, err := snapshot.New(snapConf, db, cache.TrieDB(), lastExecuted.PostExecutionStateRoot()) + if err != nil { + return nil, err + } + e := &Executor{ quit: make(chan struct{}), // closed by [Executor.Close] done: make(chan struct{}), // closed by [Executor.processQueue] after `quit` is closed log: log, hooks: hooks, + gasClock: lastExecuted.ExecutedByGasTime().Clone(), queue: make(chan *blocks.Block, 4096), // arbitrarily sized chainContext: &chainContext{blockSrc, log}, chainConfig: chainConfig, db: db, - stateCache: state.NewDatabaseWithConfig(db, triedbConfig), + stateCache: cache, + snaps: snaps, } e.lastExecuted.Store(lastExecuted) - if err := e.init(); err != nil { - return nil, err - } go e.processQueue() return e, nil } -func (e *Executor) init() error { - last := e.lastExecuted.Load() - e.gasClock = last.ExecutedByGasTime().Clone() - - root := last.PostExecutionStateRoot() - snapConf := snapshot.Config{ - CacheSize: 128, // MB - AsyncBuild: true, - } - snaps, err := snapshot.New(snapConf, e.db, e.stateCache.TrieDB(), root) - if err != nil { - return err - } - statedb, err := state.New(root, e.stateCache, snaps) - if err != nil { - return err - } - - e.executeScratchSpace = executionScratchSpace{ - snaps: snaps, - statedb: statedb, - } - return nil -} - // Close shuts down the [Executor], waits for the currently executing block // to complete, and then releases all resources. func (e *Executor) Close() { close(e.quit) <-e.done - snaps := e.executeScratchSpace.snaps - snaps.Disable() - snaps.Release() + e.snaps.Disable() + e.snaps.Release() } // ChainConfig returns the config originally passed to [New]. From c6d1ef2cc5282467eee56f2779f35fbb39dd879c Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 18 Nov 2025 20:53:04 +0000 Subject: [PATCH 37/52] refactor: make gas clock internal variable in `Executor.execute()` --- saexec/execution.go | 25 +++++++++++++++---------- saexec/saexec.go | 3 --- saexec/saexec_test.go | 28 +++++++--------------------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index dd0b960..18d6c19 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -85,18 +85,23 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) - stateDB, err := state.New(b.ParentBlock().PostExecutionStateRoot(), e.stateCache, e.snaps) + // Since `b` hasn't been executed, it definitely hasn't been settled, so we + // are guaranteed to have a non-nil parent available. + parent := b.ParentBlock() + + stateDB, err := state.New(parent.PostExecutionStateRoot(), e.stateCache, e.snaps) if err != nil { - return fmt.Errorf("state.New(%#x, ...): %v", b.ParentBlock().PostExecutionStateRoot(), err) + return fmt.Errorf("state.New(%#x, ...): %v", parent.PostExecutionStateRoot(), err) } + gasClock := parent.ExecutedByGasTime().Clone() - if err := hook.BeforeBlock(e.hooks, rules, stateDB, b, e.gasClock); err != nil { + if err := hook.BeforeBlock(e.hooks, rules, stateDB, b, gasClock); err != nil { return fmt.Errorf("before-block hook: %v", err) } - perTxClock := e.gasClock.Time.Clone() + perTxClock := gasClock.Time.Clone() header := types.CopyHeader(b.Header()) - header.BaseFee = e.gasClock.BaseFee().ToBig() + header.BaseFee = gasClock.BaseFee().ToBig() gasPool := core.GasPool(math.MaxUint64) // required by geth but irrelevant so max it out var blockGasConsumed gas.Gas @@ -148,15 +153,15 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { receipts[ti] = receipt } endTime := time.Now() - hook.AfterBlock(e.hooks, stateDB, b.EthBlock(), e.gasClock, blockGasConsumed, receipts) - if e.gasClock.Time.Compare(perTxClock) != 0 { - return fmt.Errorf("broken invariant: block-resolution clock @ %s does not match tx-resolution clock @ %s", e.gasClock.String(), perTxClock.String()) + hook.AfterBlock(e.hooks, stateDB, b.EthBlock(), gasClock, blockGasConsumed, receipts) + if gasClock.Time.Compare(perTxClock) != 0 { + return fmt.Errorf("broken invariant: block-resolution clock @ %s does not match tx-resolution clock @ %s", gasClock.String(), perTxClock.String()) } logger.Debug( "Block execution complete", zap.Uint64("gas_consumed", uint64(blockGasConsumed)), - zap.Time("gas_time", e.gasClock.AsTime()), + zap.Time("gas_time", gasClock.AsTime()), zap.Time("wall_time", endTime), ) @@ -170,7 +175,7 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { // 1. [blocks.Block.MarkExecuted] guarantees disk then in-memory changes. // 2. Internal indicator of last executed MUST follow in-memory change. // 3. External indicator of last executed MUST follow internal indicator. - if err := b.MarkExecuted(e.db, e.gasClock.Clone(), endTime, header.BaseFee, receipts, root); err != nil { + if err := b.MarkExecuted(e.db, gasClock.Clone(), endTime, header.BaseFee, receipts, root); err != nil { return err } e.lastExecuted.Store(b) // (2) diff --git a/saexec/saexec.go b/saexec/saexec.go index 17219fa..5df915f 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -21,7 +21,6 @@ import ( "github.com/ava-labs/libevm/triedb" "github.com/ava-labs/strevm/blocks" - "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/hook" ) @@ -31,7 +30,6 @@ type Executor struct { log logging.Logger hooks hook.Points - gasClock *gastime.Time queue chan *blocks.Block lastExecuted atomic.Pointer[blocks.Block] @@ -78,7 +76,6 @@ func New( done: make(chan struct{}), // closed by [Executor.processQueue] after `quit` is closed log: log, hooks: hooks, - gasClock: lastExecuted.ExecutedByGasTime().Clone(), queue: make(chan *blocks.Block, 4096), // arbitrarily sized chainContext: &chainContext{blockSrc, log}, chainConfig: chainConfig, diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 643cddf..0f7daa4 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -6,7 +6,6 @@ package saexec import ( "context" "encoding/binary" - "fmt" "math" "math/big" "math/rand/v2" @@ -105,13 +104,6 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { } } -// timeNotThreadsafe returns a clone of the gas clock that times execution. It -// is only safe to call when all blocks passed to [Executor.Enqueue] -// have been executed. -func (e *Executor) timeNotThreadsafe() *gastime.Time { - return e.gasClock.Clone() -} - func defaultHooks() *saehookstest.Stub { return &saehookstest.Stub{Target: 1e6} } @@ -481,14 +473,9 @@ func TestGasAccounting(t *testing.T) { require.NoError(t, e.Enqueue(ctx, b), "Enqueue()") require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) - for desc, got := range map[string]*gastime.Time{ - fmt.Sprintf("%T.ExecutedByGasTime()", b): b.ExecutedByGasTime(), - fmt.Sprintf("%T.TimeNotThreadSafe()", e): e.timeNotThreadsafe(), - } { - opt := proxytime.CmpOpt[gas.Gas](proxytime.IgnoreRateInvariants) - if diff := cmp.Diff(step.wantExecutedBy, got.Time, opt); diff != "" { - t.Errorf("%s diff (-want +got):\n%s", desc, diff) - } + opt := proxytime.CmpOpt[gas.Gas](proxytime.IgnoreRateInvariants) + if diff := cmp.Diff(step.wantExecutedBy, b.ExecutedByGasTime().Time, opt); diff != "" { + t.Errorf("%T.ExecutedByGasTime().Time diff (-want +got):\n%s", b, diff) } t.Run("CumulativeGasUsed", func(t *testing.T) { @@ -505,7 +492,7 @@ func TestGasAccounting(t *testing.T) { } t.Run("gas_price", func(t *testing.T) { - tm := e.timeNotThreadsafe() + tm := b.ExecutedByGasTime().Clone() assert.Equalf(t, step.wantExcessAfter, tm.Excess(), "%T.Excess()", tm) assert.Equalf(t, step.wantPriceAfter, tm.Price(), "%T.Price()", tm) @@ -517,12 +504,11 @@ func TestGasAccounting(t *testing.T) { assert.Equalf(t, wantBaseFee, gas.Price(b.BaseFee().Uint64()), "%T.BaseFee().Uint64()", b) }) } + if t.Failed() { + t.Skip("Chain in unexpected state") + } t.Run("BASEFEE_op_code", func(t *testing.T) { - if t.Failed() { - t.Skip("Chain in unexpected state") - } - finalPrice := uint64(steps[len(steps)-1].wantPriceAfter) tx := wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ From 48342cd02bd87397f473d4f4539c83ed8d54c121 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 09:24:59 +0000 Subject: [PATCH 38/52] fix: `ApplyTransaction` errors are `FATAL` --- saexec/execution.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index 18d6c19..ce6f4f3 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -33,10 +33,16 @@ func (e *Executor) Enqueue(ctx context.Context, block *blocks.Block) error { select { case e.queue <- block: return nil - case <-e.quit: - return errExecutorClosed + case <-ctx.Done(): return ctx.Err() + + case <-e.quit: + return errExecutorClosed + case <-e.done: + // `e.done` can also close due to [Executor.execute] errors. + return errExecutorClosed + case <-time.After(warnAfter): // If this happens then increase the channel's buffer size. e.log.Warn( @@ -66,7 +72,11 @@ func (e *Executor) processQueue() { ) if err := e.execute(block, logger); err != nil { - logger.Error("Block execution failed", zap.Error(err)) + logger.Fatal( + "Block execution failed; see emergency playbook", + zap.Error(err), + zap.String("playbook", "https://github.com/ava-labs/strevm/issues/28"), + ) return } } @@ -122,15 +132,14 @@ func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { vm.Config{}, ) if err != nil { - // This almost certainly means that the worst-case block inclusion - // has a bug. - logger.Error( - "Transaction execution errored (not reverted)", + logger.Fatal( + "Transaction execution errored (not reverted); see emergency playbook", zap.Int("tx_index", ti), zap.Stringer("tx_hash", tx.Hash()), + zap.String("playbook", "https://github.com/ava-labs/strevm/issues/28"), zap.Error(err), ) - continue + return err } perTxClock.Tick(gas.Gas(receipt.GasUsed)) From caf324246895c92354b7b65ca85c3ccae4122cf4 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 10:24:10 +0000 Subject: [PATCH 39/52] feat: `snapshot.Tree.Cap(..., 0)` on shutdown to persist top layer --- saexec/saexec.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/saexec/saexec.go b/saexec/saexec.go index 5df915f..1acdaae 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/libevm/event" "github.com/ava-labs/libevm/params" "github.com/ava-labs/libevm/triedb" + "go.uber.org/zap" "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/hook" @@ -95,6 +96,18 @@ func (e *Executor) Close() { close(e.quit) <-e.done + // We don't use [snapshot.Tree.Journal] because re-orgs are impossible under + // SAE so we don't mind flattening all snapshot layers to disk. Note that + // calling `Cap([disk root], 0)` returns an error when it's actually a + // no-op, so we ignore it. + root := e.LastExecuted().PostExecutionStateRoot() + if err := e.snaps.Cap(root, 0); err != nil && root != e.snaps.DiskRoot() { + e.log.Warn( + "snapshot.Tree.Cap([last post-execution state root], 0)", + zap.Error(err), + ) + } + e.snaps.Disable() e.snaps.Release() } From 0789c803a0a014561497e2f321429948de59b5c5 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 11:40:43 +0000 Subject: [PATCH 40/52] refactor: abstract `blocks.Source` --- blocks/block.go | 25 +++++++++++++++++++++++++ blocks/blockstest/chain.go | 2 ++ saexec/context.go | 12 ++---------- saexec/saexec.go | 2 +- saexec/saexec_test.go | 10 +--------- txgossip/blockchain.go | 11 ++++------- txgossip/txgossip_test.go | 19 ++----------------- 7 files changed, 37 insertions(+), 44 deletions(-) diff --git a/blocks/block.go b/blocks/block.go index d4b2760..b82874f 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -13,6 +13,7 @@ import ( "sync/atomic" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "go.uber.org/zap" ) @@ -114,3 +115,27 @@ func (b *Block) CopyAncestorsFrom(c *Block) error { a := c.ancestry.Load() return b.setAncestors(a.parent, a.lastSettled) } + +// A Source returns a [Block] that matches both a hash and number, and a boolean +// indicating if such a block was found. +type Source func(hash common.Hash, number uint64) (*Block, bool) + +// EthBlock returns the [types.Block] with the given hash and number, or nil if +// not found. +func (s Source) EthBlock(h common.Hash, n uint64) *types.Block { + b, ok := s(h, n) + if !ok { + return nil + } + return b.EthBlock() +} + +// Header returns the [types.Header] with the given hash and number, or nil if +// not found. +func (s Source) Header(h common.Hash, n uint64) *types.Header { + b, ok := s(h, n) + if !ok { + return nil + } + return b.Header() +} diff --git a/blocks/blockstest/chain.go b/blocks/blockstest/chain.go index 9a5a569..7622d4f 100644 --- a/blocks/blockstest/chain.go +++ b/blocks/blockstest/chain.go @@ -99,6 +99,8 @@ func (cb *ChainBuilder) AllExceptGenesis() []*blocks.Block { return slices.Clone(cb.chain[1:]) } +var _ blocks.Source = (*ChainBuilder)(nil).GetBlock + // GetBlock returns the block with specified hash and height, and a flag // indicating if it was found. If either argument does not match, it returns // `nil, false`. diff --git a/saexec/context.go b/saexec/context.go index eca9dd5..b8192f2 100644 --- a/saexec/context.go +++ b/saexec/context.go @@ -13,23 +13,15 @@ import ( "github.com/ava-labs/strevm/blocks" ) -// A BlockSource returns a block that matches both a hash and number, or nil -// if not found. -type BlockSource func(hash common.Hash, number uint64) *blocks.Block - var _ core.ChainContext = (*chainContext)(nil) type chainContext struct { - blocks BlockSource + blocks blocks.Source log logging.Logger } func (c *chainContext) GetHeader(h common.Hash, n uint64) *types.Header { - b := c.blocks(h, n) - if b == nil { - return nil - } - return b.Header() + return c.blocks.Header(h, n) } func (c *chainContext) Engine() consensus.Engine { diff --git a/saexec/saexec.go b/saexec/saexec.go index 83caa5c..ff7c578 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -56,7 +56,7 @@ type Executor struct { // executed block after shutdown and recovery. func New( lastExecuted *blocks.Block, - blockSrc BlockSource, + blockSrc blocks.Source, chainConfig *params.ChainConfig, db ethdb.Database, triedbConfig *triedb.Config, diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 0f7daa4..7db221d 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -32,7 +32,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" - "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/blocks/blockstest" "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/gastime" @@ -84,15 +83,8 @@ func newSUT(tb testing.TB, hooks hook.Points) (context.Context, SUT) { chain.SetDefaultOptions(blockstest.WithBlockOptions( blockstest.WithLogger(logger), )) - src := BlockSource(func(h common.Hash, n uint64) *blocks.Block { - b, ok := chain.GetBlock(h, n) - if !ok { - return nil - } - return b - }) - e, err := New(genesis, src, config, db, tdbConfig, hooks, logger) + e, err := New(genesis, chain.GetBlock, config, db, tdbConfig, hooks, logger) require.NoError(tb, err, "New()") tb.Cleanup(e.Close) diff --git a/txgossip/blockchain.go b/txgossip/blockchain.go index 45507a6..2a5a408 100644 --- a/txgossip/blockchain.go +++ b/txgossip/blockchain.go @@ -13,13 +13,10 @@ import ( "github.com/ava-labs/libevm/event" "github.com/ava-labs/libevm/params" + "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/saexec" ) -// A BlockSource returns a block that matches both a hash and number, or nil -// if not found. -type BlockSource func(common.Hash, uint64) *types.Block - // A BlockChain is the union of [txpool.BlockChain] and [legacypool.BlockChain]. type BlockChain interface { txpool.BlockChain @@ -28,7 +25,7 @@ type BlockChain interface { // NewBlockChain wraps an [saexec.Executor] to be compatible with a // non-blob-transaction mempool. -func NewBlockChain(exec *saexec.Executor, blocks BlockSource) BlockChain { +func NewBlockChain(exec *saexec.Executor, blocks blocks.Source) BlockChain { return &blockchain{ exec: exec, blocks: blocks, @@ -37,7 +34,7 @@ func NewBlockChain(exec *saexec.Executor, blocks BlockSource) BlockChain { type blockchain struct { exec *saexec.Executor - blocks BlockSource + blocks blocks.Source } func (bc *blockchain) Config() *params.ChainConfig { @@ -49,7 +46,7 @@ func (bc *blockchain) CurrentBlock() *types.Header { } func (bc *blockchain) GetBlock(hash common.Hash, number uint64) *types.Block { - return bc.blocks(hash, number) + return bc.blocks.EthBlock(hash, number) } func (bc *blockchain) StateAt(root common.Hash) (*state.StateDB, error) { diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index a7dd7b7..9ec6db9 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -29,7 +29,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" - "github.com/ava-labs/strevm/blocks" "github.com/ava-labs/strevm/blocks/blockstest" "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/saetest" @@ -66,29 +65,15 @@ func newSUT(t *testing.T, numAccounts uint) sut { db := rawdb.NewMemoryDatabase() genesis := blockstest.NewGenesis(t, db, config, saetest.MaxAllocFor(wallet.Addresses()...)) chain := blockstest.NewChainBuilder(genesis) - blockSrc := func(h common.Hash, num uint64) *blocks.Block { - b, ok := chain.GetBlock(h, num) - if !ok { - return nil - } - return b - } - exec, err := saexec.New(genesis, blockSrc, config, db, nil, &hookstest.Stub{Target: 1e6}, logger) + exec, err := saexec.New(genesis, chain.GetBlock, config, db, nil, &hookstest.Stub{Target: 1e6}, logger) require.NoError(t, err, "saexec.New()") t.Cleanup(exec.Close) bloom, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", 1, 1e-9, 1e-9) require.NoError(t, err, "gossip.NewBloomFilter([1 in a billion FP])") - bc := NewBlockChain(exec, func(h common.Hash, n uint64) *types.Block { - b, ok := chain.GetBlock(h, n) - if !ok { - return nil - } - return b.EthBlock() - }) - + bc := NewBlockChain(exec, chain.GetBlock) set, cleanup := NewSet(logger, newTxPool(t, bc), bloom, 1) t.Cleanup(func() { require.NoError(t, cleanup(), "cleanup function returned by NewSet()") From 15ea1943f263a724401d4f6cc296a2da662a408e Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 11:49:10 +0000 Subject: [PATCH 41/52] doc: improved comments / error messages in `Executor` integration test --- txgossip/txgossip_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 9ec6db9..67a2f30 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -144,11 +144,11 @@ func TestExecutorIntegration(t *testing.T) { switch { case last == nil: case tx.Sender == last.Sender: - require.Equal(t, last.Tx.Nonce()+1, tx.Tx.Nonce()) + require.Equal(t, last.Tx.Nonce()+1, tx.Tx.Nonce(), "incrementing nonce for same sender") case tx.GasTipCap.Eq(last.GasTipCap): - require.True(t, last.Time.Before(tx.Time)) + require.True(t, last.Time.Before(tx.Time), "equal gas tips ordered by first seen") default: - require.GreaterOrEqual(t, last.GasTipCap.Uint64(), tx.GasTipCap.Uint64()) + require.Greater(t, last.GasTipCap.Uint64(), tx.GasTipCap.Uint64(), "larger gas tips first") } }) } @@ -170,9 +170,9 @@ func TestExecutorIntegration(t *testing.T) { ) t.Run("block_execution", func(t *testing.T) { - // Although not strictly necessary, this is a secondary check for the - // maintenance of nonce ordering. It is more reliable than our manual - // confirmation earlier, so worth the 3 lines. + // The above test for nonce ordering only runs if the same sender has 2 + // consecutive transactions. Successful execution demonstrates correct + // nonce ordering across the board. require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) for i, r := range b.Receipts() { assert.Equalf(t, types.ReceiptStatusSuccessful, r.Status, "%T[%d].Status", r, i) From 97b474a0f96955a0e4316bcd291158d141c264ba Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 11:53:40 +0000 Subject: [PATCH 42/52] refactor: abstract `cmputils.TransactionsByHash()` --- cmputils/types.go | 8 ++++++++ txgossip/txgossip_test.go | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cmputils/types.go b/cmputils/types.go index d82ae07..1f3cc8d 100644 --- a/cmputils/types.go +++ b/cmputils/types.go @@ -26,6 +26,14 @@ func BlocksByHash() cmp.Option { }) } +// TransactionsByHash returns a [cmp.Comparer] for [types.Transaction] pointers, +// equating them by hash alone. +func TransactionsByHash() cmp.Option { + return ComparerWithNilCheck(func(t, u *types.Transaction) bool { + return t.Hash() == u.Hash() + }) +} + // ReceiptsByTxHash returns a [cmp.Comparer] for [types.Receipt] pointers, // equating them by transaction hash alone. func ReceiptsByTxHash() cmp.Option { diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 67a2f30..f970ff6 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -30,6 +30,7 @@ import ( "go.uber.org/goleak" "github.com/ava-labs/strevm/blocks/blockstest" + "github.com/ava-labs/strevm/cmputils" "github.com/ava-labs/strevm/hook/hookstest" "github.com/ava-labs/strevm/saetest" "github.com/ava-labs/strevm/saexec" @@ -248,7 +249,7 @@ func TestP2PIntegration(t *testing.T) { require.NoErrorf(t, send.Pool.Sync(), "sender %T.Sync()", send.Pool) gossiper, err := tt.gossiper(logger, sendID, send.Set, recvID, recv.Set) - require.NoError(t, err) + require.NoError(t, err, "Bad test setup: gossiper creation failed") if push, ok := gossiper.(*gossip.PushGossiper[Transaction]); ok { push.Add(tx) } @@ -270,10 +271,7 @@ func TestP2PIntegration(t *testing.T) { want := slices.Collect(send.Iterate) got := slices.Collect(recv.Iterate) - opt := cmp.Comparer(func(a, b Transaction) bool { - return a.Hash() == b.Hash() - }) - if diff := cmp.Diff(want, got, opt); diff != "" { + if diff := cmp.Diff(want, got, cmputils.TransactionsByHash()); diff != "" { t.Errorf("slices.Collect(%T.Iterate) diff (-sender +receiver):\n%s", send.Set, diff) } }) From 0e3a778d9c9a39fd3f953977142cfcd8b0468ee0 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:33:12 +0000 Subject: [PATCH 43/52] refactor: review suggestions Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/saexec.go | 14 ++++++++------ saexec/saexec_test.go | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/saexec/saexec.go b/saexec/saexec.go index 1acdaae..879f2d3 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -100,12 +100,14 @@ func (e *Executor) Close() { // SAE so we don't mind flattening all snapshot layers to disk. Note that // calling `Cap([disk root], 0)` returns an error when it's actually a // no-op, so we ignore it. - root := e.LastExecuted().PostExecutionStateRoot() - if err := e.snaps.Cap(root, 0); err != nil && root != e.snaps.DiskRoot() { - e.log.Warn( - "snapshot.Tree.Cap([last post-execution state root], 0)", - zap.Error(err), - ) + if root := e.LastExecuted().PostExecutionStateRoot(); root != e.snaps.DiskRoot() { + if err := e.snaps.Cap(root, 0); err != nil { + e.log.Warn( + "snapshot.Tree.Cap([last post-execution state root], 0)", + zap.Stringer("root", root), + zap.Error(err), + ) + } } e.snaps.Disable() diff --git a/saexec/saexec_test.go b/saexec/saexec_test.go index 0f7daa4..617c7b5 100644 --- a/saexec/saexec_test.go +++ b/saexec/saexec_test.go @@ -505,7 +505,7 @@ func TestGasAccounting(t *testing.T) { }) } if t.Failed() { - t.Skip("Chain in unexpected state") + t.Fatal("Chain in unexpected state") } t.Run("BASEFEE_op_code", func(t *testing.T) { From 06256a9ce55c76b3d4402c98fc82a0bfcf3d74cc Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:33:39 +0000 Subject: [PATCH 44/52] refactor!: don't disable snapshot generation on shutdown Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- saexec/saexec.go | 1 - 1 file changed, 1 deletion(-) diff --git a/saexec/saexec.go b/saexec/saexec.go index 879f2d3..a07deb9 100644 --- a/saexec/saexec.go +++ b/saexec/saexec.go @@ -110,7 +110,6 @@ func (e *Executor) Close() { } } - e.snaps.Disable() e.snaps.Release() } From f41f1a7de9f03909fad255cbb035e331160b414b Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 17:41:05 +0000 Subject: [PATCH 45/52] refactor: base block-ordering assertion on parent hash --- saexec/execution.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/saexec/execution.go b/saexec/execution.go index ce6f4f3..68df2b7 100644 --- a/saexec/execution.go +++ b/saexec/execution.go @@ -86,25 +86,23 @@ func (e *Executor) processQueue() { func (e *Executor) execute(b *blocks.Block, logger logging.Logger) error { logger.Debug("Executing block") + // Since `b` hasn't been executed, it definitely hasn't been settled, so we + // are guaranteed to have a non-nil parent available. + parent := b.ParentBlock() // If the VM were to encounter an error after enqueuing the block, we would // receive the same block twice for execution should consensus retry // acceptance. - if last, curr := e.lastExecuted.Load().Height(), b.Height(); curr != last+1 { - return fmt.Errorf("executing blocks out of order: %d then %d", last, curr) + if last := e.lastExecuted.Load().Hash(); last != parent.Hash() { + return fmt.Errorf("executing block built on parent %#x when last executed %#x", parent.Hash(), last) } - rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) - - // Since `b` hasn't been executed, it definitely hasn't been settled, so we - // are guaranteed to have a non-nil parent available. - parent := b.ParentBlock() - stateDB, err := state.New(parent.PostExecutionStateRoot(), e.stateCache, e.snaps) if err != nil { return fmt.Errorf("state.New(%#x, ...): %v", parent.PostExecutionStateRoot(), err) } - gasClock := parent.ExecutedByGasTime().Clone() + rules := e.chainConfig.Rules(b.Number(), true /*isMerge*/, b.BuildTime()) + gasClock := parent.ExecutedByGasTime().Clone() if err := hook.BeforeBlock(e.hooks, rules, stateDB, b, gasClock); err != nil { return fmt.Errorf("before-block hook: %v", err) } From 7ed61a4646be383c7c048ae8128fa9299b8ae319 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 19 Nov 2025 20:34:02 +0000 Subject: [PATCH 46/52] build: revert `go.mod` changes --- go.mod | 71 +++++++++++++-------------- go.sum | 150 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/go.mod b/go.mod index 433bceb..ec80283 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f github.com/google/go-cmp v0.7.0 github.com/holiman/uint256 v1.2.4 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.10.0 go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) @@ -15,81 +15,82 @@ 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.20.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect - github.com/cenkalti/backoff/v5 v5.0.2 // 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 github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/gnark-crypto v0.18.1 // indirect + 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 v1.1.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 v1.0.0 // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/getsentry/sentry-go v0.35.0 // 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/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/klauspost/compress v1.18.0 // 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 github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect 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 - go.opentelemetry.io/auto/sdk v1.1.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 ) require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect - github.com/StephenButtolph/canoto v0.17.3 + github.com/StephenButtolph/canoto v0.17.1 github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/rpc v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mr-tron/base58 v1.2.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.0 - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/supranational/blst v0.3.14 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + 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.43.0 // indirect @@ -98,10 +99,10 @@ require ( 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-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // 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 + google.golang.org/grpc v1.66.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 39485a8..c3389b7 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/StephenButtolph/canoto v0.17.3 h1:lvsnYD4b96vD1knnmp1xCmZqfYpY/jSeRozGdOfdvGI= -github.com/StephenButtolph/canoto v0.17.3/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= +github.com/StephenButtolph/canoto v0.17.1 h1:WnN5czIHHALq7pwc+Z2F1sCsKJCDhxlq0zL0YK1etHc= +github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= 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= @@ -26,16 +26,14 @@ github.com/ava-labs/libevm v1.13.15-0.20251112182915-1ec8741af98f/go.mod h1:DqSo 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= -github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= -github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= -github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +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= @@ -60,22 +58,23 @@ github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI= -github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= -github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= @@ -108,8 +107,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x 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= -github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY= -github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= @@ -117,8 +116,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -139,10 +138,13 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -171,6 +173,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= 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= @@ -180,8 +183,8 @@ github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36j github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -217,8 +220,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -232,8 +235,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= -github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -247,10 +250,15 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -258,8 +266,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -288,25 +294,24 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -322,12 +327,12 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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= @@ -338,8 +343,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= @@ -375,26 +380,22 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 h1:H2JFgRcGiyHg7H7bwcwaQJYrNFqCqrbTQ8K4p1OvDu8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0/go.mod h1:WfCWp1bGoYK8MeULtI15MmQVczfR+bFkk0DF3h06QmQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= @@ -452,6 +453,7 @@ 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= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -539,18 +541,18 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -562,8 +564,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -591,3 +593,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From ef30b88d909f862e21c5f873fab4067ad8f38992 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 10:27:43 +0000 Subject: [PATCH 47/52] refactor: `Close()` method on `Set()` --- txgossip/txgossip.go | 41 +++++++++++++++++++++++++-------------- txgossip/txgossip_test.go | 6 ++++-- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go index 8a9b5aa..7998343 100644 --- a/txgossip/txgossip.go +++ b/txgossip/txgossip.go @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/libevm/core" "github.com/ava-labs/libevm/core/txpool" "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/event" "github.com/ava-labs/libevm/rlp" "go.uber.org/zap" ) @@ -61,26 +62,23 @@ func (Marshaller) UnmarshalGossip(buf []byte) (Transaction, error) { type Set struct { Pool *txpool.TxPool bloom *gossip.BloomFilter + + txSub event.Subscription + bloomDone <-chan error } -// NewSet returns a new [gossip.Set] and a cleanup function that MUST be called -// to release resources. -func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter, targetBloomElements int) (*Set, func() error) { +// NewSet returns a new [gossip.Set]. [Set.Close] MUST be called to release +// resources. +func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter, targetBloomElements int) *Set { txs := make(chan core.NewTxsEvent) sub := pool.SubscribeTransactions(txs, false) - done := make(chan struct{}) + done := make(chan error, 1) go maintainBloomFilter(logger, bloom, txs, sub.Err(), done, targetBloomElements) - return &Set{pool, bloom}, func() error { - sub.Unsubscribe() - <-done - return pool.Close() - } + return &Set{pool, bloom, sub, done} } -func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs <-chan core.NewTxsEvent, errs <-chan error, done chan<- struct{}, targetBloomElements int) { - defer close(done) - +func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs <-chan core.NewTxsEvent, errs <-chan error, done chan<- error, targetBloomElements int) { for { select { case ev := <-txs: @@ -92,14 +90,27 @@ func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs < } case err, ok := <-errs: - if !ok { - return + // [event.Subscription] documents semantics of its error channel, + // stating that at most one error will ever be sent, and that it + // will be closed when unsubscribing. + if ok { + logger.Error("TxPool subscription", zap.Error(err)) + done <- err + } else { + close(done) } - logger.Error("TxPool subscription", zap.Error(err)) + return } } } +// Close stops background work being performed by the [Set], and returns the +// last error encountered by said processes. +func (s *Set) Close() error { + s.txSub.Unsubscribe() + return <-s.bloomDone +} + // Add is a wrapper around [txpool.TxPool.Add], exposed to accept transactions // over [gossip]. It MAY be bypassed, and the pool's method accessed directly. func (s *Set) Add(tx Transaction) error { diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index f970ff6..c1f53c3 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -75,9 +75,11 @@ func newSUT(t *testing.T, numAccounts uint) sut { require.NoError(t, err, "gossip.NewBloomFilter([1 in a billion FP])") bc := NewBlockChain(exec, chain.GetBlock) - set, cleanup := NewSet(logger, newTxPool(t, bc), bloom, 1) + pool := newTxPool(t, bc) + set := NewSet(logger, pool, bloom, 1) t.Cleanup(func() { - require.NoError(t, cleanup(), "cleanup function returned by NewSet()") + assert.NoErrorf(t, set.Close(), "%T.Close()", set) + assert.NoErrorf(t, pool.Close(), "%T.Close()", pool) }) return sut{ From 46199a2d5f839885d93665cf7402f3eb225d9e69 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 10:58:36 +0000 Subject: [PATCH 48/52] feat: Bloom filter populated at `Set` construciton w/ txs already in pool --- txgossip/txgossip.go | 13 +++++++++++ txgossip/txgossip_test.go | 47 +++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go index 7998343..f4fa349 100644 --- a/txgossip/txgossip.go +++ b/txgossip/txgossip.go @@ -70,6 +70,8 @@ type Set struct { // NewSet returns a new [gossip.Set]. [Set.Close] MUST be called to release // resources. func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter, targetBloomElements int) *Set { + fillBloomFilter(pool, bloom) + txs := make(chan core.NewTxsEvent) sub := pool.SubscribeTransactions(txs, false) done := make(chan error, 1) @@ -78,6 +80,17 @@ func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilte return &Set{pool, bloom, sub, done} } +func fillBloomFilter(pool *txpool.TxPool, bloom *gossip.BloomFilter) { + pending, queued := pool.Content() + for _, txSet := range []map[common.Address][]*types.Transaction{pending, queued} { + for _, txs := range txSet { + for _, tx := range txs { + bloom.Add(Transaction{tx}) + } + } + } +} + func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs <-chan core.NewTxsEvent, errs <-chan error, done chan<- error, targetBloomElements int) { for { select { diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 226860c..bc57ad3 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -5,6 +5,7 @@ package txgossip import ( "context" + "errors" "math/big" "math/rand/v2" "path/filepath" @@ -47,21 +48,31 @@ func TestMain(m *testing.M) { ) } -// sut is the system under test, primarily the [Set]. -type sut struct { +// SUT is the system under test, primarily the [Set]. +type SUT struct { *Set chain *blockstest.ChainBuilder wallet *saetest.Wallet exec *saexec.Executor + bloom *gossip.BloomFilter } -func newSUT(t *testing.T, numAccounts uint) sut { +func chainConfig() *params.ChainConfig { + return params.AllDevChainProtocolChanges +} + +func newWallet(tb testing.TB, numAccounts uint) *saetest.Wallet { + tb.Helper() + signer := types.LatestSigner(chainConfig()) + return saetest.NewUNSAFEWallet(tb, numAccounts, signer) +} + +func newSUT(t *testing.T, numAccounts uint, existingTxs ...*types.Transaction) SUT { t.Helper() logger := saetest.NewTBLogger(t, logging.Warn) - config := params.AllDevChainProtocolChanges - signer := types.LatestSigner(config) - wallet := saetest.NewUNSAFEWallet(t, numAccounts, signer) + wallet := newWallet(t, numAccounts) + config := chainConfig() db := rawdb.NewMemoryDatabase() genesis := blockstest.NewGenesis(t, db, config, saetest.MaxAllocFor(wallet.Addresses()...)) @@ -78,17 +89,19 @@ func newSUT(t *testing.T, numAccounts uint) sut { bc := NewBlockChain(exec, chain.GetBlock) pool := newTxPool(t, bc) + require.NoErrorf(t, errors.Join(pool.Add(existingTxs, false, true)...), "%T.Add([existing txs in setup], local=false, sync=true)", pool) set := NewSet(logger, pool, bloom, 1) t.Cleanup(func() { assert.NoErrorf(t, set.Close(), "%T.Close()", set) assert.NoErrorf(t, pool.Close(), "%T.Close()", pool) }) - return sut{ + return SUT{ Set: set, chain: chain, wallet: wallet, exec: exec, + bloom: bloom, } } @@ -298,3 +311,23 @@ func (p *stubPeers) Sample(context.Context, int) []ids.NodeID { func (p *stubPeers) Top(context.Context, float64) []ids.NodeID { return p.ids } + +func TestExistingTxsInBloomFilter(t *testing.T) { + // Note that the wallet addresses are deterministic, so this single address + // will have sufficient funds when the SUT is created below. + wallet := newWallet(t, 1) + + var txs types.Transactions + for range 10 { + txs = append(txs, wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ + To: &common.Address{}, + Gas: params.TxGas, + GasPrice: big.NewInt(1), + })) + } + + sut := newSUT(t, 1, txs...) + for _, tx := range txs { + assert.True(t, sut.bloom.Has(Transaction{tx}), "%T.Has(%#x [tx already in %T when constructing %T])", tx.Hash(), sut.bloom, sut.Pool, sut.Set) + } +} From 7358fcb2b41479829d6d0a3dfd901d96d35acb5f Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 11:16:49 +0000 Subject: [PATCH 49/52] test: txs added directly to `TxPool` still populate Bloom filter --- txgossip/txgossip_test.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index bc57ad3..1fd73bb 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -312,13 +312,14 @@ func (p *stubPeers) Top(context.Context, float64) []ids.NodeID { return p.ids } -func TestExistingTxsInBloomFilter(t *testing.T) { +func TestTxPoolPopulatesBloomFilter(t *testing.T) { // Note that the wallet addresses are deterministic, so this single address // will have sufficient funds when the SUT is created below. wallet := newWallet(t, 1) var txs types.Transactions - for range 10 { + const n = 10 + for range n { txs = append(txs, wallet.SetNonceAndSign(t, 0, &types.LegacyTx{ To: &common.Address{}, Gas: params.TxGas, @@ -326,8 +327,19 @@ func TestExistingTxsInBloomFilter(t *testing.T) { })) } - sut := newSUT(t, 1, txs...) - for _, tx := range txs { - assert.True(t, sut.bloom.Has(Transaction{tx}), "%T.Has(%#x [tx already in %T when constructing %T])", tx.Hash(), sut.bloom, sut.Pool, sut.Set) + existing := txs[:n/2] + deferred := txs[n/2:] + + sut := newSUT(t, 1, existing...) + for i, tx := range existing { + assert.True(t, sut.bloom.Has(Transaction{tx}), "%T.Has(%#x [tx[%d] already in %T when constructing %T])", sut.bloom, tx.Hash(), i, sut.Pool, sut.Set) } + + require.NoError(t, errors.Join(sut.Pool.Add(deferred, false, true)...)) + require.EventuallyWithT(t, func(c *assert.CollectT) { + for i, tx := range deferred { + idx := i + n/2 + require.Truef(c, sut.bloom.Has(Transaction{tx}), "%T.Has(%#x [tx[%d] added to %T after constructing %T])", sut.bloom, tx.Hash(), idx, sut.Pool, sut.Set) + } + }, 2*time.Second, 20*time.Millisecond) } From 9c791869fb6826882539c2306f2936d65f8796b3 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 11:27:11 +0000 Subject: [PATCH 50/52] feat: dynamically resize Bloom filter based on mempool size --- txgossip/txgossip.go | 29 +++++++++++++++++------------ txgossip/txgossip_test.go | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go index f4fa349..5d4fb71 100644 --- a/txgossip/txgossip.go +++ b/txgossip/txgossip.go @@ -64,20 +64,24 @@ type Set struct { bloom *gossip.BloomFilter txSub event.Subscription - bloomDone <-chan error + bloomDone chan error } // NewSet returns a new [gossip.Set]. [Set.Close] MUST be called to release // resources. -func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter, targetBloomElements int) *Set { +func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter) *Set { fillBloomFilter(pool, bloom) txs := make(chan core.NewTxsEvent) - sub := pool.SubscribeTransactions(txs, false) - done := make(chan error, 1) - go maintainBloomFilter(logger, bloom, txs, sub.Err(), done, targetBloomElements) + s := &Set{ + Pool: pool, + bloom: bloom, + txSub: pool.SubscribeTransactions(txs, false), + bloomDone: make(chan error, 1), + } + go s.maintainBloomFilter(logger, txs) - return &Set{pool, bloom, sub, done} + return s } func fillBloomFilter(pool *txpool.TxPool, bloom *gossip.BloomFilter) { @@ -91,26 +95,27 @@ func fillBloomFilter(pool *txpool.TxPool, bloom *gossip.BloomFilter) { } } -func maintainBloomFilter(logger logging.Logger, bloom *gossip.BloomFilter, txs <-chan core.NewTxsEvent, errs <-chan error, done chan<- error, targetBloomElements int) { +func (s *Set) maintainBloomFilter(logger logging.Logger, txs <-chan core.NewTxsEvent) { for { select { case ev := <-txs: - if _, err := gossip.ResetBloomFilterIfNeeded(bloom, targetBloomElements); err != nil { + pending, queued := s.Pool.Stats() + if _, err := gossip.ResetBloomFilterIfNeeded(s.bloom, 2*(pending+queued)); err != nil { logger.Error("Resetting mempool bloom filter", zap.Error(err)) } for _, tx := range ev.Txs { - bloom.Add(Transaction{tx}) + s.bloom.Add(Transaction{tx}) } - case err, ok := <-errs: + case err, ok := <-s.txSub.Err(): // [event.Subscription] documents semantics of its error channel, // stating that at most one error will ever be sent, and that it // will be closed when unsubscribing. if ok { logger.Error("TxPool subscription", zap.Error(err)) - done <- err + s.bloomDone <- err } else { - close(done) + close(s.bloomDone) } return } diff --git a/txgossip/txgossip_test.go b/txgossip/txgossip_test.go index 1fd73bb..f637bde 100644 --- a/txgossip/txgossip_test.go +++ b/txgossip/txgossip_test.go @@ -90,7 +90,7 @@ func newSUT(t *testing.T, numAccounts uint, existingTxs ...*types.Transaction) S bc := NewBlockChain(exec, chain.GetBlock) pool := newTxPool(t, bc) require.NoErrorf(t, errors.Join(pool.Add(existingTxs, false, true)...), "%T.Add([existing txs in setup], local=false, sync=true)", pool) - set := NewSet(logger, pool, bloom, 1) + set := NewSet(logger, pool, bloom) t.Cleanup(func() { assert.NoErrorf(t, set.Close(), "%T.Close()", set) assert.NoErrorf(t, pool.Close(), "%T.Close()", pool) From c33c71a1510c6bb59c18aa82a012d91cec68f583 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 19:30:11 +0000 Subject: [PATCH 51/52] refactor: channel pipe without nested `select` blocks --- txgossip/blockchain.go | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/txgossip/blockchain.go b/txgossip/blockchain.go index 2a5a408..fb7d0bf 100644 --- a/txgossip/blockchain.go +++ b/txgossip/blockchain.go @@ -80,29 +80,40 @@ type pipe struct { quit, done chan struct{} } +func (p *pipe) Unsubscribe() { + p.sub.Unsubscribe() + close(p.quit) + <-p.done +} + +func (p *pipe) Err() <-chan error { + return p.sub.Err() +} + func (p *pipe) loop() { - defer close(p.done) for { - select { - case b := <-p.in: - select { - case p.out <- core.ChainHeadEvent{Block: b}: - - case <-p.quit: - return - } - case <-p.quit: + b, ok := p.read() + if !ok || !p.write(b) { + close(p.done) return } } } -func (p *pipe) Err() <-chan error { - return p.sub.Err() +func (p *pipe) read() (*types.Block, bool) { + select { + case b := <-p.in: + return b, true + case <-p.quit: + return nil, false + } } -func (p *pipe) Unsubscribe() { - p.sub.Unsubscribe() - close(p.quit) - <-p.done +func (p *pipe) write(b *types.Block) bool { + select { + case p.out <- core.ChainHeadEvent{Block: b}: + return true + case <-p.quit: + return false + } } From f797f5b5d3cd34338ad70ebd1760232b1fce4d03 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 21 Nov 2025 19:34:23 +0000 Subject: [PATCH 52/52] feat: refill Bloom filter after reset --- txgossip/txgossip.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/txgossip/txgossip.go b/txgossip/txgossip.go index 5d4fb71..9ca18ba 100644 --- a/txgossip/txgossip.go +++ b/txgossip/txgossip.go @@ -70,8 +70,6 @@ type Set struct { // NewSet returns a new [gossip.Set]. [Set.Close] MUST be called to release // resources. func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilter) *Set { - fillBloomFilter(pool, bloom) - txs := make(chan core.NewTxsEvent) s := &Set{ Pool: pool, @@ -79,17 +77,19 @@ func NewSet(logger logging.Logger, pool *txpool.TxPool, bloom *gossip.BloomFilte txSub: pool.SubscribeTransactions(txs, false), bloomDone: make(chan error, 1), } + + s.fillBloomFilter() go s.maintainBloomFilter(logger, txs) return s } -func fillBloomFilter(pool *txpool.TxPool, bloom *gossip.BloomFilter) { - pending, queued := pool.Content() +func (s *Set) fillBloomFilter() { + pending, queued := s.Pool.Content() for _, txSet := range []map[common.Address][]*types.Transaction{pending, queued} { for _, txs := range txSet { for _, tx := range txs { - bloom.Add(Transaction{tx}) + s.bloom.Add(Transaction{tx}) } } } @@ -100,9 +100,14 @@ func (s *Set) maintainBloomFilter(logger logging.Logger, txs <-chan core.NewTxsE select { case ev := <-txs: pending, queued := s.Pool.Stats() - if _, err := gossip.ResetBloomFilterIfNeeded(s.bloom, 2*(pending+queued)); err != nil { + reset, err := gossip.ResetBloomFilterIfNeeded(s.bloom, 2*(pending+queued)) + if err != nil { logger.Error("Resetting mempool bloom filter", zap.Error(err)) } + if reset { + s.fillBloomFilter() + } + for _, tx := range ev.Txs { s.bloom.Add(Transaction{tx}) }