From ec46ce5e1e67b1cb84a7cf6df521eb1e57c4e6f0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 13 Nov 2024 20:05:19 +0300 Subject: [PATCH 01/57] remove shared memory from precompiles and accept (#1383) * add regression test * remove shared memory from accept and precompile interface * Update plugin/evm/vm_test.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * revert changes to test helper --------- Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali --- plugin/evm/block.go | 28 +++--------- plugin/evm/block_test.go | 2 +- plugin/evm/shared_memory_writer.go | 37 ---------------- plugin/evm/syncervm_client.go | 4 +- plugin/evm/vm.go | 3 +- plugin/evm/vm_test.go | 62 +++++++++++++++++++++++++++ precompile/precompileconfig/config.go | 13 +----- 7 files changed, 73 insertions(+), 76 deletions(-) delete mode 100644 plugin/evm/shared_memory_writer.go diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9d3d238d02..9e7d96855a 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -61,12 +61,9 @@ func (b *Block) Accept(context.Context) error { // Call Accept for relevant precompile logs. Note we do this prior to // calling Accept on the blockChain so any side effects (eg warp signatures) - // take place before the accepted log is emitted to subscribers. Use of the - // sharedMemoryWriter ensures shared memory requests generated by - // precompiles are committed atomically with the vm's lastAcceptedKey. + // take place before the accepted log is emitted to subscribers. rules := b.vm.chainConfig.Rules(b.ethBlock.Number(), b.ethBlock.Timestamp()) - sharedMemoryWriter := NewSharedMemoryWriter() - if err := b.handlePrecompileAccept(rules, sharedMemoryWriter); err != nil { + if err := b.handlePrecompileAccept(rules); err != nil { return err } if err := vm.blockChain.Accept(b.ethBlock); err != nil { @@ -77,24 +74,12 @@ func (b *Block) Accept(context.Context) error { return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err) } - // Get pending operations on the vm's versionDB so we can apply them atomically - // with the shared memory requests. - vdbBatch, err := b.vm.db.CommitBatch() - if err != nil { - return fmt.Errorf("could not create commit batch processing block[%s]: %w", b.ID(), err) - } - - // Apply any shared memory requests that accumulated from processing the logs - // of the accepted block (generated by precompiles) atomically with other pending - // changes to the vm's versionDB. - return vm.ctx.SharedMemory.Apply(sharedMemoryWriter.requests, vdbBatch) + return b.vm.db.Commit() } // handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements // contract.Accepter -// This function assumes that the Accept function will ONLY operate on state maintained in the VM's versiondb. -// This ensures that any DB operations are performed atomically with marking the block as accepted. -func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *sharedMemoryWriter) error { +func (b *Block) handlePrecompileAccept(rules params.Rules) error { // Short circuit early if there are no precompile accepters to execute if len(rules.AccepterPrecompiles) == 0 { return nil @@ -108,9 +93,8 @@ func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *s return fmt.Errorf("failed to fetch receipts for accepted block with non-empty root hash (%s) (Block: %s, Height: %d)", b.ethBlock.ReceiptHash(), b.ethBlock.Hash(), b.ethBlock.NumberU64()) } acceptCtx := &precompileconfig.AcceptContext{ - SnowCtx: b.vm.ctx, - SharedMemory: sharedMemoryWriter, - Warp: b.vm.warpBackend, + SnowCtx: b.vm.ctx, + Warp: b.vm.warpBackend, } for _, receipt := range receipts { for logIdx, log := range receipt.Logs { diff --git a/plugin/evm/block_test.go b/plugin/evm/block_test.go index f30cc4ceae..638f551b91 100644 --- a/plugin/evm/block_test.go +++ b/plugin/evm/block_test.go @@ -93,5 +93,5 @@ func TestHandlePrecompileAccept(t *testing.T) { precompileAddr: mockAccepter, }, } - require.NoError(blk.handlePrecompileAccept(rules, nil)) + require.NoError(blk.handlePrecompileAccept(rules)) } diff --git a/plugin/evm/shared_memory_writer.go b/plugin/evm/shared_memory_writer.go deleted file mode 100644 index 88589720ee..0000000000 --- a/plugin/evm/shared_memory_writer.go +++ /dev/null @@ -1,37 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "github.com/ava-labs/avalanchego/chains/atomic" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" -) - -var _ precompileconfig.SharedMemoryWriter = &sharedMemoryWriter{} - -type sharedMemoryWriter struct { - requests map[ids.ID]*atomic.Requests -} - -func NewSharedMemoryWriter() *sharedMemoryWriter { - return &sharedMemoryWriter{ - requests: make(map[ids.ID]*atomic.Requests), - } -} - -func (s *sharedMemoryWriter) AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) { - mergeAtomicOpsToMap(s.requests, chainID, requests) -} - -// mergeAtomicOps merges atomic ops for [chainID] represented by [requests] -// to the [output] map provided. -func mergeAtomicOpsToMap(output map[ids.ID]*atomic.Requests, chainID ids.ID, requests *atomic.Requests) { - if request, exists := output[chainID]; exists { - request.PutRequests = append(request.PutRequests, requests.PutRequests...) - request.RemoveRequests = append(request.RemoveRequests, requests.RemoveRequests...) - } else { - output[chainID] = requests - } -} diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index f18372995e..be4da4108a 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -299,7 +299,7 @@ func (client *stateSyncerClient) Shutdown() error { } // finishSync is responsible for updating disk and memory pointers so the VM is prepared -// for bootstrapping. Executes any shared memory operations from the atomic trie to shared memory. +// for bootstrapping. func (client *stateSyncerClient) finishSync() error { stateBlock, err := client.state.GetBlock(context.TODO(), ids.ID(client.syncSummary.BlockHash)) if err != nil { @@ -349,8 +349,6 @@ func (client *stateSyncerClient) finishSync() error { // updateVMMarkers updates the following markers in the VM's database // and commits them atomically: -// - updates atomic trie so it will have necessary metadata for the last committed root -// - updates atomic trie so it will resume applying operations to shared memory on initialize // - updates lastAcceptedKey // - removes state sync progress markers func (client *stateSyncerClient) updateVMMarkers() error { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 865042c19b..8e169f6d4b 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -1255,8 +1255,7 @@ func (vm *VM) initializeDBs(avaDB database.Database) error { // skip standalone database initialization if we are running in unit tests if vm.ctx.NetworkID != avalancheconstants.UnitTestID { // first initialize the accepted block database to check if we need to use a standalone database - verDB := versiondb.New(avaDB) - acceptedDB := prefixdb.New(acceptedPrefix, verDB) + acceptedDB := prefixdb.New(acceptedPrefix, avaDB) useStandAloneDB, err := vm.useStandaloneDatabase(acceptedDB) if err != nil { return err diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 13a22a8e81..6f1753e62e 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api/keystore" @@ -3151,3 +3152,64 @@ func TestParentBeaconRootBlock(t *testing.T) { }) } } + +func TestStandaloneDB(t *testing.T) { + vm := &VM{} + ctx := NewContext() + baseDB := memdb.New() + atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) + ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID) + issuer := make(chan commonEng.Message, 1) + sharedDB := prefixdb.New([]byte{1}, baseDB) + genesisBytes := buildGenesisTest(t, genesisJSONLatest) + // alter network ID to use standalone database + ctx.NetworkID = 123456 + appSender := &enginetest.Sender{T: t} + appSender.CantSendAppGossip = true + appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } + configJSON := `{"database-type": "memdb"}` + + isDBEmpty := func(db database.Database) bool { + it := db.NewIterator() + defer it.Release() + return !it.Next() + } + // Ensure that the database is empty + require.True(t, isDBEmpty(baseDB)) + + err := vm.Initialize( + context.Background(), + ctx, + sharedDB, + genesisBytes, + nil, + []byte(configJSON), + issuer, + []*commonEng.Fx{}, + appSender, + ) + defer vm.Shutdown(context.Background()) + require.NoError(t, err, "error initializing VM") + require.NoError(t, vm.SetState(context.Background(), snow.Bootstrapping)) + require.NoError(t, vm.SetState(context.Background(), snow.NormalOp)) + + // Issue a block + acceptedBlockEvent := make(chan core.ChainEvent, 1) + vm.blockChain.SubscribeChainAcceptedEvent(acceptedBlockEvent) + tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) + signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + require.NoError(t, err) + errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) + require.NoError(t, errs[0]) + + // accept block + blk := issueAndAccept(t, issuer, vm) + newBlock := <-acceptedBlockEvent + require.Equal(t, newBlock.Block.Hash(), common.Hash(blk.ID())) + + // Ensure that the shared database is empty + assert.True(t, isDBEmpty(baseDB)) + // Ensure that the standalone database is not empty + assert.False(t, isDBEmpty(vm.db)) + assert.False(t, isDBEmpty(vm.acceptedBlockDB)) +} diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go index 05d204de45..289549b4e4 100644 --- a/precompile/precompileconfig/config.go +++ b/precompile/precompileconfig/config.go @@ -5,8 +5,6 @@ package precompileconfig import ( - "github.com/ava-labs/avalanchego/chains/atomic" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -54,21 +52,14 @@ type Predicater interface { VerifyPredicate(predicateContext *PredicateContext, predicateBytes []byte) error } -// SharedMemoryWriter defines an interface to allow a precompile's Accepter to write operations -// into shared memory to be committed atomically on block accept. -type SharedMemoryWriter interface { - AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) -} - type WarpMessageWriter interface { AddMessage(unsignedMessage *warp.UnsignedMessage) error } // AcceptContext defines the context passed in to a precompileconfig's Accepter type AcceptContext struct { - SnowCtx *snow.Context - SharedMemory SharedMemoryWriter - Warp WarpMessageWriter + SnowCtx *snow.Context + Warp WarpMessageWriter } // Accepter is an optional interface for StatefulPrecompiledContracts to implement. From 981830ed5b1948141e3f502698152298268804eb Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 13 Nov 2024 22:27:03 +0300 Subject: [PATCH 02/57] Uptime tracking (#1263) * add validator state * add pausable uptime manager * remove stuttering name * rename state listener * add uptime tracking to VM * remove unused param * add wg for update validators * update state before network shutdown * restart bootstrapping status in test * add get validator to state * rename uptime to validator * fix mock state * tests * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * use update enum * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * respond to comments * update avalanchego dep branch * reviews * reword errs * fix test changes * fix upgrades after deactivating latest in context * use test branch from avalanchego * use branch commit for ava version * update e2e ava version * update avago dep * remove extra line... * export struct * reviews * conflict fix * custom err msg * add listener mock * bump avago test branch * remove config * remove api changes * use non-version db for validatorsDB * remove errs from resume and pause * check after stopping * use expectedTime in tests * reviews * fix len * remove log * use interfaces from pkgs * improve comments * new fields and refactorings * fix linter * clarify comments * update comment * bump to poc branch * reviews * update comment * update to avago master --------- Signed-off-by: Ceyhun Onur Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali --- go.mod | 29 +- go.sum | 65 +++- plugin/evm/block.go | 2 +- plugin/evm/syncervm_test.go | 2 +- plugin/evm/tx_gossip_test.go | 11 +- plugin/evm/uptime/pausable_manager.go | 14 +- plugin/evm/uptime/pausable_manager_test.go | 89 ++++- plugin/evm/validators/interfaces/interface.go | 28 +- plugin/evm/validators/state.go | 111 ++++-- plugin/evm/validators/state_test.go | 110 +++++- plugin/evm/vm.go | 218 +++++++++-- plugin/evm/vm_test.go | 52 +-- plugin/evm/vm_upgrade_bytes_test.go | 3 + plugin/evm/vm_validators_state_test.go | 363 ++++++++++++++++++ plugin/evm/vm_warp_test.go | 4 +- scripts/versions.sh | 2 +- utils/snow.go | 60 ++- 17 files changed, 996 insertions(+), 167 deletions(-) create mode 100644 plugin/evm/vm_validators_state_test.go diff --git a/go.mod b/go.mod index 8a074d32ca..203611e4b5 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.13-0.20241026214739-acb3d7d102a0 + github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -76,17 +76,23 @@ require ( github.com/dlclark/regexp2 v1.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -96,17 +102,25 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/jackpal/gateway v1.0.6 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // 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/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -124,7 +138,7 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect - github.com/supranational/blst v0.3.11 // indirect + github.com/supranational/blst v0.3.13 // 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 @@ -141,14 +155,25 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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 + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.29.0 // indirect + k8s.io/apimachinery v0.29.0 // indirect + k8s.io/client-go v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect rsc.io/tmplfunc v0.0.3 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index dd6bf4e4f6..d06ba20439 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,10 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/antithesishq/antithesis-sdk-go v0.3.8 h1:OvGoHxIcOXFJLyn9IJQ5DzByZ3YVAWNBc394ObzDRb8= github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.11.13-0.20241026214739-acb3d7d102a0 h1:1T9OnvZP6XZ62EVWlfmrI8rrudyE6bM2Zt51pCHfS5o= -github.com/ava-labs/avalanchego v1.11.13-0.20241026214739-acb3d7d102a0/go.mod h1:gYlTU42Q4b29hzhUN22yclym5qwB3Si0jh4+LTn7DZM= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb h1:ujIv6zAKQpZtlalLGSt9cx2AREJmYcv0wMmXGRAH6/g= +github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb/go.mod h1:86tO6F1FT8emclUwdQ2WCwAtAerqjm5A4IbV6XxNUyM= github.com/ava-labs/coreth v0.13.8 h1:f14X3KgwHl9LwzfxlN6S4bbn5VA2rhEsNnHaRLSTo/8= github.com/ava-labs/coreth v0.13.8/go.mod h1:t3BSv/eQv0AlDPMfEDCMMoD/jq1RkUsbFzQAFg5qBcE= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -171,6 +173,8 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -212,6 +216,7 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/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= @@ -220,6 +225,12 @@ github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AE github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -278,6 +289,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -288,10 +301,13 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -324,6 +340,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/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/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -365,9 +382,13 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -404,6 +425,8 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -437,13 +460,22 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh 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/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 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= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 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= @@ -546,6 +578,7 @@ github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobt 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -555,12 +588,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= +github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 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= @@ -740,6 +774,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -1020,6 +1056,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1031,6 +1069,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -1048,8 +1087,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9e7d96855a..fac7689768 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -199,7 +199,7 @@ func (b *Block) verify(predicateContext *precompileconfig.PredicateContext, writ // If the chain is still bootstrapping, we can assume that all blocks we are verifying have // been accepted by the network (so the predicate was validated by the network when the // block was originally verified). - if b.vm.bootstrapped { + if b.vm.bootstrapped.Get() { if err := b.verifyPredicates(predicateContext); err != nil { return fmt.Errorf("failed to verify predicates: %w", err) } diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index e87bd9403f..f200200db1 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -494,7 +494,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { // check we can transition to [NormalOp] state and continue to process blocks. require.NoError(syncerVM.SetState(context.Background(), snow.NormalOp)) - require.True(syncerVM.bootstrapped) + require.True(syncerVM.bootstrapped.Get()) // Generate blocks after we have entered normal consensus as well generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index 36c47ffde5..0eaaa63e6d 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -20,7 +20,6 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/enginetest" "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/snow/validators/validatorstest" agoUtils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" @@ -37,7 +36,7 @@ func TestEthTxGossip(t *testing.T) { require := require.New(t) ctx := context.Background() snowCtx := utils.TestSnowContext() - validatorState := &validatorstest.State{} + validatorState := utils.NewTestValidatorState() snowCtx.ValidatorState = validatorState responseSender := &enginetest.SenderStub{ @@ -155,14 +154,6 @@ func TestEthTxPushGossipOutbound(t *testing.T) { require := require.New(t) ctx := context.Background() snowCtx := utils.TestSnowContext() - snowCtx.ValidatorState = &validatorstest.State{ - GetCurrentHeightF: func(context.Context) (uint64, error) { - return 0, nil - }, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return nil, nil - }, - } sender := &enginetest.SenderStub{ SentAppGossip: make(chan []byte, 1), } diff --git a/plugin/evm/uptime/pausable_manager.go b/plugin/evm/uptime/pausable_manager.go index 3976fcae6c..6c437dd049 100644 --- a/plugin/evm/uptime/pausable_manager.go +++ b/plugin/evm/uptime/pausable_manager.go @@ -58,16 +58,10 @@ func (p *pausableManager) Disconnect(nodeID ids.NodeID) error { return nil } -// StartTracking starts tracking uptime for the nodes with the given IDs -// If a node is paused, it will not be tracked -func (p *pausableManager) StartTracking(nodeIDs []ids.NodeID) error { - activeNodeIDs := make([]ids.NodeID, 0, len(nodeIDs)) - for _, nodeID := range nodeIDs { - if !p.IsPaused(nodeID) { - activeNodeIDs = append(activeNodeIDs, nodeID) - } - } - return p.Manager.StartTracking(activeNodeIDs) +// IsConnected returns true if the node with the given ID is connected to this manager +// Note: Inner manager may have a different view of the connection status due to pausing +func (p *pausableManager) IsConnected(nodeID ids.NodeID) bool { + return p.connectedVdrs.Contains(nodeID) } // OnValidatorAdded is called when a validator is added. diff --git a/plugin/evm/uptime/pausable_manager_test.go b/plugin/evm/uptime/pausable_manager_test.go index e1f4f4a6f8..a910203773 100644 --- a/plugin/evm/uptime/pausable_manager_test.go +++ b/plugin/evm/uptime/pausable_manager_test.go @@ -31,6 +31,7 @@ func TestPausableManager(t *testing.T) { // Connect before tracking require.NoError(up.Connect(nodeID0)) addTime(clk, time.Second) + expectedUptime := 1 * time.Second // Pause before tracking up.OnValidatorStatusUpdated(vID, nodeID0, false) @@ -38,12 +39,15 @@ func TestPausableManager(t *testing.T) { // Elapse Time addTime(clk, time.Second) + // Since we have not started tracking this node yet, its observed uptime should + // be incremented even though it is actually paused. + expectedUptime += 1 * time.Second // Start tracking require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + // Uptime here should not increase after start tracking + // since the node is still paused after we started tracking currentTime := addTime(clk, time.Second) - // Uptime should not have increased since the node was paused - expectedUptime := 0 * time.Second checkUptime(t, up, nodeID0, expectedUptime, currentTime) // Disconnect @@ -114,15 +118,17 @@ func TestPausableManager(t *testing.T) { // Start tracking addTime(clk, time.Second) + // Uptime should be 1 since the node was paused before we started tracking + expectedUptime := 1 * time.Second require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) // Connect and check uptime addTime(clk, 1*time.Second) + checkUptime(t, up, nodeID0, expectedUptime, clk.Time()) require.NoError(up.Connect(nodeID0)) currentTime := addTime(clk, 2*time.Second) - // Uptime should not have increased since the node was paused - expectedUptime := 0 * time.Second + // Uptime should not have increased since the node was paused after we started tracking checkUptime(t, up, nodeID0, expectedUptime, currentTime) // Disconnect and check uptime @@ -209,6 +215,81 @@ func TestPausableManager(t *testing.T) { checkUptime(t, up, nodeID0, expectedUptime, currentTime) }, }, + { + name: "Case 5: Node paused after we stop tracking", + testFunc: func(t *testing.T, up interfaces.PausableManager, clk *mockable.Clock, s uptime.State) { + require := require.New(t) + + // Start tracking and connect + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + addTime(clk, time.Second) + require.NoError(up.Connect(nodeID0)) + + // Stop tracking + currentTime := addTime(clk, 2*time.Second) + expectedUptime := 2 * time.Second + checkUptime(t, up, nodeID0, expectedUptime, currentTime) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) + + // Pause after a while + addTime(clk, 3*time.Second) + // expectedUptime should increase since we stopped tracking + expectedUptime += 3 * time.Second + up.OnValidatorStatusUpdated(vID, nodeID0, false) + require.True(up.IsPaused(nodeID0)) + // expectedUptime should increase since we stopped tracking (even if the node was paused) + currentTime = addTime(clk, 4*time.Second) + expectedUptime += 4 * time.Second + + // Start tracking and check elapsed time + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + // Uptime have increased since the node was paused before we started tracking + // We should be optimistic and assume the node was online and active until we start tracking + require.True(up.IsPaused(nodeID0)) + checkUptime(t, up, nodeID0, expectedUptime, currentTime) + }, + }, + { + name: "Case 6: Paused node got resumed after we stop tracking", + testFunc: func(t *testing.T, up interfaces.PausableManager, clk *mockable.Clock, s uptime.State) { + require := require.New(t) + + // Start tracking and connect + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + addTime(clk, time.Second) + require.NoError(up.Connect(nodeID0)) + + // Pause after a while + currentTime := addTime(clk, 2*time.Second) + // expectedUptime should increase + expectedUptime := 2 * time.Second + up.OnValidatorStatusUpdated(vID, nodeID0, false) + require.True(up.IsPaused(nodeID0)) + checkUptime(t, up, nodeID0, expectedUptime, currentTime) + + // Stop tracking + currentTime = addTime(clk, 3*time.Second) + // expectedUptime should not increase since the node was paused + checkUptime(t, up, nodeID0, expectedUptime, currentTime) + require.NoError(up.StopTracking([]ids.NodeID{nodeID0})) + + // Resume after a while + addTime(clk, 4*time.Second) + // expectedUptime should increase since we stopped tracking + expectedUptime += 4 * time.Second + up.OnValidatorStatusUpdated(vID, nodeID0, true) + require.False(up.IsPaused(nodeID0)) + // expectedUptime should increase since we stopped tracking + currentTime = addTime(clk, 5*time.Second) + expectedUptime += 5 * time.Second + + // Start tracking and check elapsed time + require.NoError(up.StartTracking([]ids.NodeID{nodeID0})) + // Uptime should have increased by 4 seconds since the node was resumed + // We should be optimistic and assume the node was online and active until we start tracking + checkUptime(t, up, nodeID0, expectedUptime, currentTime) + }, + }, } for _, test := range tests { diff --git a/plugin/evm/validators/interfaces/interface.go b/plugin/evm/validators/interfaces/interface.go index 197ab553a1..39b6b8c9e9 100644 --- a/plugin/evm/validators/interfaces/interface.go +++ b/plugin/evm/validators/interfaces/interface.go @@ -4,6 +4,8 @@ package interfaces import ( + "time" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/set" @@ -12,21 +14,20 @@ import ( type State interface { uptime.State // AddValidator adds a new validator to the state - AddValidator(vID ids.ID, nodeID ids.NodeID, startTimestamp uint64, isActive bool) error + AddValidator(vdr Validator) error + // UpdateValidator updates the validator in the state + UpdateValidator(vdr Validator) error + // GetValidator returns the validator data for the given validation ID + GetValidator(vID ids.ID) (Validator, error) // DeleteValidator deletes the validator from the state DeleteValidator(vID ids.ID) error // WriteState writes the validator state to the disk WriteState() error - // SetStatus sets the active status of the validator with the given vID - SetStatus(vID ids.ID, isActive bool) error - // GetStatus returns the active status of the validator with the given vID - GetStatus(vID ids.ID) (bool, error) - // GetValidationIDs returns the validation IDs in the state GetValidationIDs() set.Set[ids.ID] - // GetValidatorIDs returns the validator node IDs in the state - GetValidatorIDs() set.Set[ids.NodeID] + // GetNodeIDs returns the validator node IDs in the state + GetNodeIDs() set.Set[ids.NodeID] // RegisterListener registers a listener to the state RegisterListener(StateCallbackListener) @@ -41,3 +42,14 @@ type StateCallbackListener interface { // OnValidatorStatusUpdated is called when a validator status is updated OnValidatorStatusUpdated(vID ids.ID, nodeID ids.NodeID, isActive bool) } + +type Validator struct { + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTimestamp uint64 `json:"startTimestamp"` + IsActive bool `json:"isActive"` + IsSoV bool `json:"isSoV"` +} + +func (v *Validator) StartTime() time.Time { return time.Unix(int64(v.StartTimestamp), 0) } diff --git a/plugin/evm/validators/state.go b/plugin/evm/validators/state.go index 0be101f6bf..a8769466e9 100644 --- a/plugin/evm/validators/state.go +++ b/plugin/evm/validators/state.go @@ -18,19 +18,24 @@ var _ uptime.State = &state{} type dbUpdateStatus bool -var ErrAlreadyExists = fmt.Errorf("validator already exists") +var ( + ErrAlreadyExists = fmt.Errorf("validator already exists") + ErrImmutableField = fmt.Errorf("immutable field cannot be updated") +) const ( - updated dbUpdateStatus = true - deleted dbUpdateStatus = false + updatedStatus dbUpdateStatus = true + deletedStatus dbUpdateStatus = false ) type validatorData struct { UpDuration time.Duration `serialize:"true"` LastUpdated uint64 `serialize:"true"` NodeID ids.NodeID `serialize:"true"` + Weight uint64 `serialize:"true"` StartTime uint64 `serialize:"true"` IsActive bool `serialize:"true"` + IsSoV bool `serialize:"true"` validationID ids.ID // database key } @@ -83,7 +88,7 @@ func (s *state) SetUptime( data.UpDuration = upDuration data.setLastUpdated(lastUpdated) - s.updatedData[data.validationID] = updated + s.updatedData[data.validationID] = updatedStatus return nil } @@ -98,23 +103,57 @@ func (s *state) GetStartTime(nodeID ids.NodeID) (time.Time, error) { // AddValidator adds a new validator to the state // the new validator is marked as updated and will be written to the disk when WriteState is called -func (s *state) AddValidator(vID ids.ID, nodeID ids.NodeID, startTimestamp uint64, isActive bool) error { +func (s *state) AddValidator(vdr interfaces.Validator) error { data := &validatorData{ - NodeID: nodeID, - validationID: vID, - IsActive: isActive, - StartTime: startTimestamp, + NodeID: vdr.NodeID, + validationID: vdr.ValidationID, + IsActive: vdr.IsActive, + StartTime: vdr.StartTimestamp, UpDuration: 0, - LastUpdated: startTimestamp, + LastUpdated: vdr.StartTimestamp, + IsSoV: vdr.IsSoV, + Weight: vdr.Weight, } - if err := s.addData(vID, data); err != nil { + if err := s.addData(vdr.ValidationID, data); err != nil { return err } - s.updatedData[vID] = updated + s.updatedData[vdr.ValidationID] = updatedStatus for _, listener := range s.listeners { - listener.OnValidatorAdded(vID, nodeID, startTimestamp, isActive) + listener.OnValidatorAdded(vdr.ValidationID, vdr.NodeID, vdr.StartTimestamp, vdr.IsActive) + } + return nil +} + +// UpdateValidator updates the validator in the state +// returns an error if the validator does not exist or if the immutable fields are modified +func (s *state) UpdateValidator(vdr interfaces.Validator) error { + data, exists := s.data[vdr.ValidationID] + if !exists { + return database.ErrNotFound + } + // check immutable fields + if !data.constantsAreUnmodified(vdr) { + return ErrImmutableField + } + // check if mutable fields have changed + updated := false + if data.IsActive != vdr.IsActive { + data.IsActive = vdr.IsActive + updated = true + for _, listener := range s.listeners { + listener.OnValidatorStatusUpdated(data.validationID, data.NodeID, data.IsActive) + } + } + + if data.Weight != vdr.Weight { + data.Weight = vdr.Weight + updated = true + } + + if updated { + s.updatedData[vdr.ValidationID] = updatedStatus } return nil } @@ -130,7 +169,7 @@ func (s *state) DeleteValidator(vID ids.ID) error { delete(s.index, data.NodeID) // mark as deleted for WriteValidator - s.updatedData[data.validationID] = deleted + s.updatedData[data.validationID] = deletedStatus for _, listener := range s.listeners { listener.OnValidatorRemoved(vID, data.NodeID) @@ -144,7 +183,7 @@ func (s *state) WriteState() error { batch := s.db.NewBatch() for vID, updateStatus := range s.updatedData { switch updateStatus { - case updated: + case updatedStatus: data := s.data[vID] dataBytes, err := vdrCodec.Marshal(codecVersion, data) @@ -154,7 +193,7 @@ func (s *state) WriteState() error { if err := batch.Put(vID[:], dataBytes); err != nil { return err } - case deleted: + case deletedStatus: if err := batch.Delete(vID[:]); err != nil { return err } @@ -174,7 +213,7 @@ func (s *state) SetStatus(vID ids.ID, isActive bool) error { return database.ErrNotFound } data.IsActive = isActive - s.updatedData[vID] = updated + s.updatedData[vID] = updatedStatus for _, listener := range s.listeners { listener.OnValidatorStatusUpdated(vID, data.NodeID, isActive) @@ -182,15 +221,6 @@ func (s *state) SetStatus(vID ids.ID, isActive bool) error { return nil } -// GetStatus returns the active status of the validator with the given vID -func (s *state) GetStatus(vID ids.ID) (bool, error) { - data, exists := s.data[vID] - if !exists { - return false, database.ErrNotFound - } - return data.IsActive, nil -} - // GetValidationIDs returns the validation IDs in the state func (s *state) GetValidationIDs() set.Set[ids.ID] { ids := set.NewSet[ids.ID](len(s.data)) @@ -200,8 +230,8 @@ func (s *state) GetValidationIDs() set.Set[ids.ID] { return ids } -// GetValidatorIDs returns the validator IDs in the state -func (s *state) GetValidatorIDs() set.Set[ids.NodeID] { +// GetNodeIDs returns the node IDs of validators in the state +func (s *state) GetNodeIDs() set.Set[ids.NodeID] { ids := set.NewSet[ids.NodeID](len(s.index)) for nodeID := range s.index { ids.Add(nodeID) @@ -209,6 +239,22 @@ func (s *state) GetValidatorIDs() set.Set[ids.NodeID] { return ids } +// GetValidator returns the validator data for the given validationID +func (s *state) GetValidator(vID ids.ID) (interfaces.Validator, error) { + data, ok := s.data[vID] + if !ok { + return interfaces.Validator{}, database.ErrNotFound + } + return interfaces.Validator{ + ValidationID: data.validationID, + NodeID: data.NodeID, + StartTimestamp: data.StartTime, + IsActive: data.IsActive, + Weight: data.Weight, + IsSoV: data.IsSoV, + }, nil +} + // RegisterListener registers a listener to the state // OnValidatorAdded is called for all current validators on the provided listener before this function returns func (s *state) RegisterListener(listener interfaces.StateCallbackListener) { @@ -293,3 +339,12 @@ func (v *validatorData) getLastUpdated() time.Time { func (v *validatorData) getStartTime() time.Time { return time.Unix(int64(v.StartTime), 0) } + +// constantsAreUnmodified returns true if the constants of this validator have +// not been modified compared to the updated validator. +func (v *validatorData) constantsAreUnmodified(u interfaces.Validator) bool { + return v.validationID == u.ValidationID && + v.NodeID == u.NodeID && + v.IsSoV == u.IsSoV && + v.StartTime == u.StartTimestamp +} diff --git a/plugin/evm/validators/state_test.go b/plugin/evm/validators/state_test.go index e5e8244027..7ba6574785 100644 --- a/plugin/evm/validators/state_test.go +++ b/plugin/evm/validators/state_test.go @@ -36,13 +36,23 @@ func TestState(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) // add new validator - state.AddValidator(vID, nodeID, uint64(startTime.Unix()), true) + vdr := interfaces.Validator{ + ValidationID: vID, + NodeID: nodeID, + Weight: 1, + StartTimestamp: uint64(startTime.Unix()), + IsActive: true, + IsSoV: true, + } + state.AddValidator(vdr) // adding the same validator should fail - err = state.AddValidator(vID, ids.GenerateTestNodeID(), uint64(startTime.Unix()), true) + err = state.AddValidator(vdr) require.ErrorIs(err, ErrAlreadyExists) // adding the same nodeID should fail - err = state.AddValidator(ids.GenerateTestID(), nodeID, uint64(startTime.Unix()), true) + newVdr := vdr + newVdr.ValidationID = ids.GenerateTestID() + err = state.AddValidator(newVdr) require.ErrorIs(err, ErrAlreadyExists) // get uptime @@ -62,11 +72,39 @@ func TestState(t *testing.T) { require.Equal(newLastUpdated, lastUpdated) // set status - require.NoError(state.SetStatus(vID, false)) + vdr.IsActive = false + require.NoError(state.UpdateValidator(vdr)) // get status - status, err := state.GetStatus(vID) + data, err := state.GetValidator(vID) + require.NoError(err) + require.False(data.IsActive) + + // set weight + newWeight := uint64(2) + vdr.Weight = newWeight + require.NoError(state.UpdateValidator(vdr)) + // get weight + data, err = state.GetValidator(vID) require.NoError(err) - require.False(status) + require.Equal(newWeight, data.Weight) + + // set a different node ID should fail + newNodeID := ids.GenerateTestNodeID() + vdr.NodeID = newNodeID + require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) + + // set a different start time should fail + newStartTime := vdr.StartTime().Add(time.Hour) + vdr.StartTimestamp = uint64(newStartTime.Unix()) + require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) + + // set SoV should fail + vdr.IsSoV = false + require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) + + // set validation ID should result in not found + vdr.ValidationID = ids.GenerateTestID() + require.ErrorIs(state.UpdateValidator(vdr), database.ErrNotFound) // delete uptime require.NoError(state.DeleteValidator(vID)) @@ -88,7 +126,14 @@ func TestWriteValidator(t *testing.T) { nodeID := ids.GenerateTestNodeID() vID := ids.GenerateTestID() startTime := time.Now() - require.NoError(state.AddValidator(vID, nodeID, uint64(startTime.Unix()), true)) + require.NoError(state.AddValidator(interfaces.Validator{ + ValidationID: vID, + NodeID: nodeID, + Weight: 1, + StartTimestamp: uint64(startTime.Unix()), + IsActive: true, + IsSoV: true, + })) // write state, should reflect to DB require.NoError(state.WriteState()) @@ -132,8 +177,14 @@ func TestParseValidator(t *testing.T) { name: "nil", bytes: nil, expected: &validatorData{ - LastUpdated: 0, - StartTime: 0, + LastUpdated: 0, + StartTime: 0, + validationID: ids.Empty, + NodeID: ids.EmptyNodeID, + UpDuration: 0, + Weight: 0, + IsActive: false, + IsSoV: false, }, expectedErr: nil, }, @@ -141,8 +192,14 @@ func TestParseValidator(t *testing.T) { name: "empty", bytes: []byte{}, expected: &validatorData{ - LastUpdated: 0, - StartTime: 0, + LastUpdated: 0, + StartTime: 0, + validationID: ids.Empty, + NodeID: ids.EmptyNodeID, + UpDuration: 0, + Weight: 0, + IsActive: false, + IsSoV: false, }, expectedErr: nil, }, @@ -159,10 +216,14 @@ func TestParseValidator(t *testing.T) { 0x7e, 0xef, 0xe8, 0x8a, 0x45, 0xfb, 0x7a, 0xc4, 0xb0, 0x59, 0xc9, 0x33, 0x71, 0x0a, 0x57, 0x33, 0xff, 0x9f, 0x4b, 0xab, + // weight + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // start time 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x8D, 0x80, // status 0x01, + // is SoV + 0x01, }, expected: &validatorData{ UpDuration: time.Duration(6000000), @@ -170,6 +231,8 @@ func TestParseValidator(t *testing.T) { NodeID: testNodeID, StartTime: 6000000, IsActive: true, + Weight: 1, + IsSoV: true, }, }, { @@ -231,7 +294,14 @@ func TestStateListener(t *testing.T) { initialStartTime := time.Now() // add initial validator - require.NoError(state.AddValidator(initialvID, initialNodeID, uint64(initialStartTime.Unix()), true)) + require.NoError(state.AddValidator(interfaces.Validator{ + ValidationID: initialvID, + NodeID: initialNodeID, + Weight: 1, + StartTimestamp: uint64(initialStartTime.Unix()), + IsActive: true, + IsSoV: true, + })) // register listener mockListener.EXPECT().OnValidatorAdded(initialvID, initialNodeID, uint64(initialStartTime.Unix()), true) @@ -239,11 +309,23 @@ func TestStateListener(t *testing.T) { // add new validator mockListener.EXPECT().OnValidatorAdded(expectedvID, expectedNodeID, uint64(expectedStartTime.Unix()), true) - require.NoError(state.AddValidator(expectedvID, expectedNodeID, uint64(expectedStartTime.Unix()), true)) + vdr := interfaces.Validator{ + ValidationID: expectedvID, + NodeID: expectedNodeID, + Weight: 1, + StartTimestamp: uint64(expectedStartTime.Unix()), + IsActive: true, + IsSoV: true, + } + require.NoError(state.AddValidator(vdr)) // set status mockListener.EXPECT().OnValidatorStatusUpdated(expectedvID, expectedNodeID, false) - require.NoError(state.SetStatus(expectedvID, false)) + vdr.IsActive = false + require.NoError(state.UpdateValidator(vdr)) + + // set status twice should not trigger listener + require.NoError(state.UpdateValidator(vdr)) // remove validator mockListener.EXPECT().OnValidatorRemoved(expectedvID, expectedNodeID) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 8e169f6d4b..bbf85c0479 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -41,6 +41,10 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/peer" "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/plugin/evm/uptime" + uptimeinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/uptime/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators" + validatorsinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" "github.com/ava-labs/subnet-evm/triedb" "github.com/ava-labs/subnet-evm/triedb/hashdb" @@ -80,10 +84,13 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + avalancheUptime "github.com/ava-labs/avalanchego/snow/uptime" + avalancheValidators "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/profiler" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/components/chain" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" @@ -129,6 +136,8 @@ const ( txGossipThrottlingPeriod = 10 * time.Second txGossipThrottlingLimit = 2 txGossipPollSize = 1 + + loadValidatorsFrequency = 1 * time.Minute ) // Define the API endpoints for the VM @@ -141,11 +150,12 @@ const ( var ( // Set last accepted key to be longer than the keys used to store accepted block IDs. - lastAcceptedKey = []byte("last_accepted_key") - acceptedPrefix = []byte("snowman_accepted") - metadataPrefix = []byte("metadata") - warpPrefix = []byte("warp") - ethDBPrefix = []byte("ethdb") + lastAcceptedKey = []byte("last_accepted_key") + acceptedPrefix = []byte("snowman_accepted") + metadataPrefix = []byte("metadata") + warpPrefix = []byte("warp") + ethDBPrefix = []byte("ethdb") + validatorsDBPrefix = []byte("validators") ) var ( @@ -216,6 +226,8 @@ type VM struct { // set to a prefixDB with the prefix [warpPrefix] warpDB database.Database + validatorsDB database.Database + toEngine chan<- commonEng.Message syntacticBlockValidator BlockValidator @@ -239,7 +251,7 @@ type VM struct { // Metrics sdkMetrics *prometheus.Registry - bootstrapped bool + bootstrapped avalancheUtils.Atomic[bool] logger SubnetEVMLogger // State sync server and client @@ -256,6 +268,9 @@ type VM struct { ethTxPushGossiper avalancheUtils.Atomic[*gossip.PushGossiper[*GossipEthTx]] ethTxPullGossiper gossip.Gossiper + uptimeManager uptimeinterfaces.PausableManager + validatorState validatorsinterfaces.State + chainAlias string // RPC handlers (should be stopped before closing chaindb) rpcHandlers []interface{ Stop() } @@ -491,6 +506,15 @@ func (vm *VM) Initialize( vm.Network = peer.NewNetwork(p2pNetwork, appSender, vm.networkCodec, chainCtx.NodeID, vm.config.MaxOutboundActiveRequests) vm.client = peer.NewNetworkClient(vm.Network) + vm.validatorState, err = validators.NewState(vm.validatorsDB) + if err != nil { + return fmt.Errorf("failed to initialize validator state: %w", err) + } + + // Initialize uptime manager + vm.uptimeManager = uptime.NewPausableManager(avalancheUptime.NewManager(vm.validatorState, &vm.clock)) + vm.validatorState.RegisterListener(vm.uptimeManager) + // Initialize warp backend offchainWarpMessages := make([][]byte, len(vm.config.WarpOffChainMessages)) for i, hexMsg := range vm.config.WarpOffChainMessages { @@ -674,38 +698,64 @@ func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { func (vm *VM) SetState(_ context.Context, state snow.State) error { switch state { case snow.StateSyncing: - vm.bootstrapped = false + vm.bootstrapped.Set(false) return nil case snow.Bootstrapping: - vm.bootstrapped = false - if err := vm.StateSyncClient.Error(); err != nil { - return err - } - // After starting bootstrapping, do not attempt to resume a previous state sync. - if err := vm.StateSyncClient.ClearOngoingSummary(); err != nil { - return err - } - // Ensure snapshots are initialized before bootstrapping (i.e., if state sync is skipped). - // Note calling this function has no effect if snapshots are already initialized. - vm.blockChain.InitializeSnapshots() - return nil + return vm.onBootstrapStarted() case snow.NormalOp: - // Initialize goroutines related to block building once we enter normal operation as there is no need to handle mempool gossip before this point. - if err := vm.initBlockBuilding(); err != nil { - return fmt.Errorf("failed to initialize block building: %w", err) - } - vm.bootstrapped = true - return nil + return vm.onNormalOperationsStarted() default: return snow.ErrUnknownState } } -// initBlockBuilding starts goroutines to manage block building -func (vm *VM) initBlockBuilding() error { +// onBootstrapStarted marks this VM as bootstrapping +func (vm *VM) onBootstrapStarted() error { + vm.bootstrapped.Set(false) + if err := vm.StateSyncClient.Error(); err != nil { + return err + } + // After starting bootstrapping, do not attempt to resume a previous state sync. + if err := vm.StateSyncClient.ClearOngoingSummary(); err != nil { + return err + } + // Ensure snapshots are initialized before bootstrapping (i.e., if state sync is skipped). + // Note calling this function has no effect if snapshots are already initialized. + vm.blockChain.InitializeSnapshots() + + return nil +} + +// onNormalOperationsStarted marks this VM as bootstrapped +func (vm *VM) onNormalOperationsStarted() error { + if vm.bootstrapped.Get() { + return nil + } + vm.bootstrapped.Set(true) + ctx, cancel := context.WithCancel(context.TODO()) vm.cancel = cancel + // update validators first + if err := vm.performValidatorUpdate(ctx); err != nil { + return fmt.Errorf("failed to update validators: %w", err) + } + vdrIDs := vm.validatorState.GetNodeIDs().List() + // Then start tracking with updated validators + // StartTracking initializes the uptime tracking with the known validators + // and update their uptime to account for the time we were being offline. + if err := vm.uptimeManager.StartTracking(vdrIDs); err != nil { + return fmt.Errorf("failed to start tracking uptime: %w", err) + } + // dispatch validator set update + vm.shutdownWg.Add(1) + go func() { + vm.dispatchUpdateValidators(ctx) + vm.shutdownWg.Done() + }() + + // Initialize goroutines related to block building + // once we enter normal operation as there is no need to handle mempool gossip before this point. ethTxGossipMarshaller := GossipEthTxMarshaller{} ethTxGossipClient := vm.Network.NewClient(p2p.TxGossipHandlerID, p2p.WithValidatorSampling(vm.validators)) ethTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, ethTxGossipNamespace) @@ -714,7 +764,7 @@ func (vm *VM) initBlockBuilding() error { } ethTxPool, err := NewGossipEthTxPool(vm.txPool, vm.sdkMetrics) if err != nil { - return err + return fmt.Errorf("failed to initialize gossip eth tx pool: %w", err) } vm.shutdownWg.Add(1) go func() { @@ -772,7 +822,7 @@ func (vm *VM) initBlockBuilding() error { } if err := vm.Network.AddHandler(p2p.TxGossipHandlerID, vm.ethTxGossipHandler); err != nil { - return err + return fmt.Errorf("failed to add eth tx gossip handler: %w", err) } if vm.ethTxPullGossiper == nil { @@ -832,6 +882,15 @@ func (vm *VM) Shutdown(context.Context) error { if vm.cancel != nil { vm.cancel() } + if vm.bootstrapped.Get() { + vdrIDs := vm.validatorState.GetNodeIDs().List() + if err := vm.uptimeManager.StopTracking(vdrIDs); err != nil { + return fmt.Errorf("failed to stop tracking uptime: %w", err) + } + if err := vm.validatorState.WriteState(); err != nil { + return fmt.Errorf("failed to write validator: %w", err) + } + } vm.Network.Shutdown() if err := vm.StateSyncClient.Shutdown(); err != nil { log.Error("error stopping state syncer", "err", err) @@ -1280,12 +1339,15 @@ func (vm *VM) initializeDBs(avaDB database.Database) error { vm.db = versiondb.New(db) vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) - // Note warpDB is not part of versiondb because it is not necessary - // that warp signatures are committed to the database atomically with + // Note warpDB and validatorsDB are not part of versiondb because it is not necessary + // that they are committed to the database atomically with // the last accepted block. // [warpDB] is used to store warp message signatures // set to a prefixDB with the prefix [warpPrefix] vm.warpDB = prefixdb.New(warpPrefix, db) + // [validatorsDB] is used to store the current validator set and uptimes + // set to a prefixDB with the prefix [validatorsDBPrefix] + vm.validatorsDB = prefixdb.New(validatorsDBPrefix, db) return nil } @@ -1344,3 +1406,97 @@ func (vm *VM) createDatabase(dbConfig avalancheNode.DatabaseConfig) (database.Da return db, nil } + +func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { + if err := vm.uptimeManager.Connect(nodeID); err != nil { + return fmt.Errorf("uptime manager failed to connect node %s: %w", nodeID, err) + } + return vm.Network.Connected(ctx, nodeID, version) +} + +func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { + if err := vm.uptimeManager.Disconnect(nodeID); err != nil { + return fmt.Errorf("uptime manager failed to disconnect node %s: %w", nodeID, err) + } + + return vm.Network.Disconnected(ctx, nodeID) +} + +func (vm *VM) dispatchUpdateValidators(ctx context.Context) { + ticker := time.NewTicker(loadValidatorsFrequency) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + vm.ctx.Lock.Lock() + if err := vm.performValidatorUpdate(ctx); err != nil { + log.Error("failed to update validators", "error", err) + } + vm.ctx.Lock.Unlock() + case <-ctx.Done(): + return + } + } +} + +// performValidatorUpdate updates the validator state with the current validator set +// and writes the state to the database. +func (vm *VM) performValidatorUpdate(ctx context.Context) error { + now := time.Now() + log.Debug("performing validator update") + // get current validator set + currentValidatorSet, _, err := vm.ctx.ValidatorState.GetCurrentValidatorSet(ctx, vm.ctx.SubnetID) + if err != nil { + return fmt.Errorf("failed to get current validator set: %w", err) + } + + // load the current validator set into the validator state + if err := loadValidators(vm.validatorState, currentValidatorSet); err != nil { + return fmt.Errorf("failed to load current validators: %w", err) + } + + // write validators to the database + if err := vm.validatorState.WriteState(); err != nil { + return fmt.Errorf("failed to write validator state: %w", err) + } + + // TODO: add metrics + log.Debug("validator update complete", "duration", time.Since(now)) + return nil +} + +// loadValidators loads the [validators] into the validator state [validatorState] +func loadValidators(validatorState validatorsinterfaces.State, newValidators map[ids.ID]*avalancheValidators.GetCurrentValidatorOutput) error { + currentValidationIDs := validatorState.GetValidationIDs() + // first check if we need to delete any existing validators + for vID := range currentValidationIDs { + // if the validator is not in the new set of validators + // delete the validator + if _, exists := newValidators[vID]; !exists { + validatorState.DeleteValidator(vID) + } + } + + // then load the new validators + for newVID, newVdr := range newValidators { + currentVdr := validatorsinterfaces.Validator{ + ValidationID: newVID, + NodeID: newVdr.NodeID, + Weight: newVdr.Weight, + StartTimestamp: newVdr.StartTime, + IsActive: newVdr.IsActive, + IsSoV: newVdr.IsSoV, + } + if currentValidationIDs.Contains(newVID) { + if err := validatorState.UpdateValidator(currentVdr); err != nil { + return err + } + } else { + if err := validatorState.AddValidator(currentVdr); err != nil { + return err + } + } + } + return nil +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 6f1753e62e..55fde2844f 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -34,10 +34,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/enginetest" - "github.com/ava-labs/avalanchego/snow/validators/validatorstest" "github.com/ava-labs/avalanchego/upgrade" - avalancheConstants "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/chain" @@ -63,17 +60,14 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" avagoconstants "github.com/ava-labs/avalanchego/utils/constants" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) var ( - testNetworkID uint32 = avagoconstants.UnitTestID - testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} - testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} - testMinGasPrice int64 = 225_000_000_000 + testNetworkID uint32 = avagoconstants.UnitTestID + + testMinGasPrice int64 = 225_000_000_000 testKeys []*ecdsa.PrivateKey testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i] - testAvaxAssetID = ids.ID{1, 2, 3} username = "Johns" password = "CjasdjhiPeirbSenfeI13" // #nosec G101 @@ -139,40 +133,6 @@ func buildGenesisTest(t *testing.T, genesisJSON string) []byte { return genesisBytes } -func NewContext() *snow.Context { - ctx := utils.TestSnowContext() - ctx.NodeID = ids.GenerateTestNodeID() - ctx.NetworkID = testNetworkID - ctx.ChainID = testCChainID - ctx.AVAXAssetID = testAvaxAssetID - ctx.XChainID = testXChainID - aliaser := ctx.BCLookup.(ids.Aliaser) - _ = aliaser.Alias(testCChainID, "C") - _ = aliaser.Alias(testCChainID, testCChainID.String()) - _ = aliaser.Alias(testXChainID, "X") - _ = aliaser.Alias(testXChainID, testXChainID.String()) - ctx.ValidatorState = &validatorstest.State{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - avalancheConstants.PlatformChainID: avalancheConstants.PrimaryNetworkID, - testXChainID: avalancheConstants.PrimaryNetworkID, - testCChainID: avalancheConstants.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errors.New("unknown chain") - } - return subnetID, nil - }, - } - blsSecretKey, err := bls.NewSecretKey() - if err != nil { - panic(err) - } - ctx.WarpSigner = avalancheWarp.NewSigner(blsSecretKey, ctx.NetworkID, ctx.ChainID) - ctx.PublicKey = bls.PublicFromSecretKey(blsSecretKey) - return ctx -} - // setupGenesis sets up the genesis // If [genesisJSON] is empty, defaults to using [genesisJSONLatest] func setupGenesis( @@ -188,7 +148,7 @@ func setupGenesis( genesisJSON = genesisJSONLatest } genesisBytes := buildGenesisTest(t, genesisJSON) - ctx := NewContext() + ctx := utils.TestSnowContext() baseDB := memdb.New() @@ -494,7 +454,7 @@ func TestBuildEthTxBlock(t *testing.T) { if err := restartedVM.Initialize( context.Background(), - NewContext(), + utils.TestSnowContext(), dbManager, genesisBytes, []byte(""), @@ -3155,7 +3115,7 @@ func TestParentBeaconRootBlock(t *testing.T) { func TestStandaloneDB(t *testing.T) { vm := &VM{} - ctx := NewContext() + ctx := utils.TestSnowContext() baseDB := memdb.New() atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID) diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go index aa44d1d663..f8043e3499 100644 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ b/plugin/evm/vm_upgrade_bytes_test.go @@ -111,6 +111,9 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { } }() // Set the VM's state to NormalOp to initialize the tx pool. + if err := vm.SetState(context.Background(), snow.Bootstrapping); err != nil { + t.Fatal(err) + } if err := vm.SetState(context.Background(), snow.NormalOp); err != nil { t.Fatal(err) } diff --git a/plugin/evm/vm_validators_state_test.go b/plugin/evm/vm_validators_state_test.go new file mode 100644 index 0000000000..8bf8c3546f --- /dev/null +++ b/plugin/evm/vm_validators_state_test.go @@ -0,0 +1,363 @@ +package evm + +import ( + "context" + "testing" + "time" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + commonEng "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/enginetest" + avagoValidators "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/subnet-evm/core" + "github.com/ava-labs/subnet-evm/plugin/evm/validators" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + "github.com/ava-labs/subnet-evm/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestValidatorState(t *testing.T) { + require := require.New(t) + genesis := &core.Genesis{} + require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONLatest))) + genesisJSON, err := genesis.MarshalJSON() + require.NoError(err) + + vm := &VM{} + ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisJSON)) + appSender := &enginetest.Sender{T: t} + appSender.CantSendAppGossip = true + testNodeIDs := []ids.NodeID{ + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + } + testValidationIDs := []ids.ID{ + ids.GenerateTestID(), + ids.GenerateTestID(), + ids.GenerateTestID(), + } + ctx.ValidatorState = &validatorstest.State{ + GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagoValidators.GetCurrentValidatorOutput, uint64, error) { + return map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[2]: { + NodeID: testNodeIDs[2], + PublicKey: nil, + Weight: 1, + }, + }, 0, nil + }, + } + appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } + err = vm.Initialize( + context.Background(), + ctx, + dbManager, + genesisBytes, + []byte(""), + []byte(""), + issuer, + []*commonEng.Fx{}, + appSender, + ) + require.NoError(err, "error initializing GenesisVM") + + // Test case 1: state should not be populated until bootstrapped + require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) + require.Equal(0, vm.validatorState.GetValidationIDs().Len()) + _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) + require.ErrorIs(database.ErrNotFound, err) + require.False(vm.uptimeManager.StartedTracking()) + + // Test case 2: state should be populated after bootstrapped + require.NoError(vm.SetState(context.Background(), snow.NormalOp)) + require.Len(vm.validatorState.GetValidationIDs(), 3) + _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) + require.NoError(err) + require.True(vm.uptimeManager.StartedTracking()) + + // Test case 3: restarting VM should not lose state + vm.Shutdown(context.Background()) + // Shutdown should stop tracking + require.False(vm.uptimeManager.StartedTracking()) + + vm = &VM{} + err = vm.Initialize( + context.Background(), + utils.TestSnowContext(), // this context does not have validators state, making VM to source it from the database + dbManager, + genesisBytes, + []byte(""), + []byte(""), + issuer, + []*commonEng.Fx{}, + appSender, + ) + require.NoError(err, "error initializing GenesisVM") + require.Len(vm.validatorState.GetValidationIDs(), 3) + _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) + require.NoError(err) + require.False(vm.uptimeManager.StartedTracking()) + + // Test case 4: new validators should be added to the state + newValidationID := ids.GenerateTestID() + newNodeID := ids.GenerateTestNodeID() + testState := &validatorstest.State{ + GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagoValidators.GetCurrentValidatorOutput, uint64, error) { + return map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[2]: { + NodeID: testNodeIDs[2], + PublicKey: nil, + Weight: 1, + }, + newValidationID: { + NodeID: newNodeID, + PublicKey: nil, + Weight: 1, + }, + }, 0, nil + }, + } + vm.ctx.ValidatorState = testState + // set VM as bootstrapped + require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) + require.NoError(vm.SetState(context.Background(), snow.NormalOp)) + + // new validator should be added to the state eventually after validatorsLoadFrequency + require.EventuallyWithT(func(c *assert.CollectT) { + assert.Len(c, vm.validatorState.GetNodeIDs(), 4) + newValidator, err := vm.validatorState.GetValidator(newValidationID) + assert.NoError(c, err) + assert.Equal(c, newNodeID, newValidator.NodeID) + }, loadValidatorsFrequency*2, 5*time.Second) +} + +func TestLoadNewValidators(t *testing.T) { + testNodeIDs := []ids.NodeID{ + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + } + testValidationIDs := []ids.ID{ + ids.GenerateTestID(), + ids.GenerateTestID(), + ids.GenerateTestID(), + } + tests := []struct { + name string + initialValidators map[ids.ID]*avagoValidators.GetCurrentValidatorOutput + newValidators map[ids.ID]*avagoValidators.GetCurrentValidatorOutput + registerMockListenerCalls func(*interfaces.MockStateCallbackListener) + expectedLoadErr error + }{ + { + name: "before empty/after empty", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, + registerMockListenerCalls: func(*interfaces.MockStateCallbackListener) {}, + }, + { + name: "before empty/after one", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "before one/after empty", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be removed + mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) + }, + }, + { + name: "no change", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "status and weight change and new one", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + Weight: 1, + }, + }, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: false, + StartTime: 0, + Weight: 2, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be updated + mock.EXPECT().OnValidatorStatusUpdated(testValidationIDs[0], testNodeIDs[0], false).Times(1) + // new validator will be added + mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[1], uint64(0), true).Times(1) + }, + }, + { + name: "renew validation ID", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[1]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be removed + mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) + // new validator will be added + mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "renew node ID", + initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[1], + IsActive: true, + StartTime: 0, + }, + }, + expectedLoadErr: validators.ErrImmutableField, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it won't be called since we don't track the node ID changes + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + require := require.New(tt) + db := memdb.New() + validatorState, err := validators.NewState(db) + require.NoError(err) + + // set initial validators + for vID, validator := range test.initialValidators { + err := validatorState.AddValidator(interfaces.Validator{ + ValidationID: vID, + NodeID: validator.NodeID, + Weight: validator.Weight, + StartTimestamp: validator.StartTime, + IsActive: validator.IsActive, + IsSoV: validator.IsSoV, + }) + require.NoError(err) + } + // enable mock listener + ctrl := gomock.NewController(tt) + mockListener := interfaces.NewMockStateCallbackListener(ctrl) + test.registerMockListenerCalls(mockListener) + + validatorState.RegisterListener(mockListener) + // load new validators + err = loadValidators(validatorState, test.newValidators) + if test.expectedLoadErr != nil { + require.Error(err) + return + } + require.NoError(err) + // check if the state is as expected + require.Equal(len(test.newValidators), validatorState.GetValidationIDs().Len()) + for vID, validator := range test.newValidators { + v, err := validatorState.GetValidator(vID) + require.NoError(err) + require.Equal(validator.NodeID, v.NodeID) + require.Equal(validator.Weight, v.Weight) + require.Equal(validator.StartTime, v.StartTimestamp) + require.Equal(validator.IsActive, v.IsActive) + require.Equal(validator.IsSoV, v.IsSoV) + } + }) + } +} diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index b80af6fdd5..2131b79d94 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -481,7 +481,7 @@ func TestReceiveWarpMessage(t *testing.T) { }, { name: "C-Chain message should be signed by subnet without RequirePrimaryNetworkSigners", - sourceChainID: testCChainID, + sourceChainID: vm.ctx.CChainID, msgFrom: fromPrimary, useSigners: signersSubnet, blockTime: upgrade.InitiallyActiveTime.Add(2 * blockGap), @@ -504,7 +504,7 @@ func TestReceiveWarpMessage(t *testing.T) { }, { name: "C-Chain message should be signed by primary with RequirePrimaryNetworkSigners (impacted)", - sourceChainID: testCChainID, + sourceChainID: vm.ctx.CChainID, msgFrom: fromPrimary, useSigners: signersPrimary, blockTime: reEnableTime.Add(2 * blockGap), diff --git a/scripts/versions.sh b/scripts/versions.sh index 98942e75d2..4c045d5632 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,7 +4,7 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.12'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'4c199890'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} # This won't be used, but it's here to make code syncs easier diff --git a/utils/snow.go b/utils/snow.go index 67c40ee293..00901fbad5 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -4,14 +4,25 @@ package utils import ( + "context" + "errors" + "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +var ( + testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} + testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} + testChainID = ids.ID{'t', 'e', 's', 't', 'c', 'h', 'a', 'i', 'n'} ) func TestSnowContext() *snow.Context { @@ -20,17 +31,56 @@ func TestSnowContext() *snow.Context { panic(err) } pk := bls.PublicFromSecretKey(sk) - return &snow.Context{ - NetworkID: constants.UnitTestID, + networkID := constants.UnitTestID + chainID := testChainID + + ctx := &snow.Context{ + NetworkID: networkID, SubnetID: ids.Empty, - ChainID: ids.Empty, - NodeID: ids.EmptyNodeID, + ChainID: chainID, + NodeID: ids.GenerateTestNodeID(), + XChainID: testXChainID, + CChainID: testCChainID, NetworkUpgrades: upgradetest.GetConfig(upgradetest.Latest), PublicKey: pk, + WarpSigner: warp.NewSigner(sk, networkID, chainID), Log: logging.NoLog{}, BCLookup: ids.NewAliaser(), Metrics: metrics.NewPrefixGatherer(), ChainDataDir: "", - ValidatorState: &validatorstest.State{}, + ValidatorState: NewTestValidatorState(), + } + + aliaser := ctx.BCLookup.(ids.Aliaser) + _ = aliaser.Alias(testCChainID, "C") + _ = aliaser.Alias(testCChainID, testCChainID.String()) + _ = aliaser.Alias(testXChainID, "X") + _ = aliaser.Alias(testXChainID, testXChainID.String()) + + return ctx +} + +func NewTestValidatorState() *validatorstest.State { + return &validatorstest.State{ + GetCurrentHeightF: func(context.Context) (uint64, error) { + return 0, nil + }, + GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { + subnetID, ok := map[ids.ID]ids.ID{ + constants.PlatformChainID: constants.PrimaryNetworkID, + testXChainID: constants.PrimaryNetworkID, + testCChainID: constants.PrimaryNetworkID, + }[chainID] + if !ok { + return ids.Empty, errors.New("unknown chain") + } + return subnetID, nil + }, + GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return map[ids.NodeID]*validators.GetValidatorOutput{}, nil + }, + GetCurrentValidatorSetF: func(context.Context, ids.ID) (map[ids.ID]*validators.GetCurrentValidatorOutput, uint64, error) { + return map[ids.ID]*validators.GetCurrentValidatorOutput{}, 0, nil + }, } } From fb40c50d40b4fbb4778a021782904c16a02a6b71 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Nov 2024 01:23:26 +0300 Subject: [PATCH 03/57] Uptime tracking api (#1374) * add validator state * add pausable uptime manager * remove stuttering name * rename state listener * add uptime tracking to VM * remove unused param * add wg for update validators * update state before network shutdown * restart bootstrapping status in test * add get validator to state * rename uptime to validator * fix mock state * tests * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * use update enum * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * respond to comments * update avalanchego dep branch * reviews * reword errs * fix test changes * fix upgrades after deactivating latest in context * use test branch from avalanchego * use branch commit for ava version * update e2e ava version * update avago dep * remove extra line... * export struct * reviews * conflict fix * custom err msg * add listener mock * bump avago test branch * remove config * remove api changes * Revert "remove api changes" This reverts commit 8ef763fca65a7144ac35b69889873728d3485558. * use non-version db for validatorsDB * remove errs from resume and pause * check after stopping * use expectedTime in tests * reviews * Update plugin/evm/vm.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * fix len * remove log * disable validators api by default * use interfaces from pkgs * improve comments * new fields and refactorings * add new fields * fix linter * clarify comments * update comment * bump to poc branch * enable validators API by default * reviews * update comment * update to avago master --------- Signed-off-by: Ceyhun Onur Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali --- plugin/evm/api.go | 38 ++++++++++++++++++++++ plugin/evm/config.go | 11 ++++--- plugin/evm/service.go | 75 ++++++++++++++++++++++++++++++------------- plugin/evm/vm.go | 11 +++++++ 4 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 plugin/evm/api.go diff --git a/plugin/evm/api.go b/plugin/evm/api.go new file mode 100644 index 0000000000..a8fe61cbc0 --- /dev/null +++ b/plugin/evm/api.go @@ -0,0 +1,38 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// SnowmanAPI introduces snowman specific functionality to the evm +type SnowmanAPI struct{ vm *VM } + +// GetAcceptedFrontReply defines the reply that will be sent from the +// GetAcceptedFront API call +type GetAcceptedFrontReply struct { + Hash common.Hash `json:"hash"` + Number *big.Int `json:"number"` +} + +// GetAcceptedFront returns the last accepted block's hash and height +func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) { + blk := api.vm.blockChain.LastConsensusAcceptedBlock() + return &GetAcceptedFrontReply{ + Hash: blk.Hash(), + Number: blk.Number(), + }, nil +} + +// IssueBlock to the chain +func (api *SnowmanAPI) IssueBlock(ctx context.Context) error { + log.Info("Issuing a new block") + api.vm.builder.signalTxsReady() + return nil +} diff --git a/plugin/evm/config.go b/plugin/evm/config.go index d4a4b44af2..1daa9339a7 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -62,6 +62,7 @@ const ( defaultStateSyncMinBlocks = 300_000 defaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request defaultDBType = pebbledb.Name + defaultValidatorAPIEnabled = true ) type PBool bool @@ -91,10 +92,11 @@ type Config struct { AirdropFile string `json:"airdrop"` // Subnet EVM APIs - SnowmanAPIEnabled bool `json:"snowman-api-enabled"` - AdminAPIEnabled bool `json:"admin-api-enabled"` - AdminAPIDir string `json:"admin-api-dir"` - WarpAPIEnabled bool `json:"warp-api-enabled"` + SnowmanAPIEnabled bool `json:"snowman-api-enabled"` + ValidatorsAPIEnabled bool `json:"validators-api-enabled"` + AdminAPIEnabled bool `json:"admin-api-enabled"` + AdminAPIDir string `json:"admin-api-dir"` + WarpAPIEnabled bool `json:"warp-api-enabled"` // EnabledEthAPIs is a list of Ethereum services that should be enabled // If none is specified, then we use the default list [defaultEnabledAPIs] @@ -297,6 +299,7 @@ func (c *Config) SetDefaults() { c.AllowUnprotectedTxHashes = defaultAllowUnprotectedTxHashes c.AcceptedCacheSize = defaultAcceptedCacheSize c.DatabaseType = defaultDBType + c.ValidatorsAPIEnabled = defaultValidatorAPIEnabled } func (d *Duration) UnmarshalJSON(data []byte) (err error) { diff --git a/plugin/evm/service.go b/plugin/evm/service.go index a8fe61cbc0..3b6959ee59 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -4,35 +4,66 @@ package evm import ( - "context" - "math/big" + "net/http" + "time" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" + "github.com/ava-labs/avalanchego/ids" ) -// SnowmanAPI introduces snowman specific functionality to the evm -type SnowmanAPI struct{ vm *VM } +type ValidatorsAPI struct { + vm *VM +} + +type GetCurrentValidatorsRequest struct { + NodeIDs []ids.NodeID `json:"nodeIDs"` +} -// GetAcceptedFrontReply defines the reply that will be sent from the -// GetAcceptedFront API call -type GetAcceptedFrontReply struct { - Hash common.Hash `json:"hash"` - Number *big.Int `json:"number"` +type GetCurrentValidatorsResponse struct { + Validators []CurrentValidator `json:"validators"` } -// GetAcceptedFront returns the last accepted block's hash and height -func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) { - blk := api.vm.blockChain.LastConsensusAcceptedBlock() - return &GetAcceptedFrontReply{ - Hash: blk.Hash(), - Number: blk.Number(), - }, nil +type CurrentValidator struct { + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTime time.Time `json:"startTime"` + IsActive bool `json:"isActive"` + IsSoV bool `json:"isSoV"` + IsConnected bool `json:"isConnected"` + Uptime time.Duration `json:"uptime"` } -// IssueBlock to the chain -func (api *SnowmanAPI) IssueBlock(ctx context.Context) error { - log.Info("Issuing a new block") - api.vm.builder.signalTxsReady() +func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, reply *GetCurrentValidatorsResponse) error { + api.vm.ctx.Lock.RLock() + defer api.vm.ctx.Lock.RUnlock() + + vIDs := api.vm.validatorState.GetValidationIDs() + + reply.Validators = make([]CurrentValidator, 0, vIDs.Len()) + + for _, vID := range vIDs.List() { + validator, err := api.vm.validatorState.GetValidator(vID) + if err != nil { + return err + } + + isConnected := api.vm.uptimeManager.IsConnected(validator.NodeID) + + uptime, _, err := api.vm.uptimeManager.CalculateUptime(validator.NodeID) + if err != nil { + return err + } + + reply.Validators = append(reply.Validators, CurrentValidator{ + ValidationID: validator.ValidationID, + NodeID: validator.NodeID, + StartTime: validator.StartTime(), + Weight: validator.Weight, + IsActive: validator.IsActive, + IsSoV: validator.IsSoV, + IsConnected: isConnected, + Uptime: time.Duration(uptime.Seconds()), + }) + } return nil } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index bbf85c0479..d937c0e175 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -145,6 +145,7 @@ const ( adminEndpoint = "/admin" ethRPCEndpoint = "/rpc" ethWSEndpoint = "/ws" + validatorsEndpoint = "/validators" ethTxGossipNamespace = "eth_tx_gossip" ) @@ -1087,6 +1088,16 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { enabledAPIs = append(enabledAPIs, "subnet-evm-admin") } + if vm.config.ValidatorsAPIEnabled { + validatorsAPI, err := newHandler("validators", &ValidatorsAPI{vm}) + if err != nil { + return nil, fmt.Errorf("failed to register service for validators API due to %w", err) + } + apis[validatorsEndpoint] = validatorsAPI + enabledAPIs = append(enabledAPIs, "validators") + } + + // RPC APIs if vm.config.SnowmanAPIEnabled { if err := handler.RegisterName("snowman", &SnowmanAPI{vm}); err != nil { return nil, err From f1d549caeaabbbe1096ae001ef6ad18e9b752775 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 14 Nov 2024 01:43:56 +0300 Subject: [PATCH 04/57] Sign validator uptime warp msg (#1367) * Bump avalanchego to master * always sign uptime messages (testing branch) * nits * cleanup * assign to correct `err` * fix handler * move ValidatorUptime type to subnet-evm * disable always signing * implement on the type itself * remove unneeded code * fix ut * add validator state * add pausable uptime manager * remove stuttering name * rename state listener * add uptime tracking to VM * remove unused param * add wg for update validators * update state before network shutdown * restart bootstrapping status in test * add get validator to state * rename uptime to validator * fix mock state * tests * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * use update enum * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * Update plugin/evm/validators/state.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * respond to comments * update avalanchego dep branch * reviews * reword errs * fix test changes * fix upgrades after deactivating latest in context * use test branch from avalanchego * use branch commit for ava version * update e2e ava version * update avago dep * remove extra line... * export struct * implement acp118 signer and verifier * avoid revalidating in sign * refactor warp backend to use acp118 handler * prune warp db before backend init * add cache tests * remove uptime msg type * add cache test * fix linter * add validator uptimes * bump avago getcurrentvalidators branch * rename get validator IDs to NodeIDs * sign uptime warp msg base on uptime calculator * add tests * reviews * conflict fix * custom err msg * add listener mock * bump avago test branch * remove config * remove api changes * Revert "remove api changes" This reverts commit 8ef763fca65a7144ac35b69889873728d3485558. * remove wrapped cache * use non-version db for validatorsDB * remove errs from resume and pause * check after stopping * use expectedTime in tests * reviews * Update plugin/evm/vm.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * fix len * fix tests * update avago branch * use ctx from utils * add empty check for source address * nits * remove log * disable validators api by default * fix test context * use interfaces from pkgs * improve comments * Uptime validation nits (#1378) * add uptime warp example * remove log * nit unused interface * add weight and isSov as fields * use validator struct in AddValidator * add comments to example file * fix test * add new fields to tests --------- Signed-off-by: Ceyhun Onur * Update plugin/evm/validators/state.go Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> Signed-off-by: Ceyhun Onur * pass locker * rename addresscall verifier fn * new fields and refactorings * add new fields * merge nits * fix linter * clarify comments * update comment * remove getnodeID * bump to poc branch * enable validators API by default * reviews * reviews * update comment * update to avago master * revert test change --------- Signed-off-by: Ceyhun Onur Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> --- examples/sign-uptime-message/main.go | 124 ++++++++++++++++++ .../validators/validatorstest/noop_state.go | 64 +++++++++ plugin/evm/vm.go | 3 + warp/backend.go | 14 +- warp/backend_test.go | 20 ++- warp/handlers/signature_request_test.go | 7 +- warp/messages/payload.go | 6 - warp/messages/validator_uptime.go | 2 +- warp/verifier_backend.go | 82 +++++++++++- warp/verifier_backend_test.go | 108 ++++++++++++++- warp/verifier_stats.go | 24 +++- 11 files changed, 428 insertions(+), 26 deletions(-) create mode 100644 examples/sign-uptime-message/main.go create mode 100644 plugin/evm/validators/validatorstest/noop_state.go diff --git a/examples/sign-uptime-message/main.go b/examples/sign-uptime-message/main.go new file mode 100644 index 0000000000..94929bc2da --- /dev/null +++ b/examples/sign-uptime-message/main.go @@ -0,0 +1,124 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "log" + "net/netip" + "time" + + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/protobuf/proto" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/peer" + "github.com/ava-labs/avalanchego/proto/pb/sdk" + "github.com/ava-labs/avalanchego/snow/networking/router" + "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/subnet-evm/warp/messages" + + p2pmessage "github.com/ava-labs/avalanchego/message" +) + +// An example application demonstrating how to request a signature for +// an uptime message from a node running locally. +func main() { + uri := primary.LocalAPIURI + // The following IDs are placeholders and should be replaced with real values + // before running the code. + // The validationID is for the validation period that the uptime message is signed for. + validationID := ids.FromStringOrPanic("p3NUAY4PbcAnyCyvUTjGVjezNEQCdnVdfAbJcZScvKpxP5tJr") + // The sourceChainID is the ID of the chain. + sourceChainID := ids.FromStringOrPanic("2UZWB4xjNadRcHSpXarQoCryiVdcGWoT5w1dUztNfMKkAd2hJX") + reqUptime := uint64(3486) + infoClient := info.NewClient(uri) + networkID, err := infoClient.GetNetworkID(context.Background()) + if err != nil { + log.Fatalf("failed to fetch network ID: %s\n", err) + } + + validatorUptime, err := messages.NewValidatorUptime(validationID, reqUptime) + if err != nil { + log.Fatalf("failed to create validatorUptime message: %s\n", err) + } + + addressedCall, err := payload.NewAddressedCall( + nil, + validatorUptime.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create AddressedCall message: %s\n", err) + } + + unsignedWarp, err := warp.NewUnsignedMessage( + networkID, + sourceChainID, + addressedCall.Bytes(), + ) + if err != nil { + log.Fatalf("failed to create unsigned Warp message: %s\n", err) + } + + p, err := peer.StartTestPeer( + context.Background(), + netip.AddrPortFrom( + netip.AddrFrom4([4]byte{127, 0, 0, 1}), + 9651, + ), + networkID, + router.InboundHandlerFunc(func(_ context.Context, msg p2pmessage.InboundMessage) { + log.Printf("received %s: %s", msg.Op(), msg.Message()) + }), + ) + if err != nil { + log.Fatalf("failed to start peer: %s\n", err) + } + + messageBuilder, err := p2pmessage.NewCreator( + logging.NoLog{}, + prometheus.NewRegistry(), + compression.TypeZstd, + time.Hour, + ) + if err != nil { + log.Fatalf("failed to create message builder: %s\n", err) + } + + appRequestPayload, err := proto.Marshal(&sdk.SignatureRequest{ + Message: unsignedWarp.Bytes(), + }) + if err != nil { + log.Fatalf("failed to marshal SignatureRequest: %s\n", err) + } + + appRequest, err := messageBuilder.AppRequest( + sourceChainID, + 0, + time.Hour, + p2p.PrefixMessage( + p2p.ProtocolPrefix(p2p.SignatureRequestHandlerID), + appRequestPayload, + ), + ) + if err != nil { + log.Fatalf("failed to create AppRequest: %s\n", err) + } + + p.Send(context.Background(), appRequest) + + time.Sleep(5 * time.Second) + + p.StartClose() + err = p.AwaitClosed(context.Background()) + if err != nil { + log.Fatalf("failed to close peer: %s\n", err) + } +} diff --git a/plugin/evm/validators/validatorstest/noop_state.go b/plugin/evm/validators/validatorstest/noop_state.go new file mode 100644 index 0000000000..3594999574 --- /dev/null +++ b/plugin/evm/validators/validatorstest/noop_state.go @@ -0,0 +1,64 @@ +package validatorstest + +import ( + "time" + + ids "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" +) + +var NoOpState interfaces.State = &noOpState{} + +type noOpState struct{} + +func (n *noOpState) GetStatus(vID ids.ID) (bool, error) { return false, nil } + +func (n *noOpState) GetValidationIDs() set.Set[ids.ID] { return set.NewSet[ids.ID](0) } + +func (n *noOpState) GetNodeIDs() set.Set[ids.NodeID] { return set.NewSet[ids.NodeID](0) } + +func (n *noOpState) GetValidator(vID ids.ID) (interfaces.Validator, error) { + return interfaces.Validator{}, nil +} + +func (n *noOpState) GetNodeID(vID ids.ID) (ids.NodeID, error) { return ids.NodeID{}, nil } + +func (n *noOpState) AddValidator(vdr interfaces.Validator) error { + return nil +} + +func (n *noOpState) UpdateValidator(vdr interfaces.Validator) error { + return nil +} + +func (n *noOpState) DeleteValidator(vID ids.ID) error { + return nil +} +func (n *noOpState) WriteState() error { return nil } + +func (n *noOpState) SetStatus(vID ids.ID, isActive bool) error { return nil } + +func (n *noOpState) SetWeight(vID ids.ID, newWeight uint64) error { return nil } + +func (n *noOpState) RegisterListener(interfaces.StateCallbackListener) {} + +func (n *noOpState) GetUptime( + nodeID ids.NodeID, +) (upDuration time.Duration, lastUpdated time.Time, err error) { + return 0, time.Time{}, nil +} + +func (n *noOpState) SetUptime( + nodeID ids.NodeID, + upDuration time.Duration, + lastUpdated time.Time, +) error { + return nil +} + +func (n *noOpState) GetStartTime( + nodeID ids.NodeID, +) (startTime time.Time, err error) { + return time.Time{}, nil +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index d937c0e175..ee0bf345ef 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -539,6 +539,9 @@ func (vm *VM) Initialize( vm.ctx.ChainID, vm.ctx.WarpSigner, vm, + vm.uptimeManager, + vm.validatorState, + vm.ctx.Lock.RLocker(), vm.warpDB, meteredCache, offchainWarpMessages, diff --git a/warp/backend.go b/warp/backend.go index 6e1f6a9553..eb38f1f410 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -7,14 +7,17 @@ import ( "context" "errors" "fmt" + "sync" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/snow/uptime" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" "github.com/ethereum/go-ethereum/log" ) @@ -56,6 +59,9 @@ type backend struct { db database.Database warpSigner avalancheWarp.Signer blockClient BlockClient + uptimeCalculator uptime.Calculator + validatorState interfaces.State + stateLock sync.Locker signatureCache cache.Cacher[ids.ID, []byte] messageCache *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage] offchainAddressedCallMsgs map[ids.ID]*avalancheWarp.UnsignedMessage @@ -68,6 +74,9 @@ func NewBackend( sourceChainID ids.ID, warpSigner avalancheWarp.Signer, blockClient BlockClient, + uptimeCalculator uptime.Calculator, + validatorsState interfaces.State, + stateLock sync.Locker, db database.Database, signatureCache cache.Cacher[ids.ID, []byte], offchainMessages [][]byte, @@ -79,6 +88,9 @@ func NewBackend( warpSigner: warpSigner, blockClient: blockClient, signatureCache: signatureCache, + uptimeCalculator: uptimeCalculator, + validatorState: validatorsState, + stateLock: stateLock, messageCache: &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: messageCacheSize}, stats: newVerifierStats(), offchainAddressedCallMsgs: make(map[ids.ID]*avalancheWarp.UnsignedMessage), @@ -180,7 +192,7 @@ func (b *backend) GetMessage(messageID ids.ID) (*avalancheWarp.UnsignedMessage, unsignedMessageBytes, err := b.db.Get(messageID[:]) if err != nil { - return nil, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err) + return nil, err } unsignedMessage, err := avalancheWarp.ParseUnsignedMessage(unsignedMessageBytes) diff --git a/warp/backend_test.go b/warp/backend_test.go index cae9c14bc6..331bd7c1ff 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -5,15 +5,19 @@ package warp import ( "context" + "sync" "testing" "github.com/ava-labs/avalanchego/cache" + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" "github.com/ava-labs/subnet-evm/warp/warptest" "github.com/stretchr/testify/require" ) @@ -44,7 +48,7 @@ func TestAddAndGetValidMessage(t *testing.T) { require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) require.NoError(t, err) // Add testUnsignedMessage to the warp backend @@ -67,7 +71,7 @@ func TestAddAndGetUnknownMessage(t *testing.T) { require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) require.NoError(t, err) // Try getting a signature for a message that was not added. @@ -86,7 +90,7 @@ func TestGetBlockSignature(t *testing.T) { require.NoError(err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) require.NoError(err) blockHashPayload, err := payload.NewHash(blkID) @@ -113,7 +117,7 @@ func TestZeroSizedCache(t *testing.T) { // Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0. messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) require.NoError(t, err) // Add testUnsignedMessage to the warp backend @@ -157,6 +161,12 @@ func TestOffChainMessages(t *testing.T) { require.Equal(expectedSignatureBytes, signature[:]) }, }, + "unknown message": { + check: func(require *require.Assertions, b Backend) { + _, err := b.GetMessage(testUnsignedMessage.ID()) + require.ErrorIs(err, database.ErrNotFound) + }, + }, "invalid message": { offchainMessages: [][]byte{{1, 2, 3}}, err: errParsingOffChainMessage, @@ -167,7 +177,7 @@ func TestOffChainMessages(t *testing.T) { db := memdb.New() messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, db, messageSignatureCache, test.offchainMessages) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, test.offchainMessages) require.ErrorIs(err, test.err) if test.check != nil { test.check(require, backend) diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index 3189478106..af664d5f16 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -10,10 +10,12 @@ import ( "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/warp" "github.com/ava-labs/subnet-evm/warp/warptest" @@ -33,7 +35,7 @@ func TestMessageSignatureHandler(t *testing.T) { require.NoError(t, err) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 100} - backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, messageSignatureCache, [][]byte{offchainMessage.Bytes()}) + backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptime.NoOpCalculator, validatorstest.NoOpState, snowCtx.Lock.RLocker(), database, messageSignatureCache, [][]byte{offchainMessage.Bytes()}) require.NoError(t, err) msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, []byte("test")) @@ -139,6 +141,9 @@ func TestBlockSignatureHandler(t *testing.T) { snowCtx.ChainID, warpSigner, blockClient, + uptime.NoOpCalculator, + validatorstest.NoOpState, + snowCtx.Lock.RLocker(), database, messageSignatureCache, nil, diff --git a/warp/messages/payload.go b/warp/messages/payload.go index 3776a1356d..facf54524d 100644 --- a/warp/messages/payload.go +++ b/warp/messages/payload.go @@ -20,12 +20,6 @@ type Payload interface { initialize(b []byte) } -// Signable is an optional interface that payloads can implement to allow -// on-the-fly signing of incoming messages by the warp backend. -type Signable interface { - VerifyMesssage(sourceAddress []byte) error -} - func Parse(bytes []byte) (Payload, error) { var payload Payload if _, err := Codec.Unmarshal(bytes, &payload); err != nil { diff --git a/warp/messages/validator_uptime.go b/warp/messages/validator_uptime.go index 3d3e4dd5dd..cd14b39538 100644 --- a/warp/messages/validator_uptime.go +++ b/warp/messages/validator_uptime.go @@ -13,7 +13,7 @@ import ( // has been up for TotalUptime seconds. type ValidatorUptime struct { ValidationID ids.ID `serialize:"true"` - TotalUptime uint64 `serialize:"true"` + TotalUptime uint64 `serialize:"true"` // in seconds bytes []byte } diff --git a/warp/verifier_backend.go b/warp/verifier_backend.go index c70563c585..71a33356cc 100644 --- a/warp/verifier_backend.go +++ b/warp/verifier_backend.go @@ -7,6 +7,9 @@ import ( "context" "fmt" + "github.com/ava-labs/subnet-evm/warp/messages" + + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/snow/engine/common" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -24,6 +27,11 @@ func (b *backend) Verify(ctx context.Context, unsignedMessage *avalancheWarp.Uns // Known on-chain messages should be signed if _, err := b.GetMessage(messageID); err == nil { return nil + } else if err != database.ErrNotFound { + return &common.AppError{ + Code: ParseErrCode, + Message: fmt.Sprintf("failed to get message %s: %s", messageID, err.Error()), + } } parsed, err := payload.Parse(unsignedMessage.Payload) @@ -36,6 +44,8 @@ func (b *backend) Verify(ctx context.Context, unsignedMessage *avalancheWarp.Uns } switch p := parsed.(type) { + case *payload.AddressedCall: + return b.verifyOffchainAddressedCall(p) case *payload.Hash: return b.verifyBlockMessage(ctx, p) default: @@ -53,7 +63,7 @@ func (b *backend) verifyBlockMessage(ctx context.Context, blockHashPayload *payl blockID := blockHashPayload.Hash _, err := b.blockClient.GetAcceptedBlock(ctx, blockID) if err != nil { - b.stats.IncBlockSignatureValidationFail() + b.stats.IncBlockValidationFail() return &common.AppError{ Code: VerifyErrCode, Message: fmt.Sprintf("failed to get block %s: %s", blockID, err.Error()), @@ -62,3 +72,73 @@ func (b *backend) verifyBlockMessage(ctx context.Context, blockHashPayload *payl return nil } + +// verifyOffchainAddressedCall verifies the addressed call message +func (b *backend) verifyOffchainAddressedCall(addressedCall *payload.AddressedCall) *common.AppError { + // Further, parse the payload to see if it is a known type. + parsed, err := messages.Parse(addressedCall.Payload) + if err != nil { + b.stats.IncMessageParseFail() + return &common.AppError{ + Code: ParseErrCode, + Message: "failed to parse addressed call message: " + err.Error(), + } + } + + if len(addressedCall.SourceAddress) != 0 { + return &common.AppError{ + Code: VerifyErrCode, + Message: "source address should be empty for offchain addressed messages", + } + } + + switch p := parsed.(type) { + case *messages.ValidatorUptime: + if err := b.verifyUptimeMessage(p); err != nil { + b.stats.IncUptimeValidationFail() + return err + } + default: + b.stats.IncMessageParseFail() + return &common.AppError{ + Code: ParseErrCode, + Message: fmt.Sprintf("unknown message type: %T", p), + } + } + + return nil +} + +func (b *backend) verifyUptimeMessage(uptimeMsg *messages.ValidatorUptime) *common.AppError { + b.stateLock.Lock() + defer b.stateLock.Unlock() + // first get the validator's nodeID + vdr, err := b.validatorState.GetValidator(uptimeMsg.ValidationID) + if err != nil { + return &common.AppError{ + Code: VerifyErrCode, + Message: fmt.Sprintf("failed to get validator for validationID %s: %s", uptimeMsg.ValidationID, err.Error()), + } + } + nodeID := vdr.NodeID + + // then get the current uptime + currentUptime, _, err := b.uptimeCalculator.CalculateUptime(nodeID) + if err != nil { + return &common.AppError{ + Code: VerifyErrCode, + Message: fmt.Sprintf("failed to calculate uptime for nodeID %s: %s", nodeID, err.Error()), + } + } + + currentUptimeSeconds := uint64(currentUptime.Seconds()) + // verify the current uptime against the total uptime in the message + if currentUptimeSeconds < uptimeMsg.TotalUptime { + return &common.AppError{ + Code: VerifyErrCode, + Message: fmt.Sprintf("current uptime %d is less than queried uptime %d for nodeID %s", currentUptimeSeconds, uptimeMsg.TotalUptime, nodeID), + } + } + + return nil +} diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index 54fd8dbf19..a3546f60c7 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -14,10 +14,16 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/timer/mockable" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" + "github.com/ava-labs/subnet-evm/plugin/evm/validators" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" "github.com/ava-labs/subnet-evm/utils" + "github.com/ava-labs/subnet-evm/warp/messages" "github.com/ava-labs/subnet-evm/warp/warptest" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -56,7 +62,7 @@ func TestAddressedCallSignatures(t *testing.T) { }, verifyStats: func(t *testing.T, stats *verifierStats) { require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count()) - require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count()) + require.EqualValues(t, 0, stats.blockValidationFail.Snapshot().Count()) }, }, "offchain message": { @@ -65,7 +71,7 @@ func TestAddressedCallSignatures(t *testing.T) { }, verifyStats: func(t *testing.T, stats *verifierStats) { require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count()) - require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count()) + require.EqualValues(t, 0, stats.blockValidationFail.Snapshot().Count()) }, }, "unknown message": { @@ -78,7 +84,7 @@ func TestAddressedCallSignatures(t *testing.T) { }, verifyStats: func(t *testing.T, stats *verifierStats) { require.EqualValues(t, 1, stats.messageParseFail.Snapshot().Count()) - require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count()) + require.EqualValues(t, 0, stats.blockValidationFail.Snapshot().Count()) }, err: &common.AppError{Code: ParseErrCode}, }, @@ -98,7 +104,7 @@ func TestAddressedCallSignatures(t *testing.T) { } else { sigCache = &cache.Empty[ids.ID, []byte]{} } - warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, database, sigCache, [][]byte{offchainMessage.Bytes()}) + warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptime.NoOpCalculator, validatorstest.NoOpState, snowCtx.Lock.RLocker(), database, sigCache, [][]byte{offchainMessage.Bytes()}) require.NoError(t, err) handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner) @@ -177,7 +183,7 @@ func TestBlockSignatures(t *testing.T) { return toMessageBytes(knownBlkID), signature[:] }, verifyStats: func(t *testing.T, stats *verifierStats) { - require.EqualValues(t, 0, stats.blockSignatureValidationFail.Snapshot().Count()) + require.EqualValues(t, 0, stats.blockValidationFail.Snapshot().Count()) require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count()) }, }, @@ -187,7 +193,7 @@ func TestBlockSignatures(t *testing.T) { return toMessageBytes(unknownBlockID), nil }, verifyStats: func(t *testing.T, stats *verifierStats) { - require.EqualValues(t, 1, stats.blockSignatureValidationFail.Snapshot().Count()) + require.EqualValues(t, 1, stats.blockValidationFail.Snapshot().Count()) require.EqualValues(t, 0, stats.messageParseFail.Snapshot().Count()) }, err: &common.AppError{Code: VerifyErrCode}, @@ -213,6 +219,9 @@ func TestBlockSignatures(t *testing.T) { snowCtx.ChainID, warpSigner, blockClient, + uptime.NoOpCalculator, + validatorstest.NoOpState, + snowCtx.Lock.RLocker(), database, sigCache, nil, @@ -253,3 +262,90 @@ func TestBlockSignatures(t *testing.T) { } } } + +func TestUptimeSignatures(t *testing.T) { + database := memdb.New() + snowCtx := utils.TestSnowContext() + blsSecretKey, err := bls.NewSecretKey() + require.NoError(t, err) + warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) + + getUptimeMessageBytes := func(sourceAddress []byte, vID ids.ID, totalUptime uint64) ([]byte, *avalancheWarp.UnsignedMessage) { + uptimePayload, err := messages.NewValidatorUptime(vID, 80) + require.NoError(t, err) + addressedCall, err := payload.NewAddressedCall(sourceAddress, uptimePayload.Bytes()) + require.NoError(t, err) + unsignedMessage, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, addressedCall.Bytes()) + require.NoError(t, err) + + protoMsg := &sdk.SignatureRequest{Message: unsignedMessage.Bytes()} + protoBytes, err := proto.Marshal(protoMsg) + require.NoError(t, err) + return protoBytes, unsignedMessage + } + + for _, withCache := range []bool{true, false} { + var sigCache cache.Cacher[ids.ID, []byte] + if withCache { + sigCache = &cache.LRU[ids.ID, []byte]{Size: 100} + } else { + sigCache = &cache.Empty[ids.ID, []byte]{} + } + state, err := validators.NewState(memdb.New()) + require.NoError(t, err) + clk := &mockable.Clock{} + uptimeManager := uptime.NewManager(state, clk) + uptimeManager.StartTracking([]ids.NodeID{}) + warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptimeManager, state, snowCtx.Lock.RLocker(), database, sigCache, nil) + require.NoError(t, err) + handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner) + + // sourceAddress nonZero + protoBytes, _ := getUptimeMessageBytes([]byte{1, 2, 3}, ids.GenerateTestID(), 80) + _, appErr := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, protoBytes) + require.ErrorIs(t, appErr, &common.AppError{Code: VerifyErrCode}) + require.Contains(t, appErr.Error(), "source address should be empty") + + // not existing validationID + vID := ids.GenerateTestID() + protoBytes, _ = getUptimeMessageBytes([]byte{}, vID, 80) + _, appErr = handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, protoBytes) + require.ErrorIs(t, appErr, &common.AppError{Code: VerifyErrCode}) + require.Contains(t, appErr.Error(), "failed to get validator") + + // uptime is less than requested (not connected) + validationID := ids.GenerateTestID() + nodeID := ids.GenerateTestNodeID() + require.NoError(t, state.AddValidator(interfaces.Validator{ + ValidationID: validationID, + NodeID: nodeID, + Weight: 1, + StartTimestamp: clk.Unix(), + IsActive: true, + IsSoV: true, + })) + protoBytes, _ = getUptimeMessageBytes([]byte{}, validationID, 80) + _, appErr = handler.AppRequest(context.Background(), nodeID, time.Time{}, protoBytes) + require.ErrorIs(t, appErr, &common.AppError{Code: VerifyErrCode}) + require.Contains(t, appErr.Error(), "current uptime 0 is less than queried uptime 80") + + // uptime is less than requested (not enough) + require.NoError(t, uptimeManager.Connect(nodeID)) + clk.Set(clk.Time().Add(40 * time.Second)) + protoBytes, _ = getUptimeMessageBytes([]byte{}, validationID, 80) + _, appErr = handler.AppRequest(context.Background(), nodeID, time.Time{}, protoBytes) + require.ErrorIs(t, appErr, &common.AppError{Code: VerifyErrCode}) + require.Contains(t, appErr.Error(), "current uptime 40 is less than queried uptime 80") + + // valid uptime + clk.Set(clk.Time().Add(40 * time.Second)) + protoBytes, msg := getUptimeMessageBytes([]byte{}, validationID, 80) + responseBytes, appErr := handler.AppRequest(context.Background(), nodeID, time.Time{}, protoBytes) + require.Nil(t, appErr) + expectedSignature, err := warpSigner.Sign(msg) + require.NoError(t, err) + response := &sdk.SignatureResponse{} + require.NoError(t, proto.Unmarshal(responseBytes, response)) + require.Equal(t, expectedSignature[:], response.Signature) + } +} diff --git a/warp/verifier_stats.go b/warp/verifier_stats.go index bc56d725a5..d1ef62a50f 100644 --- a/warp/verifier_stats.go +++ b/warp/verifier_stats.go @@ -9,21 +9,35 @@ import ( type verifierStats struct { messageParseFail metrics.Counter + // AddressedCall metrics + addressedCallValidationFail metrics.Counter // BlockRequest metrics - blockSignatureValidationFail metrics.Counter + blockValidationFail metrics.Counter + // Uptime metrics + uptimeValidationFail metrics.Counter } func newVerifierStats() *verifierStats { return &verifierStats{ - messageParseFail: metrics.NewRegisteredCounter("message_parse_fail", nil), - blockSignatureValidationFail: metrics.NewRegisteredCounter("block_signature_validation_fail", nil), + messageParseFail: metrics.NewRegisteredCounter("warp_backend_message_parse_fail", nil), + addressedCallValidationFail: metrics.NewRegisteredCounter("warp_backend_addressed_call_validation_fail", nil), + blockValidationFail: metrics.NewRegisteredCounter("warp_backend_block_validation_fail", nil), + uptimeValidationFail: metrics.NewRegisteredCounter("warp_backend_uptime_validation_fail", nil), } } -func (h *verifierStats) IncBlockSignatureValidationFail() { - h.blockSignatureValidationFail.Inc(1) +func (h *verifierStats) IncAddressedCallValidationFail() { + h.addressedCallValidationFail.Inc(1) +} + +func (h *verifierStats) IncBlockValidationFail() { + h.blockValidationFail.Inc(1) } func (h *verifierStats) IncMessageParseFail() { h.messageParseFail.Inc(1) } + +func (h *verifierStats) IncUptimeValidationFail() { + h.uptimeValidationFail.Inc(1) +} From f4e6b05fc665ccd116cfa944523b3ff424f8b3d2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 15 Nov 2024 00:46:57 +0300 Subject: [PATCH 05/57] Use startimestamp (#1386) * fix start timestamp * add client --- plugin/evm/client.go | 25 +++++++++++++++++-------- plugin/evm/service.go | 36 ++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/plugin/evm/client.go b/plugin/evm/client.go index b91618fc4c..f87507da56 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client.go @@ -24,26 +24,28 @@ type Client interface { LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) + GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) } // Client implementation for interacting with EVM [chain] type client struct { - adminRequester rpc.EndpointRequester + adminRequester rpc.EndpointRequester + validatorsRequester rpc.EndpointRequester } // NewClient returns a Client for interacting with EVM [chain] func NewClient(uri, chain string) Client { + requestUri := fmt.Sprintf("%s/ext/bc/%s", uri, chain) return &client{ - adminRequester: rpc.NewEndpointRequester(fmt.Sprintf("%s/ext/bc/%s/admin", uri, chain)), + adminRequester: rpc.NewEndpointRequester( + requestUri + "/admin", + ), + validatorsRequester: rpc.NewEndpointRequester( + requestUri + "/validators", + ), } } -// NewCChainClient returns a Client for interacting with the C Chain -func NewCChainClient(uri string) Client { - // TODO: Update for Subnet-EVM compatibility - return NewClient(uri, "C") -} - func (c *client) StartCPUProfiler(ctx context.Context, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.startCPUProfiler", struct{}{}, &api.EmptyReply{}, options...) } @@ -73,3 +75,10 @@ func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Confi err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) return res.Config, err } + +// GetCurrentValidators returns the current validators +func (c *client) GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) { + res := &GetCurrentValidatorsResponse{} + err := c.validatorsRequester.SendRequest(ctx, "validators.getCurrentValidators", struct{}{}, res, options...) + return res.Validators, err +} diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 3b6959ee59..1a7b285bfe 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -14,23 +14,19 @@ type ValidatorsAPI struct { vm *VM } -type GetCurrentValidatorsRequest struct { - NodeIDs []ids.NodeID `json:"nodeIDs"` -} - type GetCurrentValidatorsResponse struct { Validators []CurrentValidator `json:"validators"` } type CurrentValidator struct { - ValidationID ids.ID `json:"validationID"` - NodeID ids.NodeID `json:"nodeID"` - Weight uint64 `json:"weight"` - StartTime time.Time `json:"startTime"` - IsActive bool `json:"isActive"` - IsSoV bool `json:"isSoV"` - IsConnected bool `json:"isConnected"` - Uptime time.Duration `json:"uptime"` + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTimestamp uint64 `json:"startTimestamp"` + IsActive bool `json:"isActive"` + IsSoV bool `json:"isSoV"` + IsConnected bool `json:"isConnected"` + Uptime time.Duration `json:"uptime"` } func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, reply *GetCurrentValidatorsResponse) error { @@ -55,14 +51,14 @@ func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, rep } reply.Validators = append(reply.Validators, CurrentValidator{ - ValidationID: validator.ValidationID, - NodeID: validator.NodeID, - StartTime: validator.StartTime(), - Weight: validator.Weight, - IsActive: validator.IsActive, - IsSoV: validator.IsSoV, - IsConnected: isConnected, - Uptime: time.Duration(uptime.Seconds()), + ValidationID: validator.ValidationID, + NodeID: validator.NodeID, + StartTimestamp: validator.StartTimestamp, + Weight: validator.Weight, + IsActive: validator.IsActive, + IsSoV: validator.IsSoV, + IsConnected: isConnected, + Uptime: time.Duration(uptime.Seconds()), }) } return nil From cc414c8cd0fc36826a5cdfe60a1683bfd448fb72 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 15 Nov 2024 18:12:10 +0300 Subject: [PATCH 06/57] add db inspect (#1382) * add db inspect * Update plugin/evm/database.go Co-authored-by: Darioush Jalali Signed-off-by: Ceyhun Onur * reduce db code from vm --------- Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali --- plugin/evm/database/database.go | 79 ++++++++ .../wrapped_database.go} | 32 ++-- plugin/evm/syncervm_test.go | 7 +- plugin/evm/vm.go | 168 +---------------- plugin/evm/vm_database.go | 171 ++++++++++++++++++ 5 files changed, 273 insertions(+), 184 deletions(-) create mode 100644 plugin/evm/database/database.go rename plugin/evm/{database.go => database/wrapped_database.go} (55%) create mode 100644 plugin/evm/vm_database.go diff --git a/plugin/evm/database/database.go b/plugin/evm/database/database.go new file mode 100644 index 0000000000..536bb9c36a --- /dev/null +++ b/plugin/evm/database/database.go @@ -0,0 +1,79 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package database + +import ( + "fmt" + "path/filepath" + + "github.com/ava-labs/avalanchego/api/metrics" + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/leveldb" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/meterdb" + "github.com/ava-labs/avalanchego/database/pebbledb" + "github.com/ava-labs/avalanchego/database/versiondb" + avalanchenode "github.com/ava-labs/avalanchego/node" + "github.com/ava-labs/avalanchego/utils/logging" +) + +const ( + dbMetricsPrefix = "db" +) + +// createDatabase returns a new database instance with the provided configuration +func NewStandaloneDatabase(dbConfig avalanchenode.DatabaseConfig, gatherer metrics.MultiGatherer, logger logging.Logger) (database.Database, error) { + dbRegisterer, err := metrics.MakeAndRegister( + gatherer, + dbMetricsPrefix, + ) + if err != nil { + return nil, err + } + var db database.Database + // start the db + switch dbConfig.Name { + case leveldb.Name: + dbPath := filepath.Join(dbConfig.Path, leveldb.Name) + db, err = leveldb.New(dbPath, dbConfig.Config, logger, dbRegisterer) + if err != nil { + return nil, fmt.Errorf("couldn't create %s at %s: %w", leveldb.Name, dbPath, err) + } + case memdb.Name: + db = memdb.New() + case pebbledb.Name: + dbPath := filepath.Join(dbConfig.Path, pebbledb.Name) + db, err = pebbledb.New(dbPath, dbConfig.Config, logger, dbRegisterer) + if err != nil { + return nil, fmt.Errorf("couldn't create %s at %s: %w", pebbledb.Name, dbPath, err) + } + default: + return nil, fmt.Errorf( + "db-type was %q but should have been one of {%s, %s, %s}", + dbConfig.Name, + leveldb.Name, + memdb.Name, + pebbledb.Name, + ) + } + + if dbConfig.ReadOnly && dbConfig.Name != memdb.Name { + db = versiondb.New(db) + } + + meterDBReg, err := metrics.MakeAndRegister( + gatherer, + "meterdb", + ) + if err != nil { + return nil, err + } + + db, err = meterdb.New(meterDBReg, db) + if err != nil { + return nil, fmt.Errorf("failed to create meterdb: %w", err) + } + + return db, nil +} diff --git a/plugin/evm/database.go b/plugin/evm/database/wrapped_database.go similarity index 55% rename from plugin/evm/database.go rename to plugin/evm/database/wrapped_database.go index 479c995ba3..f8a36913bb 100644 --- a/plugin/evm/database.go +++ b/plugin/evm/database/wrapped_database.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package database import ( "errors" @@ -11,25 +11,29 @@ import ( ) var ( - _ ethdb.KeyValueStore = &Database{} + _ ethdb.KeyValueStore = ðDbWrapper{} ErrSnapshotNotSupported = errors.New("snapshot is not supported") ) -// Database implements ethdb.Database -type Database struct{ database.Database } +// ethDbWrapper implements ethdb.Database +type ethDbWrapper struct{ database.Database } + +func WrapDatabase(db database.Database) ethdb.KeyValueStore { return ethDbWrapper{db} } // Stat implements ethdb.Database -func (db Database) Stat(string) (string, error) { return "", database.ErrNotFound } +func (db ethDbWrapper) Stat(string) (string, error) { return "", database.ErrNotFound } // NewBatch implements ethdb.Database -func (db Database) NewBatch() ethdb.Batch { return Batch{db.Database.NewBatch()} } +func (db ethDbWrapper) NewBatch() ethdb.Batch { return wrappedBatch{db.Database.NewBatch()} } // NewBatchWithSize implements ethdb.Database // TODO: propagate size through avalanchego Database interface -func (db Database) NewBatchWithSize(size int) ethdb.Batch { return Batch{db.Database.NewBatch()} } +func (db ethDbWrapper) NewBatchWithSize(size int) ethdb.Batch { + return wrappedBatch{db.Database.NewBatch()} +} -func (db Database) NewSnapshot() (ethdb.Snapshot, error) { +func (db ethDbWrapper) NewSnapshot() (ethdb.Snapshot, error) { return nil, ErrSnapshotNotSupported } @@ -37,7 +41,7 @@ func (db Database) NewSnapshot() (ethdb.Snapshot, error) { // // Note: This method assumes that the prefix is NOT part of the start, so there's // no need for the caller to prepend the prefix to the start. -func (db Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { +func (db ethDbWrapper) NewIterator(prefix []byte, start []byte) ethdb.Iterator { // avalanchego's database implementation assumes that the prefix is part of the // start, so it is added here (if it is provided). if len(prefix) > 0 { @@ -50,15 +54,15 @@ func (db Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { } // NewIteratorWithStart implements ethdb.Database -func (db Database) NewIteratorWithStart(start []byte) ethdb.Iterator { +func (db ethDbWrapper) NewIteratorWithStart(start []byte) ethdb.Iterator { return db.Database.NewIteratorWithStart(start) } -// Batch implements ethdb.Batch -type Batch struct{ database.Batch } +// wrappedBatch implements ethdb.wrappedBatch +type wrappedBatch struct{ database.Batch } // ValueSize implements ethdb.Batch -func (batch Batch) ValueSize() int { return batch.Batch.Size() } +func (batch wrappedBatch) ValueSize() int { return batch.Batch.Size() } // Replay implements ethdb.Batch -func (batch Batch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) } +func (batch wrappedBatch) Replay(w ethdb.KeyValueWriter) error { return batch.Batch.Replay(w) } diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index f200200db1..823452ea78 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/database" + avalanchedatabase "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" @@ -32,6 +32,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/database" "github.com/ava-labs/subnet-evm/predicate" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/statesync" @@ -372,7 +373,7 @@ type syncVMSetup struct { fundedAccounts map[*keystore.Key]*types.StateAccount syncerVM *VM - syncerDB database.Database + syncerDB avalanchedatabase.Database syncerEngineChan <-chan commonEng.Message shutdownOnceSyncerVM *shutdownOnceVM } @@ -430,7 +431,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if test.expectedErr != nil { require.ErrorIs(err, test.expectedErr) // Note we re-open the database here to avoid a closed error when the test is for a shutdown VM. - chaindb := Database{prefixdb.NewNested(ethDBPrefix, syncerVM.db)} + chaindb := database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, syncerVM.db)) assertSyncPerformedHeights(t, chaindb, map[uint64]struct{}{}) return } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index ee0bf345ef..b09aa99860 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -5,7 +5,6 @@ package evm import ( "context" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -24,7 +23,6 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/prometheus/client_golang/prometheus" - avalancheNode "github.com/ava-labs/avalanchego/node" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/constants" @@ -74,11 +72,6 @@ import ( avalancheRPC "github.com/gorilla/rpc/v2" "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/database/leveldb" - "github.com/ava-labs/avalanchego/database/memdb" - "github.com/ava-labs/avalanchego/database/meterdb" - "github.com/ava-labs/avalanchego/database/pebbledb" - "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" @@ -95,10 +88,8 @@ import ( commonEng "github.com/ava-labs/avalanchego/snow/engine/common" - avalanchemetrics "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/database" avalancheUtils "github.com/ava-labs/avalanchego/utils" - avalancheconstants "github.com/ava-labs/avalanchego/utils/constants" avalancheJSON "github.com/ava-labs/avalanchego/utils/json" ) @@ -123,7 +114,6 @@ const ( ethMetricsPrefix = "eth" sdkMetricsPrefix = "sdk" chainStateMetricsPrefix = "chain_state" - dbMetricsPrefix = "db" // gossip constants pushGossipDiscardedElements = 16_384 @@ -345,12 +335,9 @@ func (vm *VM) Initialize( } if vm.config.InspectDatabase { - start := time.Now() - log.Info("Starting database inspection") - if err := rawdb.InspectDatabase(vm.chaindb, nil, nil); err != nil { + if err := vm.inspectDatabases(); err != nil { return err } - log.Info("Completed database inspection", "elapsed", time.Since(start)) } g := new(core.Genesis) @@ -1268,159 +1255,6 @@ func attachEthService(handler *rpc.Server, apis []rpc.API, names []string) error return nil } -// useStandaloneDatabase returns true if the chain can and should use a standalone database -// other than given by [db] in Initialize() -func (vm *VM) useStandaloneDatabase(acceptedDB database.Database) (bool, error) { - // no config provided, use default - standaloneDBFlag := vm.config.UseStandaloneDatabase - if standaloneDBFlag != nil { - return standaloneDBFlag.Bool(), nil - } - - // check if the chain can use a standalone database - _, err := acceptedDB.Get(lastAcceptedKey) - if err == database.ErrNotFound { - // If there is nothing in the database, we can use the standalone database - return true, nil - } - return false, err -} - -// getDatabaseConfig returns the database configuration for the chain -// to be used by separate, standalone database. -func getDatabaseConfig(config Config, chainDataDir string) (avalancheNode.DatabaseConfig, error) { - var ( - configBytes []byte - err error - ) - if len(config.DatabaseConfigContent) != 0 { - dbConfigContent := config.DatabaseConfigContent - configBytes, err = base64.StdEncoding.DecodeString(dbConfigContent) - if err != nil { - return avalancheNode.DatabaseConfig{}, fmt.Errorf("unable to decode base64 content: %w", err) - } - } else if len(config.DatabaseConfigFile) != 0 { - configPath := config.DatabaseConfigFile - configBytes, err = os.ReadFile(configPath) - if err != nil { - return avalancheNode.DatabaseConfig{}, err - } - } - - dbPath := filepath.Join(chainDataDir, "db") - if len(config.DatabasePath) != 0 { - dbPath = config.DatabasePath - } - - return avalancheNode.DatabaseConfig{ - Name: config.DatabaseType, - ReadOnly: config.DatabaseReadOnly, - Path: dbPath, - Config: configBytes, - }, nil -} - -// initializeDBs initializes the databases used by the VM. -// If [useStandaloneDB] is true, the chain will use a standalone database for its state. -// Otherwise, the chain will use the provided [avaDB] for its state. -func (vm *VM) initializeDBs(avaDB database.Database) error { - db := avaDB - // skip standalone database initialization if we are running in unit tests - if vm.ctx.NetworkID != avalancheconstants.UnitTestID { - // first initialize the accepted block database to check if we need to use a standalone database - acceptedDB := prefixdb.New(acceptedPrefix, avaDB) - useStandAloneDB, err := vm.useStandaloneDatabase(acceptedDB) - if err != nil { - return err - } - if useStandAloneDB { - // If we are using a standalone database, we need to create a new database - // for the chain state. - dbConfig, err := getDatabaseConfig(vm.config, vm.ctx.ChainDataDir) - if err != nil { - return err - } - log.Info("Using standalone database for the chain state", "DatabaseConfig", dbConfig) - db, err = vm.createDatabase(dbConfig) - if err != nil { - return err - } - } - } - // Use NewNested rather than New so that the structure of the database - // remains the same regardless of the provided baseDB type. - vm.chaindb = rawdb.NewDatabase(Database{prefixdb.NewNested(ethDBPrefix, db)}) - vm.db = versiondb.New(db) - vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) - vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) - // Note warpDB and validatorsDB are not part of versiondb because it is not necessary - // that they are committed to the database atomically with - // the last accepted block. - // [warpDB] is used to store warp message signatures - // set to a prefixDB with the prefix [warpPrefix] - vm.warpDB = prefixdb.New(warpPrefix, db) - // [validatorsDB] is used to store the current validator set and uptimes - // set to a prefixDB with the prefix [validatorsDBPrefix] - vm.validatorsDB = prefixdb.New(validatorsDBPrefix, db) - return nil -} - -// createDatabase returns a new database instance with the provided configuration -func (vm *VM) createDatabase(dbConfig avalancheNode.DatabaseConfig) (database.Database, error) { - dbRegisterer, err := avalanchemetrics.MakeAndRegister( - vm.ctx.Metrics, - dbMetricsPrefix, - ) - if err != nil { - return nil, err - } - var db database.Database - // start the db - switch dbConfig.Name { - case leveldb.Name: - dbPath := filepath.Join(dbConfig.Path, leveldb.Name) - db, err = leveldb.New(dbPath, dbConfig.Config, vm.ctx.Log, dbRegisterer) - if err != nil { - return nil, fmt.Errorf("couldn't create %s at %s: %w", leveldb.Name, dbPath, err) - } - case memdb.Name: - db = memdb.New() - case pebbledb.Name: - dbPath := filepath.Join(dbConfig.Path, pebbledb.Name) - db, err = pebbledb.New(dbPath, dbConfig.Config, vm.ctx.Log, dbRegisterer) - if err != nil { - return nil, fmt.Errorf("couldn't create %s at %s: %w", pebbledb.Name, dbPath, err) - } - default: - return nil, fmt.Errorf( - "db-type was %q but should have been one of {%s, %s, %s}", - dbConfig.Name, - leveldb.Name, - memdb.Name, - pebbledb.Name, - ) - } - - if dbConfig.ReadOnly && dbConfig.Name != memdb.Name { - db = versiondb.New(db) - } - - meterDBReg, err := avalanchemetrics.MakeAndRegister( - vm.ctx.Metrics, - "meterdb", - ) - if err != nil { - return nil, err - } - - db, err = meterdb.New(meterDBReg, db) - if err != nil { - return nil, fmt.Errorf("failed to create meterdb: %w", err) - } - - return db, nil -} - func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { if err := vm.uptimeManager.Connect(nodeID); err != nil { return fmt.Errorf("uptime manager failed to connect node %s: %w", nodeID, err) diff --git a/plugin/evm/vm_database.go b/plugin/evm/vm_database.go new file mode 100644 index 0000000000..54d37ed8e0 --- /dev/null +++ b/plugin/evm/vm_database.go @@ -0,0 +1,171 @@ +// (c) 2019-2021, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "encoding/base64" + "fmt" + "os" + "path/filepath" + "time" + + avalanchedatabase "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/prefixdb" + "github.com/ava-labs/avalanchego/database/versiondb" + "github.com/ava-labs/avalanchego/node" + avalancheconstants "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/plugin/evm/database" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// initializeDBs initializes the databases used by the VM. +// If [useStandaloneDB] is true, the chain will use a standalone database for its state. +// Otherwise, the chain will use the provided [avaDB] for its state. +func (vm *VM) initializeDBs(avaDB avalanchedatabase.Database) error { + db := avaDB + // skip standalone database initialization if we are running in unit tests + if vm.ctx.NetworkID != avalancheconstants.UnitTestID { + // first initialize the accepted block database to check if we need to use a standalone database + acceptedDB := prefixdb.New(acceptedPrefix, avaDB) + useStandAloneDB, err := vm.useStandaloneDatabase(acceptedDB) + if err != nil { + return err + } + if useStandAloneDB { + // If we are using a standalone database, we need to create a new database + // for the chain state. + dbConfig, err := getDatabaseConfig(vm.config, vm.ctx.ChainDataDir) + if err != nil { + return err + } + log.Info("Using standalone database for the chain state", "DatabaseConfig", dbConfig) + db, err = database.NewStandaloneDatabase(dbConfig, vm.ctx.Metrics, vm.ctx.Log) + if err != nil { + return err + } + } + } + // Use NewNested rather than New so that the structure of the database + // remains the same regardless of the provided baseDB type. + vm.chaindb = rawdb.NewDatabase(database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, db))) + vm.db = versiondb.New(db) + vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) + vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) + // Note warpDB and validatorsDB are not part of versiondb because it is not necessary + // that they are committed to the database atomically with + // the last accepted block. + // [warpDB] is used to store warp message signatures + // set to a prefixDB with the prefix [warpPrefix] + vm.warpDB = prefixdb.New(warpPrefix, db) + // [validatorsDB] is used to store the current validator set and uptimes + // set to a prefixDB with the prefix [validatorsDBPrefix] + vm.validatorsDB = prefixdb.New(validatorsDBPrefix, db) + return nil +} + +func (vm *VM) inspectDatabases() error { + start := time.Now() + log.Info("Starting database inspection") + if err := rawdb.InspectDatabase(vm.chaindb, nil, nil); err != nil { + return err + } + if err := inspectDB(vm.acceptedBlockDB, "acceptedBlockDB"); err != nil { + return err + } + if err := inspectDB(vm.metadataDB, "metadataDB"); err != nil { + return err + } + if err := inspectDB(vm.warpDB, "warpDB"); err != nil { + return err + } + if err := inspectDB(vm.validatorsDB, "validatorsDB"); err != nil { + return err + } + log.Info("Completed database inspection", "elapsed", time.Since(start)) + return nil +} + +// useStandaloneDatabase returns true if the chain can and should use a standalone database +// other than given by [db] in Initialize() +func (vm *VM) useStandaloneDatabase(acceptedDB avalanchedatabase.Database) (bool, error) { + // no config provided, use default + standaloneDBFlag := vm.config.UseStandaloneDatabase + if standaloneDBFlag != nil { + return standaloneDBFlag.Bool(), nil + } + + // check if the chain can use a standalone database + _, err := acceptedDB.Get(lastAcceptedKey) + if err == avalanchedatabase.ErrNotFound { + // If there is nothing in the database, we can use the standalone database + return true, nil + } + return false, err +} + +// getDatabaseConfig returns the database configuration for the chain +// to be used by separate, standalone database. +func getDatabaseConfig(config Config, chainDataDir string) (node.DatabaseConfig, error) { + var ( + configBytes []byte + err error + ) + if len(config.DatabaseConfigContent) != 0 { + dbConfigContent := config.DatabaseConfigContent + configBytes, err = base64.StdEncoding.DecodeString(dbConfigContent) + if err != nil { + return node.DatabaseConfig{}, fmt.Errorf("unable to decode base64 content: %w", err) + } + } else if len(config.DatabaseConfigFile) != 0 { + configPath := config.DatabaseConfigFile + configBytes, err = os.ReadFile(configPath) + if err != nil { + return node.DatabaseConfig{}, err + } + } + + dbPath := filepath.Join(chainDataDir, "db") + if len(config.DatabasePath) != 0 { + dbPath = config.DatabasePath + } + + return node.DatabaseConfig{ + Name: config.DatabaseType, + ReadOnly: config.DatabaseReadOnly, + Path: dbPath, + Config: configBytes, + }, nil +} + +func inspectDB(db avalanchedatabase.Database, label string) error { + it := db.NewIterator() + defer it.Release() + + var ( + count int64 + start = time.Now() + logged = time.Now() + + // Totals + total common.StorageSize + ) + // Inspect key-value database first. + for it.Next() { + var ( + key = it.Key() + size = common.StorageSize(len(key) + len(it.Value())) + ) + total += size + count++ + if count%1000 == 0 && time.Since(logged) > 8*time.Second { + log.Info("Inspecting database", "label", label, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + // Display the database statistic. + log.Info("Database statistics", "label", label, "total", total.String(), "count", count) + return nil +} From fad48ec3f0d9ad088b4b3bf0c68e7187dc494222 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 15 Nov 2024 22:01:56 +0300 Subject: [PATCH 07/57] Refactor uptime tracking (#1388) Co-authored-by: Darioush Jalali Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> --- plugin/evm/service.go | 8 +- .../evm/validators/interfaces/interfaces.go | 30 ++ plugin/evm/validators/manager.go | 161 ++++++++ plugin/evm/validators/manager_test.go | 221 +++++++++++ plugin/evm/validators/{ => state}/codec.go | 2 +- .../{ => state}/interfaces/mock_listener.go | 4 +- .../interfaces/state.go} | 22 +- plugin/evm/validators/{ => state}/state.go | 4 +- .../evm/validators/{ => state}/state_test.go | 7 +- .../uptime/interfaces/interface.go | 4 +- .../uptime/pausable_manager.go | 2 +- .../uptime/pausable_manager_test.go | 2 +- .../validators/validatorstest/noop_state.go | 64 --- plugin/evm/vm.go | 134 +------ plugin/evm/vm_validators_state_test.go | 363 ------------------ plugin/evm/vm_validators_test.go | 161 ++++++++ scripts/mocks.mockgen.txt | 2 +- warp/backend.go | 14 +- warp/backend_test.go | 13 +- warp/handlers/signature_request_test.go | 8 +- warp/verifier_backend.go | 19 +- warp/verifier_backend_test.go | 24 +- warp/warptest/noop_validator_reader.go | 21 + 23 files changed, 665 insertions(+), 625 deletions(-) create mode 100644 plugin/evm/validators/interfaces/interfaces.go create mode 100644 plugin/evm/validators/manager.go create mode 100644 plugin/evm/validators/manager_test.go rename plugin/evm/validators/{ => state}/codec.go (96%) rename plugin/evm/validators/{ => state}/interfaces/mock_listener.go (94%) rename plugin/evm/validators/{interfaces/interface.go => state/interfaces/state.go} (94%) rename plugin/evm/validators/{ => state}/state.go (99%) rename plugin/evm/validators/{ => state}/state_test.go (97%) rename plugin/evm/{ => validators}/uptime/interfaces/interface.go (67%) rename plugin/evm/{ => validators}/uptime/pausable_manager.go (98%) rename plugin/evm/{ => validators}/uptime/pausable_manager_test.go (99%) delete mode 100644 plugin/evm/validators/validatorstest/noop_state.go delete mode 100644 plugin/evm/vm_validators_state_test.go create mode 100644 plugin/evm/vm_validators_test.go create mode 100644 warp/warptest/noop_validator_reader.go diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 1a7b285bfe..625d169514 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -33,19 +33,19 @@ func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, rep api.vm.ctx.Lock.RLock() defer api.vm.ctx.Lock.RUnlock() - vIDs := api.vm.validatorState.GetValidationIDs() + vIDs := api.vm.validatorsManager.GetValidationIDs() reply.Validators = make([]CurrentValidator, 0, vIDs.Len()) for _, vID := range vIDs.List() { - validator, err := api.vm.validatorState.GetValidator(vID) + validator, err := api.vm.validatorsManager.GetValidator(vID) if err != nil { return err } - isConnected := api.vm.uptimeManager.IsConnected(validator.NodeID) + isConnected := api.vm.validatorsManager.IsConnected(validator.NodeID) - uptime, _, err := api.vm.uptimeManager.CalculateUptime(validator.NodeID) + uptime, _, err := api.vm.validatorsManager.CalculateUptime(validator.NodeID) if err != nil { return err } diff --git a/plugin/evm/validators/interfaces/interfaces.go b/plugin/evm/validators/interfaces/interfaces.go new file mode 100644 index 0000000000..d8b88b3626 --- /dev/null +++ b/plugin/evm/validators/interfaces/interfaces.go @@ -0,0 +1,30 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package interfaces + +import ( + "context" + "time" + + "github.com/ava-labs/avalanchego/ids" + avalancheuptime "github.com/ava-labs/avalanchego/snow/uptime" + stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" +) + +type ValidatorReader interface { + // GetValidatorAndUptime returns the uptime of the validator specified by validationID + GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) +} + +type Manager interface { + stateinterfaces.State + avalancheuptime.Manager + ValidatorReader + + // Sync updates the validator set managed + // by the manager + Sync(ctx context.Context) error + // DispatchSync starts the sync process + DispatchSync(ctx context.Context) +} diff --git a/plugin/evm/validators/manager.go b/plugin/evm/validators/manager.go new file mode 100644 index 0000000000..e679be220c --- /dev/null +++ b/plugin/evm/validators/manager.go @@ -0,0 +1,161 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package validators + +import ( + "context" + "fmt" + "time" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + avalancheuptime "github.com/ava-labs/avalanchego/snow/uptime" + avalanchevalidators "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + validators "github.com/ava-labs/subnet-evm/plugin/evm/validators/state" + stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/uptime" + uptimeinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/uptime/interfaces" + + "github.com/ethereum/go-ethereum/log" +) + +const ( + SyncFrequency = 1 * time.Minute +) + +type manager struct { + chainCtx *snow.Context + stateinterfaces.State + uptimeinterfaces.PausableManager +} + +// NewManager returns a new validator manager +// that manages the validator state and the uptime manager. +func NewManager( + ctx *snow.Context, + db database.Database, + clock *mockable.Clock, +) (interfaces.Manager, error) { + validatorState, err := validators.NewState(db) + if err != nil { + return nil, fmt.Errorf("failed to initialize validator state: %w", err) + } + + // Initialize uptime manager + uptimeManager := uptime.NewPausableManager(avalancheuptime.NewManager(validatorState, clock)) + validatorState.RegisterListener(uptimeManager) + + return &manager{ + chainCtx: ctx, + State: validatorState, + PausableManager: uptimeManager, + }, nil +} + +// GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID +// and the last updated time. +// GetValidatorAndUptime holds the chain context lock while performing the operation and can be called concurrently. +func (m *manager) GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) { + // lock the state + m.chainCtx.Lock.RLock() + defer m.chainCtx.Lock.RUnlock() + + // Get validator first + vdr, err := m.GetValidator(validationID) + if err != nil { + return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get validator: %w", err) + } + + uptime, lastUpdated, err := m.CalculateUptime(vdr.NodeID) + if err != nil { + return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get uptime: %w", err) + } + + return vdr, uptime, lastUpdated, nil +} + +// DispatchSync starts the sync process +// DispatchSync holds the chain context lock while performing the sync. +func (m *manager) DispatchSync(ctx context.Context) { + ticker := time.NewTicker(SyncFrequency) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + m.chainCtx.Lock.Lock() + if err := m.Sync(ctx); err != nil { + log.Error("failed to sync validators", "error", err) + } + m.chainCtx.Lock.Unlock() + case <-ctx.Done(): + return + } + } +} + +// Sync synchronizes the validator state with the current validator set +// and writes the state to the database. +// Sync is not safe to call concurrently and should be called with the chain context locked. +func (m *manager) Sync(ctx context.Context) error { + now := time.Now() + log.Debug("performing validator sync") + // get current validator set + currentValidatorSet, _, err := m.chainCtx.ValidatorState.GetCurrentValidatorSet(ctx, m.chainCtx.SubnetID) + if err != nil { + return fmt.Errorf("failed to get current validator set: %w", err) + } + + // load the current validator set into the validator state + if err := loadValidators(m.State, currentValidatorSet); err != nil { + return fmt.Errorf("failed to load current validators: %w", err) + } + + // write validators to the database + if err := m.State.WriteState(); err != nil { + return fmt.Errorf("failed to write validator state: %w", err) + } + + // TODO: add metrics + log.Debug("validator sync complete", "duration", time.Since(now)) + return nil +} + +// loadValidators loads the [validators] into the validator state [validatorState] +func loadValidators(validatorState stateinterfaces.State, newValidators map[ids.ID]*avalanchevalidators.GetCurrentValidatorOutput) error { + currentValidationIDs := validatorState.GetValidationIDs() + // first check if we need to delete any existing validators + for vID := range currentValidationIDs { + // if the validator is not in the new set of validators + // delete the validator + if _, exists := newValidators[vID]; !exists { + validatorState.DeleteValidator(vID) + } + } + + // then load the new validators + for newVID, newVdr := range newValidators { + currentVdr := stateinterfaces.Validator{ + ValidationID: newVID, + NodeID: newVdr.NodeID, + Weight: newVdr.Weight, + StartTimestamp: newVdr.StartTime, + IsActive: newVdr.IsActive, + IsSoV: newVdr.IsSoV, + } + if currentValidationIDs.Contains(newVID) { + if err := validatorState.UpdateValidator(currentVdr); err != nil { + return err + } + } else { + if err := validatorState.AddValidator(currentVdr); err != nil { + return err + } + } + } + return nil +} diff --git a/plugin/evm/validators/manager_test.go b/plugin/evm/validators/manager_test.go new file mode 100644 index 0000000000..282488f68f --- /dev/null +++ b/plugin/evm/validators/manager_test.go @@ -0,0 +1,221 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package validators + +import ( + "testing" + + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/state" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + avagovalidators "github.com/ava-labs/avalanchego/snow/validators" +) + +func TestLoadNewValidators(t *testing.T) { + testNodeIDs := []ids.NodeID{ + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + } + testValidationIDs := []ids.ID{ + ids.GenerateTestID(), + ids.GenerateTestID(), + ids.GenerateTestID(), + } + tests := []struct { + name string + initialValidators map[ids.ID]*avagovalidators.GetCurrentValidatorOutput + newValidators map[ids.ID]*avagovalidators.GetCurrentValidatorOutput + registerMockListenerCalls func(*interfaces.MockStateCallbackListener) + expectedLoadErr error + }{ + { + name: "before empty/after empty", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{}, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{}, + registerMockListenerCalls: func(*interfaces.MockStateCallbackListener) {}, + }, + { + name: "before empty/after one", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{}, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "before one/after empty", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{}, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be removed + mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) + }, + }, + { + name: "no change", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "status and weight change and new one", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + Weight: 1, + }, + }, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: false, + StartTime: 0, + Weight: 2, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be updated + mock.EXPECT().OnValidatorStatusUpdated(testValidationIDs[0], testNodeIDs[0], false).Times(1) + // new validator will be added + mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[1], uint64(0), true).Times(1) + }, + }, + { + name: "renew validation ID", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[1]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it will be removed + mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) + // new validator will be added + mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[0], uint64(0), true).Times(1) + }, + }, + { + name: "renew node ID", + initialValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + IsActive: true, + StartTime: 0, + }, + }, + newValidators: map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[1], + IsActive: true, + StartTime: 0, + }, + }, + expectedLoadErr: state.ErrImmutableField, + registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { + // initial validator will trigger first + mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) + // then it won't be called since we don't track the node ID changes + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + require := require.New(tt) + db := memdb.New() + validatorState, err := state.NewState(db) + require.NoError(err) + + // set initial validators + for vID, validator := range test.initialValidators { + err := validatorState.AddValidator(interfaces.Validator{ + ValidationID: vID, + NodeID: validator.NodeID, + Weight: validator.Weight, + StartTimestamp: validator.StartTime, + IsActive: validator.IsActive, + IsSoV: validator.IsSoV, + }) + require.NoError(err) + } + // enable mock listener + ctrl := gomock.NewController(tt) + mockListener := interfaces.NewMockStateCallbackListener(ctrl) + test.registerMockListenerCalls(mockListener) + + validatorState.RegisterListener(mockListener) + // load new validators + err = loadValidators(validatorState, test.newValidators) + if test.expectedLoadErr != nil { + require.Error(err) + return + } + require.NoError(err) + // check if the state is as expected + require.Equal(len(test.newValidators), validatorState.GetValidationIDs().Len()) + for vID, validator := range test.newValidators { + v, err := validatorState.GetValidator(vID) + require.NoError(err) + require.Equal(validator.NodeID, v.NodeID) + require.Equal(validator.Weight, v.Weight) + require.Equal(validator.StartTime, v.StartTimestamp) + require.Equal(validator.IsActive, v.IsActive) + require.Equal(validator.IsSoV, v.IsSoV) + } + }) + } +} diff --git a/plugin/evm/validators/codec.go b/plugin/evm/validators/state/codec.go similarity index 96% rename from plugin/evm/validators/codec.go rename to plugin/evm/validators/state/codec.go index dadba8b273..aeb1a683b2 100644 --- a/plugin/evm/validators/codec.go +++ b/plugin/evm/validators/state/codec.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package validators +package state import ( "math" diff --git a/plugin/evm/validators/interfaces/mock_listener.go b/plugin/evm/validators/state/interfaces/mock_listener.go similarity index 94% rename from plugin/evm/validators/interfaces/mock_listener.go rename to plugin/evm/validators/state/interfaces/mock_listener.go index 8cf1903729..053a371d16 100644 --- a/plugin/evm/validators/interfaces/mock_listener.go +++ b/plugin/evm/validators/state/interfaces/mock_listener.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces (interfaces: StateCallbackListener) +// Source: github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces (interfaces: StateCallbackListener) // // Generated by this command: // -// mockgen -package=interfaces -destination=plugin/evm/validators/interfaces/mock_listener.go github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces StateCallbackListener +// mockgen -package=interfaces -destination=plugin/evm/validators/state/interfaces/mock_listener.go github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces StateCallbackListener // // Package interfaces is a generated GoMock package. diff --git a/plugin/evm/validators/interfaces/interface.go b/plugin/evm/validators/state/interfaces/state.go similarity index 94% rename from plugin/evm/validators/interfaces/interface.go rename to plugin/evm/validators/state/interfaces/state.go index 39b6b8c9e9..ec7fac2416 100644 --- a/plugin/evm/validators/interfaces/interface.go +++ b/plugin/evm/validators/state/interfaces/state.go @@ -4,31 +4,31 @@ package interfaces import ( - "time" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/set" ) +type StateReader interface { + // GetValidator returns the validator data for the given validation ID + GetValidator(vID ids.ID) (Validator, error) + // GetValidationIDs returns the validation IDs in the state + GetValidationIDs() set.Set[ids.ID] + // GetNodeIDs returns the validator node IDs in the state + GetNodeIDs() set.Set[ids.NodeID] +} + type State interface { uptime.State + StateReader // AddValidator adds a new validator to the state AddValidator(vdr Validator) error // UpdateValidator updates the validator in the state UpdateValidator(vdr Validator) error - // GetValidator returns the validator data for the given validation ID - GetValidator(vID ids.ID) (Validator, error) // DeleteValidator deletes the validator from the state DeleteValidator(vID ids.ID) error // WriteState writes the validator state to the disk WriteState() error - - // GetValidationIDs returns the validation IDs in the state - GetValidationIDs() set.Set[ids.ID] - // GetNodeIDs returns the validator node IDs in the state - GetNodeIDs() set.Set[ids.NodeID] - // RegisterListener registers a listener to the state RegisterListener(StateCallbackListener) } @@ -51,5 +51,3 @@ type Validator struct { IsActive bool `json:"isActive"` IsSoV bool `json:"isSoV"` } - -func (v *Validator) StartTime() time.Time { return time.Unix(int64(v.StartTimestamp), 0) } diff --git a/plugin/evm/validators/state.go b/plugin/evm/validators/state/state.go similarity index 99% rename from plugin/evm/validators/state.go rename to plugin/evm/validators/state/state.go index a8769466e9..14c7912ea9 100644 --- a/plugin/evm/validators/state.go +++ b/plugin/evm/validators/state/state.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package validators +package state import ( "fmt" @@ -11,7 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" ) var _ uptime.State = &state{} diff --git a/plugin/evm/validators/state_test.go b/plugin/evm/validators/state/state_test.go similarity index 97% rename from plugin/evm/validators/state_test.go rename to plugin/evm/validators/state/state_test.go index 7ba6574785..b888de156d 100644 --- a/plugin/evm/validators/state_test.go +++ b/plugin/evm/validators/state/state_test.go @@ -1,7 +1,7 @@ // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package validators +package state import ( "testing" @@ -15,7 +15,7 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" ) func TestState(t *testing.T) { @@ -94,8 +94,7 @@ func TestState(t *testing.T) { require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) // set a different start time should fail - newStartTime := vdr.StartTime().Add(time.Hour) - vdr.StartTimestamp = uint64(newStartTime.Unix()) + vdr.StartTimestamp = vdr.StartTimestamp + 100 require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) // set SoV should fail diff --git a/plugin/evm/uptime/interfaces/interface.go b/plugin/evm/validators/uptime/interfaces/interface.go similarity index 67% rename from plugin/evm/uptime/interfaces/interface.go rename to plugin/evm/validators/uptime/interfaces/interface.go index 13e6b7abba..296daae314 100644 --- a/plugin/evm/uptime/interfaces/interface.go +++ b/plugin/evm/validators/uptime/interfaces/interface.go @@ -6,11 +6,11 @@ package interfaces import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/uptime" - validatorsinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + validatorsstateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" ) type PausableManager interface { uptime.Manager - validatorsinterfaces.StateCallbackListener + validatorsstateinterfaces.StateCallbackListener IsPaused(nodeID ids.NodeID) bool } diff --git a/plugin/evm/uptime/pausable_manager.go b/plugin/evm/validators/uptime/pausable_manager.go similarity index 98% rename from plugin/evm/uptime/pausable_manager.go rename to plugin/evm/validators/uptime/pausable_manager.go index 6c437dd049..a715c54257 100644 --- a/plugin/evm/uptime/pausable_manager.go +++ b/plugin/evm/validators/uptime/pausable_manager.go @@ -6,7 +6,7 @@ package uptime import ( "errors" - "github.com/ava-labs/subnet-evm/plugin/evm/uptime/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/uptime/interfaces" "github.com/ethereum/go-ethereum/log" "github.com/ava-labs/avalanchego/ids" diff --git a/plugin/evm/uptime/pausable_manager_test.go b/plugin/evm/validators/uptime/pausable_manager_test.go similarity index 99% rename from plugin/evm/uptime/pausable_manager_test.go rename to plugin/evm/validators/uptime/pausable_manager_test.go index a910203773..8cdadba0fd 100644 --- a/plugin/evm/uptime/pausable_manager_test.go +++ b/plugin/evm/validators/uptime/pausable_manager_test.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/subnet-evm/plugin/evm/uptime/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/uptime/interfaces" "github.com/stretchr/testify/require" ) diff --git a/plugin/evm/validators/validatorstest/noop_state.go b/plugin/evm/validators/validatorstest/noop_state.go deleted file mode 100644 index 3594999574..0000000000 --- a/plugin/evm/validators/validatorstest/noop_state.go +++ /dev/null @@ -1,64 +0,0 @@ -package validatorstest - -import ( - "time" - - ids "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" -) - -var NoOpState interfaces.State = &noOpState{} - -type noOpState struct{} - -func (n *noOpState) GetStatus(vID ids.ID) (bool, error) { return false, nil } - -func (n *noOpState) GetValidationIDs() set.Set[ids.ID] { return set.NewSet[ids.ID](0) } - -func (n *noOpState) GetNodeIDs() set.Set[ids.NodeID] { return set.NewSet[ids.NodeID](0) } - -func (n *noOpState) GetValidator(vID ids.ID) (interfaces.Validator, error) { - return interfaces.Validator{}, nil -} - -func (n *noOpState) GetNodeID(vID ids.ID) (ids.NodeID, error) { return ids.NodeID{}, nil } - -func (n *noOpState) AddValidator(vdr interfaces.Validator) error { - return nil -} - -func (n *noOpState) UpdateValidator(vdr interfaces.Validator) error { - return nil -} - -func (n *noOpState) DeleteValidator(vID ids.ID) error { - return nil -} -func (n *noOpState) WriteState() error { return nil } - -func (n *noOpState) SetStatus(vID ids.ID, isActive bool) error { return nil } - -func (n *noOpState) SetWeight(vID ids.ID, newWeight uint64) error { return nil } - -func (n *noOpState) RegisterListener(interfaces.StateCallbackListener) {} - -func (n *noOpState) GetUptime( - nodeID ids.NodeID, -) (upDuration time.Duration, lastUpdated time.Time, err error) { - return 0, time.Time{}, nil -} - -func (n *noOpState) SetUptime( - nodeID ids.NodeID, - upDuration time.Duration, - lastUpdated time.Time, -) error { - return nil -} - -func (n *noOpState) GetStartTime( - nodeID ids.NodeID, -) (startTime time.Time, err error) { - return time.Time{}, nil -} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index b09aa99860..4966114884 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -39,10 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/peer" "github.com/ava-labs/subnet-evm/plugin/evm/message" - "github.com/ava-labs/subnet-evm/plugin/evm/uptime" - uptimeinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/uptime/interfaces" "github.com/ava-labs/subnet-evm/plugin/evm/validators" - validatorsinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" "github.com/ava-labs/subnet-evm/triedb" "github.com/ava-labs/subnet-evm/triedb/hashdb" @@ -77,8 +75,6 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - avalancheUptime "github.com/ava-labs/avalanchego/snow/uptime" - avalancheValidators "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/profiler" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -126,8 +122,6 @@ const ( txGossipThrottlingPeriod = 10 * time.Second txGossipThrottlingLimit = 2 txGossipPollSize = 1 - - loadValidatorsFrequency = 1 * time.Minute ) // Define the API endpoints for the VM @@ -237,7 +231,7 @@ type VM struct { client peer.NetworkClient networkCodec codec.Manager - validators *p2p.Validators + p2pValidators *p2p.Validators // Metrics sdkMetrics *prometheus.Registry @@ -259,8 +253,7 @@ type VM struct { ethTxPushGossiper avalancheUtils.Atomic[*gossip.PushGossiper[*GossipEthTx]] ethTxPullGossiper gossip.Gossiper - uptimeManager uptimeinterfaces.PausableManager - validatorState validatorsinterfaces.State + validatorsManager interfaces.Manager chainAlias string // RPC handlers (should be stopped before closing chaindb) @@ -489,20 +482,16 @@ func (vm *VM) Initialize( if err != nil { return fmt.Errorf("failed to initialize p2p network: %w", err) } - vm.validators = p2p.NewValidators(p2pNetwork.Peers, vm.ctx.Log, vm.ctx.SubnetID, vm.ctx.ValidatorState, maxValidatorSetStaleness) + vm.p2pValidators = p2p.NewValidators(p2pNetwork.Peers, vm.ctx.Log, vm.ctx.SubnetID, vm.ctx.ValidatorState, maxValidatorSetStaleness) vm.networkCodec = message.Codec vm.Network = peer.NewNetwork(p2pNetwork, appSender, vm.networkCodec, chainCtx.NodeID, vm.config.MaxOutboundActiveRequests) vm.client = peer.NewNetworkClient(vm.Network) - vm.validatorState, err = validators.NewState(vm.validatorsDB) + vm.validatorsManager, err = validators.NewManager(vm.ctx, vm.validatorsDB, &vm.clock) if err != nil { - return fmt.Errorf("failed to initialize validator state: %w", err) + return fmt.Errorf("failed to initialize validators manager: %w", err) } - // Initialize uptime manager - vm.uptimeManager = uptime.NewPausableManager(avalancheUptime.NewManager(vm.validatorState, &vm.clock)) - vm.validatorState.RegisterListener(vm.uptimeManager) - // Initialize warp backend offchainWarpMessages := make([][]byte, len(vm.config.WarpOffChainMessages)) for i, hexMsg := range vm.config.WarpOffChainMessages { @@ -526,9 +515,7 @@ func (vm *VM) Initialize( vm.ctx.ChainID, vm.ctx.WarpSigner, vm, - vm.uptimeManager, - vm.validatorState, - vm.ctx.Lock.RLocker(), + vm.validatorsManager, vm.warpDB, meteredCache, offchainWarpMessages, @@ -727,28 +714,28 @@ func (vm *VM) onNormalOperationsStarted() error { ctx, cancel := context.WithCancel(context.TODO()) vm.cancel = cancel - // update validators first - if err := vm.performValidatorUpdate(ctx); err != nil { + // sync validators first + if err := vm.validatorsManager.Sync(ctx); err != nil { return fmt.Errorf("failed to update validators: %w", err) } - vdrIDs := vm.validatorState.GetNodeIDs().List() + vdrIDs := vm.validatorsManager.GetNodeIDs().List() // Then start tracking with updated validators // StartTracking initializes the uptime tracking with the known validators // and update their uptime to account for the time we were being offline. - if err := vm.uptimeManager.StartTracking(vdrIDs); err != nil { + if err := vm.validatorsManager.StartTracking(vdrIDs); err != nil { return fmt.Errorf("failed to start tracking uptime: %w", err) } // dispatch validator set update vm.shutdownWg.Add(1) go func() { - vm.dispatchUpdateValidators(ctx) + vm.validatorsManager.DispatchSync(ctx) vm.shutdownWg.Done() }() // Initialize goroutines related to block building // once we enter normal operation as there is no need to handle mempool gossip before this point. ethTxGossipMarshaller := GossipEthTxMarshaller{} - ethTxGossipClient := vm.Network.NewClient(p2p.TxGossipHandlerID, p2p.WithValidatorSampling(vm.validators)) + ethTxGossipClient := vm.Network.NewClient(p2p.TxGossipHandlerID, p2p.WithValidatorSampling(vm.p2pValidators)) ethTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, ethTxGossipNamespace) if err != nil { return fmt.Errorf("failed to initialize eth tx gossip metrics: %w", err) @@ -778,7 +765,7 @@ func (vm *VM) onNormalOperationsStarted() error { ethTxPushGossiper, err = gossip.NewPushGossiper[*GossipEthTx]( ethTxGossipMarshaller, ethTxPool, - vm.validators, + vm.p2pValidators, ethTxGossipClient, ethTxGossipMetrics, pushGossipParams, @@ -808,7 +795,7 @@ func (vm *VM) onNormalOperationsStarted() error { txGossipTargetMessageSize, txGossipThrottlingPeriod, txGossipThrottlingLimit, - vm.validators, + vm.p2pValidators, ) } @@ -829,7 +816,7 @@ func (vm *VM) onNormalOperationsStarted() error { vm.ethTxPullGossiper = gossip.ValidatorGossiper{ Gossiper: ethTxPullGossiper, NodeID: vm.ctx.NodeID, - Validators: vm.validators, + Validators: vm.p2pValidators, } } @@ -874,11 +861,11 @@ func (vm *VM) Shutdown(context.Context) error { vm.cancel() } if vm.bootstrapped.Get() { - vdrIDs := vm.validatorState.GetNodeIDs().List() - if err := vm.uptimeManager.StopTracking(vdrIDs); err != nil { + vdrIDs := vm.validatorsManager.GetNodeIDs().List() + if err := vm.validatorsManager.StopTracking(vdrIDs); err != nil { return fmt.Errorf("failed to stop tracking uptime: %w", err) } - if err := vm.validatorState.WriteState(); err != nil { + if err := vm.validatorsManager.WriteState(); err != nil { return fmt.Errorf("failed to write validator: %w", err) } } @@ -1256,95 +1243,16 @@ func attachEthService(handler *rpc.Server, apis []rpc.API, names []string) error } func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { - if err := vm.uptimeManager.Connect(nodeID); err != nil { + if err := vm.validatorsManager.Connect(nodeID); err != nil { return fmt.Errorf("uptime manager failed to connect node %s: %w", nodeID, err) } return vm.Network.Connected(ctx, nodeID, version) } func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { - if err := vm.uptimeManager.Disconnect(nodeID); err != nil { + if err := vm.validatorsManager.Disconnect(nodeID); err != nil { return fmt.Errorf("uptime manager failed to disconnect node %s: %w", nodeID, err) } return vm.Network.Disconnected(ctx, nodeID) } - -func (vm *VM) dispatchUpdateValidators(ctx context.Context) { - ticker := time.NewTicker(loadValidatorsFrequency) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - vm.ctx.Lock.Lock() - if err := vm.performValidatorUpdate(ctx); err != nil { - log.Error("failed to update validators", "error", err) - } - vm.ctx.Lock.Unlock() - case <-ctx.Done(): - return - } - } -} - -// performValidatorUpdate updates the validator state with the current validator set -// and writes the state to the database. -func (vm *VM) performValidatorUpdate(ctx context.Context) error { - now := time.Now() - log.Debug("performing validator update") - // get current validator set - currentValidatorSet, _, err := vm.ctx.ValidatorState.GetCurrentValidatorSet(ctx, vm.ctx.SubnetID) - if err != nil { - return fmt.Errorf("failed to get current validator set: %w", err) - } - - // load the current validator set into the validator state - if err := loadValidators(vm.validatorState, currentValidatorSet); err != nil { - return fmt.Errorf("failed to load current validators: %w", err) - } - - // write validators to the database - if err := vm.validatorState.WriteState(); err != nil { - return fmt.Errorf("failed to write validator state: %w", err) - } - - // TODO: add metrics - log.Debug("validator update complete", "duration", time.Since(now)) - return nil -} - -// loadValidators loads the [validators] into the validator state [validatorState] -func loadValidators(validatorState validatorsinterfaces.State, newValidators map[ids.ID]*avalancheValidators.GetCurrentValidatorOutput) error { - currentValidationIDs := validatorState.GetValidationIDs() - // first check if we need to delete any existing validators - for vID := range currentValidationIDs { - // if the validator is not in the new set of validators - // delete the validator - if _, exists := newValidators[vID]; !exists { - validatorState.DeleteValidator(vID) - } - } - - // then load the new validators - for newVID, newVdr := range newValidators { - currentVdr := validatorsinterfaces.Validator{ - ValidationID: newVID, - NodeID: newVdr.NodeID, - Weight: newVdr.Weight, - StartTimestamp: newVdr.StartTime, - IsActive: newVdr.IsActive, - IsSoV: newVdr.IsSoV, - } - if currentValidationIDs.Contains(newVID) { - if err := validatorState.UpdateValidator(currentVdr); err != nil { - return err - } - } else { - if err := validatorState.AddValidator(currentVdr); err != nil { - return err - } - } - } - return nil -} diff --git a/plugin/evm/vm_validators_state_test.go b/plugin/evm/vm_validators_state_test.go deleted file mode 100644 index 8bf8c3546f..0000000000 --- a/plugin/evm/vm_validators_state_test.go +++ /dev/null @@ -1,363 +0,0 @@ -package evm - -import ( - "context" - "testing" - "time" - - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/database/memdb" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" - commonEng "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/snow/engine/enginetest" - avagoValidators "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/plugin/evm/validators" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" - "github.com/ava-labs/subnet-evm/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestValidatorState(t *testing.T) { - require := require.New(t) - genesis := &core.Genesis{} - require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONLatest))) - genesisJSON, err := genesis.MarshalJSON() - require.NoError(err) - - vm := &VM{} - ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisJSON)) - appSender := &enginetest.Sender{T: t} - appSender.CantSendAppGossip = true - testNodeIDs := []ids.NodeID{ - ids.GenerateTestNodeID(), - ids.GenerateTestNodeID(), - ids.GenerateTestNodeID(), - } - testValidationIDs := []ids.ID{ - ids.GenerateTestID(), - ids.GenerateTestID(), - ids.GenerateTestID(), - } - ctx.ValidatorState = &validatorstest.State{ - GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagoValidators.GetCurrentValidatorOutput, uint64, error) { - return map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - PublicKey: nil, - Weight: 1, - }, - testValidationIDs[1]: { - NodeID: testNodeIDs[1], - PublicKey: nil, - Weight: 1, - }, - testValidationIDs[2]: { - NodeID: testNodeIDs[2], - PublicKey: nil, - Weight: 1, - }, - }, 0, nil - }, - } - appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } - err = vm.Initialize( - context.Background(), - ctx, - dbManager, - genesisBytes, - []byte(""), - []byte(""), - issuer, - []*commonEng.Fx{}, - appSender, - ) - require.NoError(err, "error initializing GenesisVM") - - // Test case 1: state should not be populated until bootstrapped - require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) - require.Equal(0, vm.validatorState.GetValidationIDs().Len()) - _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) - require.ErrorIs(database.ErrNotFound, err) - require.False(vm.uptimeManager.StartedTracking()) - - // Test case 2: state should be populated after bootstrapped - require.NoError(vm.SetState(context.Background(), snow.NormalOp)) - require.Len(vm.validatorState.GetValidationIDs(), 3) - _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) - require.NoError(err) - require.True(vm.uptimeManager.StartedTracking()) - - // Test case 3: restarting VM should not lose state - vm.Shutdown(context.Background()) - // Shutdown should stop tracking - require.False(vm.uptimeManager.StartedTracking()) - - vm = &VM{} - err = vm.Initialize( - context.Background(), - utils.TestSnowContext(), // this context does not have validators state, making VM to source it from the database - dbManager, - genesisBytes, - []byte(""), - []byte(""), - issuer, - []*commonEng.Fx{}, - appSender, - ) - require.NoError(err, "error initializing GenesisVM") - require.Len(vm.validatorState.GetValidationIDs(), 3) - _, _, err = vm.uptimeManager.CalculateUptime(testNodeIDs[0]) - require.NoError(err) - require.False(vm.uptimeManager.StartedTracking()) - - // Test case 4: new validators should be added to the state - newValidationID := ids.GenerateTestID() - newNodeID := ids.GenerateTestNodeID() - testState := &validatorstest.State{ - GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagoValidators.GetCurrentValidatorOutput, uint64, error) { - return map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - PublicKey: nil, - Weight: 1, - }, - testValidationIDs[1]: { - NodeID: testNodeIDs[1], - PublicKey: nil, - Weight: 1, - }, - testValidationIDs[2]: { - NodeID: testNodeIDs[2], - PublicKey: nil, - Weight: 1, - }, - newValidationID: { - NodeID: newNodeID, - PublicKey: nil, - Weight: 1, - }, - }, 0, nil - }, - } - vm.ctx.ValidatorState = testState - // set VM as bootstrapped - require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) - require.NoError(vm.SetState(context.Background(), snow.NormalOp)) - - // new validator should be added to the state eventually after validatorsLoadFrequency - require.EventuallyWithT(func(c *assert.CollectT) { - assert.Len(c, vm.validatorState.GetNodeIDs(), 4) - newValidator, err := vm.validatorState.GetValidator(newValidationID) - assert.NoError(c, err) - assert.Equal(c, newNodeID, newValidator.NodeID) - }, loadValidatorsFrequency*2, 5*time.Second) -} - -func TestLoadNewValidators(t *testing.T) { - testNodeIDs := []ids.NodeID{ - ids.GenerateTestNodeID(), - ids.GenerateTestNodeID(), - ids.GenerateTestNodeID(), - } - testValidationIDs := []ids.ID{ - ids.GenerateTestID(), - ids.GenerateTestID(), - ids.GenerateTestID(), - } - tests := []struct { - name string - initialValidators map[ids.ID]*avagoValidators.GetCurrentValidatorOutput - newValidators map[ids.ID]*avagoValidators.GetCurrentValidatorOutput - registerMockListenerCalls func(*interfaces.MockStateCallbackListener) - expectedLoadErr error - }{ - { - name: "before empty/after empty", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, - registerMockListenerCalls: func(*interfaces.MockStateCallbackListener) {}, - }, - { - name: "before empty/after one", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - }, - }, - { - name: "before one/after empty", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{}, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - // initial validator will trigger first - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - // then it will be removed - mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) - }, - }, - { - name: "no change", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - }, - }, - { - name: "status and weight change and new one", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - Weight: 1, - }, - }, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: false, - StartTime: 0, - Weight: 2, - }, - testValidationIDs[1]: { - NodeID: testNodeIDs[1], - IsActive: true, - StartTime: 0, - }, - }, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - // initial validator will trigger first - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - // then it will be updated - mock.EXPECT().OnValidatorStatusUpdated(testValidationIDs[0], testNodeIDs[0], false).Times(1) - // new validator will be added - mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[1], uint64(0), true).Times(1) - }, - }, - { - name: "renew validation ID", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[1]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - // initial validator will trigger first - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - // then it will be removed - mock.EXPECT().OnValidatorRemoved(testValidationIDs[0], testNodeIDs[0]).Times(1) - // new validator will be added - mock.EXPECT().OnValidatorAdded(testValidationIDs[1], testNodeIDs[0], uint64(0), true).Times(1) - }, - }, - { - name: "renew node ID", - initialValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[0], - IsActive: true, - StartTime: 0, - }, - }, - newValidators: map[ids.ID]*avagoValidators.GetCurrentValidatorOutput{ - testValidationIDs[0]: { - NodeID: testNodeIDs[1], - IsActive: true, - StartTime: 0, - }, - }, - expectedLoadErr: validators.ErrImmutableField, - registerMockListenerCalls: func(mock *interfaces.MockStateCallbackListener) { - // initial validator will trigger first - mock.EXPECT().OnValidatorAdded(testValidationIDs[0], testNodeIDs[0], uint64(0), true).Times(1) - // then it won't be called since we don't track the node ID changes - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(tt *testing.T) { - require := require.New(tt) - db := memdb.New() - validatorState, err := validators.NewState(db) - require.NoError(err) - - // set initial validators - for vID, validator := range test.initialValidators { - err := validatorState.AddValidator(interfaces.Validator{ - ValidationID: vID, - NodeID: validator.NodeID, - Weight: validator.Weight, - StartTimestamp: validator.StartTime, - IsActive: validator.IsActive, - IsSoV: validator.IsSoV, - }) - require.NoError(err) - } - // enable mock listener - ctrl := gomock.NewController(tt) - mockListener := interfaces.NewMockStateCallbackListener(ctrl) - test.registerMockListenerCalls(mockListener) - - validatorState.RegisterListener(mockListener) - // load new validators - err = loadValidators(validatorState, test.newValidators) - if test.expectedLoadErr != nil { - require.Error(err) - return - } - require.NoError(err) - // check if the state is as expected - require.Equal(len(test.newValidators), validatorState.GetValidationIDs().Len()) - for vID, validator := range test.newValidators { - v, err := validatorState.GetValidator(vID) - require.NoError(err) - require.Equal(validator.NodeID, v.NodeID) - require.Equal(validator.Weight, v.Weight) - require.Equal(validator.StartTime, v.StartTimestamp) - require.Equal(validator.IsActive, v.IsActive) - require.Equal(validator.IsSoV, v.IsSoV) - } - }) - } -} diff --git a/plugin/evm/vm_validators_test.go b/plugin/evm/vm_validators_test.go new file mode 100644 index 0000000000..d8d0719fde --- /dev/null +++ b/plugin/evm/vm_validators_test.go @@ -0,0 +1,161 @@ +// See the file LICENSE for licensing terms. + +package evm + +import ( + "context" + "testing" + "time" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + commonEng "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/enginetest" + avagovalidators "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/subnet-evm/core" + "github.com/ava-labs/subnet-evm/plugin/evm/validators" + "github.com/ava-labs/subnet-evm/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestValidatorState(t *testing.T) { + require := require.New(t) + genesis := &core.Genesis{} + require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONLatest))) + genesisJSON, err := genesis.MarshalJSON() + require.NoError(err) + + vm := &VM{} + ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisJSON)) + appSender := &enginetest.Sender{T: t} + appSender.CantSendAppGossip = true + testNodeIDs := []ids.NodeID{ + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + ids.GenerateTestNodeID(), + } + testValidationIDs := []ids.ID{ + ids.GenerateTestID(), + ids.GenerateTestID(), + ids.GenerateTestID(), + } + ctx.ValidatorState = &validatorstest.State{ + GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagovalidators.GetCurrentValidatorOutput, uint64, error) { + return map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[2]: { + NodeID: testNodeIDs[2], + PublicKey: nil, + Weight: 1, + }, + }, 0, nil + }, + } + appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } + err = vm.Initialize( + context.Background(), + ctx, + dbManager, + genesisBytes, + []byte(""), + []byte(""), + issuer, + []*commonEng.Fx{}, + appSender, + ) + require.NoError(err, "error initializing GenesisVM") + + // Test case 1: state should not be populated until bootstrapped + require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) + require.Equal(0, vm.validatorsManager.GetValidationIDs().Len()) + _, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0]) + require.ErrorIs(database.ErrNotFound, err) + require.False(vm.validatorsManager.StartedTracking()) + + // Test case 2: state should be populated after bootstrapped + require.NoError(vm.SetState(context.Background(), snow.NormalOp)) + require.Len(vm.validatorsManager.GetValidationIDs(), 3) + _, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0]) + require.NoError(err) + require.True(vm.validatorsManager.StartedTracking()) + + // Test case 3: restarting VM should not lose state + vm.Shutdown(context.Background()) + // Shutdown should stop tracking + require.False(vm.validatorsManager.StartedTracking()) + + vm = &VM{} + err = vm.Initialize( + context.Background(), + utils.TestSnowContext(), // this context does not have validators state, making VM to source it from the database + dbManager, + genesisBytes, + []byte(""), + []byte(""), + issuer, + []*commonEng.Fx{}, + appSender, + ) + require.NoError(err, "error initializing GenesisVM") + require.Len(vm.validatorsManager.GetValidationIDs(), 3) + _, _, err = vm.validatorsManager.CalculateUptime(testNodeIDs[0]) + require.NoError(err) + require.False(vm.validatorsManager.StartedTracking()) + + // Test case 4: new validators should be added to the state + newValidationID := ids.GenerateTestID() + newNodeID := ids.GenerateTestNodeID() + testState := &validatorstest.State{ + GetCurrentValidatorSetF: func(ctx context.Context, subnetID ids.ID) (map[ids.ID]*avagovalidators.GetCurrentValidatorOutput, uint64, error) { + return map[ids.ID]*avagovalidators.GetCurrentValidatorOutput{ + testValidationIDs[0]: { + NodeID: testNodeIDs[0], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[1]: { + NodeID: testNodeIDs[1], + PublicKey: nil, + Weight: 1, + }, + testValidationIDs[2]: { + NodeID: testNodeIDs[2], + PublicKey: nil, + Weight: 1, + }, + newValidationID: { + NodeID: newNodeID, + PublicKey: nil, + Weight: 1, + }, + }, 0, nil + }, + } + // set VM as bootstrapped + require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) + require.NoError(vm.SetState(context.Background(), snow.NormalOp)) + + vm.ctx.ValidatorState = testState + + // new validator should be added to the state eventually after SyncFrequency + require.EventuallyWithT(func(c *assert.CollectT) { + vm.ctx.Lock.Lock() + defer vm.ctx.Lock.Unlock() + assert.Len(c, vm.validatorsManager.GetNodeIDs(), 4) + newValidator, err := vm.validatorsManager.GetValidator(newValidationID) + assert.NoError(c, err) + assert.Equal(c, newNodeID, newValidator.NodeID) + }, validators.SyncFrequency*2, 5*time.Second) +} diff --git a/scripts/mocks.mockgen.txt b/scripts/mocks.mockgen.txt index 43a9d60cad..aba87c80da 100644 --- a/scripts/mocks.mockgen.txt +++ b/scripts/mocks.mockgen.txt @@ -1,3 +1,3 @@ github.com/ava-labs/subnet-evm/precompile/precompileconfig=Predicater,Config,ChainConfig,Accepter=precompile/precompileconfig/mocks.go github.com/ava-labs/subnet-evm/precompile/contract=BlockContext,AccessibleState,StateDB=precompile/contract/mocks.go -github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces=StateCallbackListener=plugin/evm/validators/interfaces/mock_listener.go +github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces=StateCallbackListener=plugin/evm/validators/state/interfaces/mock_listener.go diff --git a/warp/backend.go b/warp/backend.go index eb38f1f410..84e62c7a57 100644 --- a/warp/backend.go +++ b/warp/backend.go @@ -7,14 +7,12 @@ import ( "context" "errors" "fmt" - "sync" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/snow/consensus/snowman" - "github.com/ava-labs/avalanchego/snow/uptime" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" @@ -59,9 +57,7 @@ type backend struct { db database.Database warpSigner avalancheWarp.Signer blockClient BlockClient - uptimeCalculator uptime.Calculator - validatorState interfaces.State - stateLock sync.Locker + validatorReader interfaces.ValidatorReader signatureCache cache.Cacher[ids.ID, []byte] messageCache *cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage] offchainAddressedCallMsgs map[ids.ID]*avalancheWarp.UnsignedMessage @@ -74,9 +70,7 @@ func NewBackend( sourceChainID ids.ID, warpSigner avalancheWarp.Signer, blockClient BlockClient, - uptimeCalculator uptime.Calculator, - validatorsState interfaces.State, - stateLock sync.Locker, + validatorReader interfaces.ValidatorReader, db database.Database, signatureCache cache.Cacher[ids.ID, []byte], offchainMessages [][]byte, @@ -88,9 +82,7 @@ func NewBackend( warpSigner: warpSigner, blockClient: blockClient, signatureCache: signatureCache, - uptimeCalculator: uptimeCalculator, - validatorState: validatorsState, - stateLock: stateLock, + validatorReader: validatorReader, messageCache: &cache.LRU[ids.ID, *avalancheWarp.UnsignedMessage]{Size: messageCacheSize}, stats: newVerifierStats(), offchainAddressedCallMsgs: make(map[ids.ID]*avalancheWarp.UnsignedMessage), diff --git a/warp/backend_test.go b/warp/backend_test.go index 331bd7c1ff..cb98a2351c 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -5,19 +5,16 @@ package warp import ( "context" - "sync" "testing" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" "github.com/ava-labs/subnet-evm/warp/warptest" "github.com/stretchr/testify/require" ) @@ -48,7 +45,7 @@ func TestAddAndGetValidMessage(t *testing.T) { require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, warptest.NoOpValidatorReader{}, db, messageSignatureCache, nil) require.NoError(t, err) // Add testUnsignedMessage to the warp backend @@ -71,7 +68,7 @@ func TestAddAndGetUnknownMessage(t *testing.T) { require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, warptest.NoOpValidatorReader{}, db, messageSignatureCache, nil) require.NoError(t, err) // Try getting a signature for a message that was not added. @@ -90,7 +87,7 @@ func TestGetBlockSignature(t *testing.T) { require.NoError(err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, blockClient, warptest.NoOpValidatorReader{}, db, messageSignatureCache, nil) require.NoError(err) blockHashPayload, err := payload.NewHash(blkID) @@ -117,7 +114,7 @@ func TestZeroSizedCache(t *testing.T) { // Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0. messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, nil) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, warptest.NoOpValidatorReader{}, db, messageSignatureCache, nil) require.NoError(t, err) // Add testUnsignedMessage to the warp backend @@ -177,7 +174,7 @@ func TestOffChainMessages(t *testing.T) { db := memdb.New() messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 0} - backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, uptime.NoOpCalculator, validatorstest.NoOpState, &sync.RWMutex{}, db, messageSignatureCache, test.offchainMessages) + backend, err := NewBackend(networkID, sourceChainID, warpSigner, nil, warptest.NoOpValidatorReader{}, db, messageSignatureCache, test.offchainMessages) require.ErrorIs(err, test.err) if test.check != nil { test.check(require, backend) diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index af664d5f16..aa265d9b33 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -10,12 +10,10 @@ import ( "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/crypto/bls" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/plugin/evm/message" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/warp" "github.com/ava-labs/subnet-evm/warp/warptest" @@ -35,7 +33,7 @@ func TestMessageSignatureHandler(t *testing.T) { require.NoError(t, err) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 100} - backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptime.NoOpCalculator, validatorstest.NoOpState, snowCtx.Lock.RLocker(), database, messageSignatureCache, [][]byte{offchainMessage.Bytes()}) + backend, err := warp.NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, warptest.NoOpValidatorReader{}, database, messageSignatureCache, [][]byte{offchainMessage.Bytes()}) require.NoError(t, err) msg, err := avalancheWarp.NewUnsignedMessage(snowCtx.NetworkID, snowCtx.ChainID, []byte("test")) @@ -141,9 +139,7 @@ func TestBlockSignatureHandler(t *testing.T) { snowCtx.ChainID, warpSigner, blockClient, - uptime.NoOpCalculator, - validatorstest.NoOpState, - snowCtx.Lock.RLocker(), + warptest.NoOpValidatorReader{}, database, messageSignatureCache, nil, diff --git a/warp/verifier_backend.go b/warp/verifier_backend.go index 71a33356cc..255851d601 100644 --- a/warp/verifier_backend.go +++ b/warp/verifier_backend.go @@ -110,24 +110,11 @@ func (b *backend) verifyOffchainAddressedCall(addressedCall *payload.AddressedCa } func (b *backend) verifyUptimeMessage(uptimeMsg *messages.ValidatorUptime) *common.AppError { - b.stateLock.Lock() - defer b.stateLock.Unlock() - // first get the validator's nodeID - vdr, err := b.validatorState.GetValidator(uptimeMsg.ValidationID) + vdr, currentUptime, _, err := b.validatorReader.GetValidatorAndUptime(uptimeMsg.ValidationID) if err != nil { return &common.AppError{ Code: VerifyErrCode, - Message: fmt.Sprintf("failed to get validator for validationID %s: %s", uptimeMsg.ValidationID, err.Error()), - } - } - nodeID := vdr.NodeID - - // then get the current uptime - currentUptime, _, err := b.uptimeCalculator.CalculateUptime(nodeID) - if err != nil { - return &common.AppError{ - Code: VerifyErrCode, - Message: fmt.Sprintf("failed to calculate uptime for nodeID %s: %s", nodeID, err.Error()), + Message: fmt.Sprintf("failed to get uptime for validationID %s: %s", uptimeMsg.ValidationID, err.Error()), } } @@ -136,7 +123,7 @@ func (b *backend) verifyUptimeMessage(uptimeMsg *messages.ValidatorUptime) *comm if currentUptimeSeconds < uptimeMsg.TotalUptime { return &common.AppError{ Code: VerifyErrCode, - Message: fmt.Sprintf("current uptime %d is less than queried uptime %d for nodeID %s", currentUptimeSeconds, uptimeMsg.TotalUptime, nodeID), + Message: fmt.Sprintf("current uptime %d is less than queried uptime %d for nodeID %s", currentUptimeSeconds, uptimeMsg.TotalUptime, vdr.NodeID), } } diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index a3546f60c7..659c8b94b5 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -14,14 +14,12 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/timer/mockable" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/plugin/evm/validators" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/validatorstest" + stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/warp/messages" "github.com/ava-labs/subnet-evm/warp/warptest" @@ -104,7 +102,7 @@ func TestAddressedCallSignatures(t *testing.T) { } else { sigCache = &cache.Empty[ids.ID, []byte]{} } - warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptime.NoOpCalculator, validatorstest.NoOpState, snowCtx.Lock.RLocker(), database, sigCache, [][]byte{offchainMessage.Bytes()}) + warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, warptest.NoOpValidatorReader{}, database, sigCache, [][]byte{offchainMessage.Bytes()}) require.NoError(t, err) handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner) @@ -219,9 +217,7 @@ func TestBlockSignatures(t *testing.T) { snowCtx.ChainID, warpSigner, blockClient, - uptime.NoOpCalculator, - validatorstest.NoOpState, - snowCtx.Lock.RLocker(), + warptest.NoOpValidatorReader{}, database, sigCache, nil, @@ -291,12 +287,12 @@ func TestUptimeSignatures(t *testing.T) { } else { sigCache = &cache.Empty[ids.ID, []byte]{} } - state, err := validators.NewState(memdb.New()) - require.NoError(t, err) + chainCtx := utils.TestSnowContext() clk := &mockable.Clock{} - uptimeManager := uptime.NewManager(state, clk) - uptimeManager.StartTracking([]ids.NodeID{}) - warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, uptimeManager, state, snowCtx.Lock.RLocker(), database, sigCache, nil) + validatorsManager, err := validators.NewManager(chainCtx, memdb.New(), clk) + require.NoError(t, err) + validatorsManager.StartTracking([]ids.NodeID{}) + warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, validatorsManager, database, sigCache, nil) require.NoError(t, err) handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner) @@ -316,7 +312,7 @@ func TestUptimeSignatures(t *testing.T) { // uptime is less than requested (not connected) validationID := ids.GenerateTestID() nodeID := ids.GenerateTestNodeID() - require.NoError(t, state.AddValidator(interfaces.Validator{ + require.NoError(t, validatorsManager.AddValidator(stateinterfaces.Validator{ ValidationID: validationID, NodeID: nodeID, Weight: 1, @@ -330,7 +326,7 @@ func TestUptimeSignatures(t *testing.T) { require.Contains(t, appErr.Error(), "current uptime 0 is less than queried uptime 80") // uptime is less than requested (not enough) - require.NoError(t, uptimeManager.Connect(nodeID)) + require.NoError(t, validatorsManager.Connect(nodeID)) clk.Set(clk.Time().Add(40 * time.Second)) protoBytes, _ = getUptimeMessageBytes([]byte{}, validationID, 80) _, appErr = handler.AppRequest(context.Background(), nodeID, time.Time{}, protoBytes) diff --git a/warp/warptest/noop_validator_reader.go b/warp/warptest/noop_validator_reader.go new file mode 100644 index 0000000000..03171f84f5 --- /dev/null +++ b/warp/warptest/noop_validator_reader.go @@ -0,0 +1,21 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// warptest exposes common functionality for testing the warp package. +package warptest + +import ( + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" +) + +var _ interfaces.ValidatorReader = &NoOpValidatorReader{} + +type NoOpValidatorReader struct{} + +func (NoOpValidatorReader) GetValidatorAndUptime(ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) { + return stateinterfaces.Validator{}, 0, time.Time{}, nil +} From 0f3c51382778142257a0037d3fd1a460b77e1bb1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 16 Nov 2024 00:47:21 +0300 Subject: [PATCH 08/57] rename sov to l1validator (#1387) * fix start timestamp * add client * rename sov to l1validator --- plugin/evm/service.go | 4 +- plugin/evm/validators/manager.go | 2 +- plugin/evm/validators/manager_test.go | 4 +- .../evm/validators/state/interfaces/state.go | 2 +- plugin/evm/validators/state/state.go | 34 +++++------ plugin/evm/validators/state/state_test.go | 56 +++++++++---------- warp/verifier_backend_test.go | 2 +- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 625d169514..dcddd54eec 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -24,7 +24,7 @@ type CurrentValidator struct { Weight uint64 `json:"weight"` StartTimestamp uint64 `json:"startTimestamp"` IsActive bool `json:"isActive"` - IsSoV bool `json:"isSoV"` + IsL1Validator bool `json:"isL1Validator"` IsConnected bool `json:"isConnected"` Uptime time.Duration `json:"uptime"` } @@ -56,7 +56,7 @@ func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, rep StartTimestamp: validator.StartTimestamp, Weight: validator.Weight, IsActive: validator.IsActive, - IsSoV: validator.IsSoV, + IsL1Validator: validator.IsL1Validator, IsConnected: isConnected, Uptime: time.Duration(uptime.Seconds()), }) diff --git a/plugin/evm/validators/manager.go b/plugin/evm/validators/manager.go index e679be220c..188b423424 100644 --- a/plugin/evm/validators/manager.go +++ b/plugin/evm/validators/manager.go @@ -145,7 +145,7 @@ func loadValidators(validatorState stateinterfaces.State, newValidators map[ids. Weight: newVdr.Weight, StartTimestamp: newVdr.StartTime, IsActive: newVdr.IsActive, - IsSoV: newVdr.IsSoV, + IsL1Validator: newVdr.IsSoV, } if currentValidationIDs.Contains(newVID) { if err := validatorState.UpdateValidator(currentVdr); err != nil { diff --git a/plugin/evm/validators/manager_test.go b/plugin/evm/validators/manager_test.go index 282488f68f..85e7988857 100644 --- a/plugin/evm/validators/manager_test.go +++ b/plugin/evm/validators/manager_test.go @@ -188,7 +188,7 @@ func TestLoadNewValidators(t *testing.T) { Weight: validator.Weight, StartTimestamp: validator.StartTime, IsActive: validator.IsActive, - IsSoV: validator.IsSoV, + IsL1Validator: validator.IsSoV, }) require.NoError(err) } @@ -214,7 +214,7 @@ func TestLoadNewValidators(t *testing.T) { require.Equal(validator.Weight, v.Weight) require.Equal(validator.StartTime, v.StartTimestamp) require.Equal(validator.IsActive, v.IsActive) - require.Equal(validator.IsSoV, v.IsSoV) + require.Equal(validator.IsSoV, v.IsL1Validator) } }) } diff --git a/plugin/evm/validators/state/interfaces/state.go b/plugin/evm/validators/state/interfaces/state.go index ec7fac2416..13cb68ba10 100644 --- a/plugin/evm/validators/state/interfaces/state.go +++ b/plugin/evm/validators/state/interfaces/state.go @@ -49,5 +49,5 @@ type Validator struct { Weight uint64 `json:"weight"` StartTimestamp uint64 `json:"startTimestamp"` IsActive bool `json:"isActive"` - IsSoV bool `json:"isSoV"` + IsL1Validator bool `json:"isL1Validator"` } diff --git a/plugin/evm/validators/state/state.go b/plugin/evm/validators/state/state.go index 14c7912ea9..175a67a5bf 100644 --- a/plugin/evm/validators/state/state.go +++ b/plugin/evm/validators/state/state.go @@ -29,13 +29,13 @@ const ( ) type validatorData struct { - UpDuration time.Duration `serialize:"true"` - LastUpdated uint64 `serialize:"true"` - NodeID ids.NodeID `serialize:"true"` - Weight uint64 `serialize:"true"` - StartTime uint64 `serialize:"true"` - IsActive bool `serialize:"true"` - IsSoV bool `serialize:"true"` + UpDuration time.Duration `serialize:"true"` + LastUpdated uint64 `serialize:"true"` + NodeID ids.NodeID `serialize:"true"` + Weight uint64 `serialize:"true"` + StartTime uint64 `serialize:"true"` + IsActive bool `serialize:"true"` + IsL1Validator bool `serialize:"true"` validationID ids.ID // database key } @@ -105,14 +105,14 @@ func (s *state) GetStartTime(nodeID ids.NodeID) (time.Time, error) { // the new validator is marked as updated and will be written to the disk when WriteState is called func (s *state) AddValidator(vdr interfaces.Validator) error { data := &validatorData{ - NodeID: vdr.NodeID, - validationID: vdr.ValidationID, - IsActive: vdr.IsActive, - StartTime: vdr.StartTimestamp, - UpDuration: 0, - LastUpdated: vdr.StartTimestamp, - IsSoV: vdr.IsSoV, - Weight: vdr.Weight, + NodeID: vdr.NodeID, + validationID: vdr.ValidationID, + IsActive: vdr.IsActive, + StartTime: vdr.StartTimestamp, + UpDuration: 0, + LastUpdated: vdr.StartTimestamp, + IsL1Validator: vdr.IsL1Validator, + Weight: vdr.Weight, } if err := s.addData(vdr.ValidationID, data); err != nil { return err @@ -251,7 +251,7 @@ func (s *state) GetValidator(vID ids.ID) (interfaces.Validator, error) { StartTimestamp: data.StartTime, IsActive: data.IsActive, Weight: data.Weight, - IsSoV: data.IsSoV, + IsL1Validator: data.IsL1Validator, }, nil } @@ -345,6 +345,6 @@ func (v *validatorData) getStartTime() time.Time { func (v *validatorData) constantsAreUnmodified(u interfaces.Validator) bool { return v.validationID == u.ValidationID && v.NodeID == u.NodeID && - v.IsSoV == u.IsSoV && + v.IsL1Validator == u.IsL1Validator && v.StartTime == u.StartTimestamp } diff --git a/plugin/evm/validators/state/state_test.go b/plugin/evm/validators/state/state_test.go index b888de156d..4039c48604 100644 --- a/plugin/evm/validators/state/state_test.go +++ b/plugin/evm/validators/state/state_test.go @@ -42,7 +42,7 @@ func TestState(t *testing.T) { Weight: 1, StartTimestamp: uint64(startTime.Unix()), IsActive: true, - IsSoV: true, + IsL1Validator: true, } state.AddValidator(vdr) @@ -98,7 +98,7 @@ func TestState(t *testing.T) { require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) // set SoV should fail - vdr.IsSoV = false + vdr.IsL1Validator = false require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) // set validation ID should result in not found @@ -131,7 +131,7 @@ func TestWriteValidator(t *testing.T) { Weight: 1, StartTimestamp: uint64(startTime.Unix()), IsActive: true, - IsSoV: true, + IsL1Validator: true, })) // write state, should reflect to DB @@ -176,14 +176,14 @@ func TestParseValidator(t *testing.T) { name: "nil", bytes: nil, expected: &validatorData{ - LastUpdated: 0, - StartTime: 0, - validationID: ids.Empty, - NodeID: ids.EmptyNodeID, - UpDuration: 0, - Weight: 0, - IsActive: false, - IsSoV: false, + LastUpdated: 0, + StartTime: 0, + validationID: ids.Empty, + NodeID: ids.EmptyNodeID, + UpDuration: 0, + Weight: 0, + IsActive: false, + IsL1Validator: false, }, expectedErr: nil, }, @@ -191,14 +191,14 @@ func TestParseValidator(t *testing.T) { name: "empty", bytes: []byte{}, expected: &validatorData{ - LastUpdated: 0, - StartTime: 0, - validationID: ids.Empty, - NodeID: ids.EmptyNodeID, - UpDuration: 0, - Weight: 0, - IsActive: false, - IsSoV: false, + LastUpdated: 0, + StartTime: 0, + validationID: ids.Empty, + NodeID: ids.EmptyNodeID, + UpDuration: 0, + Weight: 0, + IsActive: false, + IsL1Validator: false, }, expectedErr: nil, }, @@ -225,13 +225,13 @@ func TestParseValidator(t *testing.T) { 0x01, }, expected: &validatorData{ - UpDuration: time.Duration(6000000), - LastUpdated: 900000, - NodeID: testNodeID, - StartTime: 6000000, - IsActive: true, - Weight: 1, - IsSoV: true, + UpDuration: time.Duration(6000000), + LastUpdated: 900000, + NodeID: testNodeID, + StartTime: 6000000, + IsActive: true, + Weight: 1, + IsL1Validator: true, }, }, { @@ -299,7 +299,7 @@ func TestStateListener(t *testing.T) { Weight: 1, StartTimestamp: uint64(initialStartTime.Unix()), IsActive: true, - IsSoV: true, + IsL1Validator: true, })) // register listener @@ -314,7 +314,7 @@ func TestStateListener(t *testing.T) { Weight: 1, StartTimestamp: uint64(expectedStartTime.Unix()), IsActive: true, - IsSoV: true, + IsL1Validator: true, } require.NoError(state.AddValidator(vdr)) diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index 659c8b94b5..747d757d5e 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -318,7 +318,7 @@ func TestUptimeSignatures(t *testing.T) { Weight: 1, StartTimestamp: clk.Unix(), IsActive: true, - IsSoV: true, + IsL1Validator: true, })) protoBytes, _ = getUptimeMessageBytes([]byte{}, validationID, 80) _, appErr = handler.AppRequest(context.Background(), nodeID, time.Time{}, protoBytes) From 4ef3c47e7d3188016c74758bd08ffa43ff7cda09 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 16 Nov 2024 01:22:56 +0300 Subject: [PATCH 09/57] bump avago to v1.11.13-rc.1 (#1389) * fix start timestamp * add client * rename sov to l1validator * bump avago to v1.11.13-rc.1 * update compatibility --- README.md | 1 + compatibility.json | 1 + go.mod | 4 ++-- go.sum | 8 ++++---- plugin/evm/validators/manager.go | 2 +- plugin/evm/validators/manager_test.go | 4 ++-- plugin/evm/validators/state/state_test.go | 4 ++-- plugin/evm/version.go | 2 +- scripts/versions.sh | 2 +- tests/warp/warp_test.go | 5 +++-- 10 files changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 186ff7dc14..d9ed5e5d9f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.6.9] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.10] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.11] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) +[v0.6.12] AvalancheGo@v1.11.13 (Protocol Version: 38) ``` ## API diff --git a/compatibility.json b/compatibility.json index 0a169e4e2f..f735a579fa 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.6.12": 38, "v0.6.11": 37, "v0.6.10": 37, "v0.6.9": 37, diff --git a/go.mod b/go.mod index 203611e4b5..7426edd516 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb + github.com/ava-labs/avalanchego v1.11.13-rc.1 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -54,7 +54,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.13.8 // indirect + github.com/ava-labs/coreth v0.13.9-rc.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index d06ba20439..fc29608ae3 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb h1:ujIv6zAKQpZtlalLGSt9cx2AREJmYcv0wMmXGRAH6/g= -github.com/ava-labs/avalanchego v1.11.13-0.20241113171850-4c199890fadb/go.mod h1:86tO6F1FT8emclUwdQ2WCwAtAerqjm5A4IbV6XxNUyM= -github.com/ava-labs/coreth v0.13.8 h1:f14X3KgwHl9LwzfxlN6S4bbn5VA2rhEsNnHaRLSTo/8= -github.com/ava-labs/coreth v0.13.8/go.mod h1:t3BSv/eQv0AlDPMfEDCMMoD/jq1RkUsbFzQAFg5qBcE= +github.com/ava-labs/avalanchego v1.11.13-rc.1 h1:lHAkvMo98y/4xvsRKZ4GUTEj0yPnRbGC4U68rC06/rs= +github.com/ava-labs/avalanchego v1.11.13-rc.1/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= +github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= +github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= 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= diff --git a/plugin/evm/validators/manager.go b/plugin/evm/validators/manager.go index 188b423424..be58ee4d2e 100644 --- a/plugin/evm/validators/manager.go +++ b/plugin/evm/validators/manager.go @@ -145,7 +145,7 @@ func loadValidators(validatorState stateinterfaces.State, newValidators map[ids. Weight: newVdr.Weight, StartTimestamp: newVdr.StartTime, IsActive: newVdr.IsActive, - IsL1Validator: newVdr.IsSoV, + IsL1Validator: newVdr.IsL1Validator, } if currentValidationIDs.Contains(newVID) { if err := validatorState.UpdateValidator(currentVdr); err != nil { diff --git a/plugin/evm/validators/manager_test.go b/plugin/evm/validators/manager_test.go index 85e7988857..e73fcca26c 100644 --- a/plugin/evm/validators/manager_test.go +++ b/plugin/evm/validators/manager_test.go @@ -188,7 +188,7 @@ func TestLoadNewValidators(t *testing.T) { Weight: validator.Weight, StartTimestamp: validator.StartTime, IsActive: validator.IsActive, - IsL1Validator: validator.IsSoV, + IsL1Validator: validator.IsL1Validator, }) require.NoError(err) } @@ -214,7 +214,7 @@ func TestLoadNewValidators(t *testing.T) { require.Equal(validator.Weight, v.Weight) require.Equal(validator.StartTime, v.StartTimestamp) require.Equal(validator.IsActive, v.IsActive) - require.Equal(validator.IsSoV, v.IsL1Validator) + require.Equal(validator.IsL1Validator, v.IsL1Validator) } }) } diff --git a/plugin/evm/validators/state/state_test.go b/plugin/evm/validators/state/state_test.go index 4039c48604..852a654109 100644 --- a/plugin/evm/validators/state/state_test.go +++ b/plugin/evm/validators/state/state_test.go @@ -97,7 +97,7 @@ func TestState(t *testing.T) { vdr.StartTimestamp = vdr.StartTimestamp + 100 require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) - // set SoV should fail + // set IsL1Validator should fail vdr.IsL1Validator = false require.ErrorIs(state.UpdateValidator(vdr), ErrImmutableField) @@ -221,7 +221,7 @@ func TestParseValidator(t *testing.T) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x8D, 0x80, // status 0x01, - // is SoV + // IsL1Validator 0x01, }, expected: &validatorData{ diff --git a/plugin/evm/version.go b/plugin/evm/version.go index 4ab2b39409..619db5485a 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.6.11" + Version string = "v0.6.12" ) func init() { diff --git a/scripts/versions.sh b/scripts/versions.sh index 4c045d5632..197c0b978d 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,7 +4,7 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'4c199890'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.13-rc.1'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} # This won't be used, but it's here to make code syncs easier diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go index 57567ae451..0960dac989 100644 --- a/tests/warp/warp_test.go +++ b/tests/warp/warp_test.go @@ -31,6 +31,7 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/ava-labs/avalanchego/vms/platformvm/api" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -402,9 +403,9 @@ func (w *warpTest) aggregateSignaturesViaAPI() { // If the destination turns out to be the Primary Network as well, then this is a no-op. var validators map[ids.NodeID]*validators.GetValidatorOutput if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { - validators, err = pChainClient.GetValidatorsAt(ctx, w.receivingSubnet.SubnetID, pChainHeight) + validators, err = pChainClient.GetValidatorsAt(ctx, w.receivingSubnet.SubnetID, api.Height(pChainHeight)) } else { - validators, err = pChainClient.GetValidatorsAt(ctx, w.sendingSubnet.SubnetID, pChainHeight) + validators, err = pChainClient.GetValidatorsAt(ctx, w.sendingSubnet.SubnetID, api.Height(pChainHeight)) } require.NoError(err) require.NotZero(len(validators)) From 6083668625f2518ec513417040421aca8254d882 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 19 Nov 2024 17:22:47 +0300 Subject: [PATCH 10/57] use nodeID filter and uptimePerc (#1390) * use nodeID filter and uptimePerc * add uptime seconds --- plugin/evm/client.go | 9 ++- plugin/evm/service.go | 72 +++++++++++++------ .../evm/validators/state/interfaces/state.go | 2 + plugin/evm/validators/state/state.go | 9 +++ 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/plugin/evm/client.go b/plugin/evm/client.go index f87507da56..36a0ee675f 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client.go @@ -10,6 +10,7 @@ import ( "golang.org/x/exp/slog" "github.com/ava-labs/avalanchego/api" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/rpc" ) @@ -24,7 +25,7 @@ type Client interface { LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) - GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) + GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) } // Client implementation for interacting with EVM [chain] @@ -77,8 +78,10 @@ func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Confi } // GetCurrentValidators returns the current validators -func (c *client) GetCurrentValidators(ctx context.Context, options ...rpc.Option) ([]CurrentValidator, error) { +func (c *client) GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) { res := &GetCurrentValidatorsResponse{} - err := c.validatorsRequester.SendRequest(ctx, "validators.getCurrentValidators", struct{}{}, res, options...) + err := c.validatorsRequester.SendRequest(ctx, "validators.getCurrentValidators", &GetCurrentValidatorsRequest{ + NodeIDs: nodeIDs, + }, res, options...) return res.Validators, err } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index dcddd54eec..0ad10a63b6 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -4,61 +4,93 @@ package evm import ( + "fmt" "net/http" "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" ) type ValidatorsAPI struct { vm *VM } +type GetCurrentValidatorsRequest struct { + NodeIDs []ids.NodeID `json:"nodeIDs"` +} + type GetCurrentValidatorsResponse struct { Validators []CurrentValidator `json:"validators"` } type CurrentValidator struct { - ValidationID ids.ID `json:"validationID"` - NodeID ids.NodeID `json:"nodeID"` - Weight uint64 `json:"weight"` - StartTimestamp uint64 `json:"startTimestamp"` - IsActive bool `json:"isActive"` - IsL1Validator bool `json:"isL1Validator"` - IsConnected bool `json:"isConnected"` - Uptime time.Duration `json:"uptime"` + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTimestamp uint64 `json:"startTimestamp"` + IsActive bool `json:"isActive"` + IsL1Validator bool `json:"isL1Validator"` + IsConnected bool `json:"isConnected"` + UptimePercentage float32 `json:"uptimePercentage"` + UptimeSeconds uint64 `json:"uptimeSeconds"` } -func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, _ *struct{}, reply *GetCurrentValidatorsResponse) error { +func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *GetCurrentValidatorsRequest, reply *GetCurrentValidatorsResponse) error { api.vm.ctx.Lock.RLock() defer api.vm.ctx.Lock.RUnlock() - vIDs := api.vm.validatorsManager.GetValidationIDs() + var vIDs set.Set[ids.ID] + if len(req.NodeIDs) > 0 { + vIDs = set.NewSet[ids.ID](len(req.NodeIDs)) + for _, nodeID := range req.NodeIDs { + vID, err := api.vm.validatorsManager.GetValidationID(nodeID) + if err != nil { + return fmt.Errorf("couldn't find validator with node ID %s", nodeID) + } + vIDs.Add(vID) + } + } else { + vIDs = api.vm.validatorsManager.GetValidationIDs() + } reply.Validators = make([]CurrentValidator, 0, vIDs.Len()) for _, vID := range vIDs.List() { validator, err := api.vm.validatorsManager.GetValidator(vID) if err != nil { - return err + return fmt.Errorf("couldn't find validator with validation ID %s", vID) } isConnected := api.vm.validatorsManager.IsConnected(validator.NodeID) - uptime, _, err := api.vm.validatorsManager.CalculateUptime(validator.NodeID) + upDuration, lastUpdated, err := api.vm.validatorsManager.CalculateUptime(validator.NodeID) if err != nil { return err } + var uptimeFloat float64 + startTime := time.Unix(int64(validator.StartTimestamp), 0) + bestPossibleUpDuration := lastUpdated.Sub(startTime) + if bestPossibleUpDuration == 0 { + uptimeFloat = 1 + } else { + uptimeFloat = float64(upDuration) / float64(bestPossibleUpDuration) + } + + // Transform this to a percentage (0-100) to make it consistent + // with currentValidators in PlatformVM API + uptimePercentage := float32(uptimeFloat * 100) reply.Validators = append(reply.Validators, CurrentValidator{ - ValidationID: validator.ValidationID, - NodeID: validator.NodeID, - StartTimestamp: validator.StartTimestamp, - Weight: validator.Weight, - IsActive: validator.IsActive, - IsL1Validator: validator.IsL1Validator, - IsConnected: isConnected, - Uptime: time.Duration(uptime.Seconds()), + ValidationID: validator.ValidationID, + NodeID: validator.NodeID, + StartTimestamp: validator.StartTimestamp, + Weight: validator.Weight, + IsActive: validator.IsActive, + IsL1Validator: validator.IsL1Validator, + IsConnected: isConnected, + UptimePercentage: uptimePercentage, + UptimeSeconds: uint64(upDuration.Seconds()), }) } return nil diff --git a/plugin/evm/validators/state/interfaces/state.go b/plugin/evm/validators/state/interfaces/state.go index 13cb68ba10..6ed743d09c 100644 --- a/plugin/evm/validators/state/interfaces/state.go +++ b/plugin/evm/validators/state/interfaces/state.go @@ -16,6 +16,8 @@ type StateReader interface { GetValidationIDs() set.Set[ids.ID] // GetNodeIDs returns the validator node IDs in the state GetNodeIDs() set.Set[ids.NodeID] + // GetValidationID returns the validation ID for the given node ID + GetValidationID(nodeID ids.NodeID) (ids.ID, error) } type State interface { diff --git a/plugin/evm/validators/state/state.go b/plugin/evm/validators/state/state.go index 175a67a5bf..c3150c10ac 100644 --- a/plugin/evm/validators/state/state.go +++ b/plugin/evm/validators/state/state.go @@ -239,6 +239,15 @@ func (s *state) GetNodeIDs() set.Set[ids.NodeID] { return ids } +// GetValidationID returns the validation ID for the given nodeID +func (s *state) GetValidationID(nodeID ids.NodeID) (ids.ID, error) { + vID, exists := s.index[nodeID] + if !exists { + return ids.ID{}, database.ErrNotFound + } + return vID, nil +} + // GetValidator returns the validator data for the given validationID func (s *state) GetValidator(vID ids.ID) (interfaces.Validator, error) { data, ok := s.data[vID] From 038c939247d6ade4af4717bbd33d1aa2c916eac4 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 19 Nov 2024 17:36:45 +0300 Subject: [PATCH 11/57] bump avago to v1.11.13 (#1391) --- README.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- scripts/versions.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d9ed5e5d9f..568bfbf7dc 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.6.9] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.10] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.11] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) -[v0.6.12] AvalancheGo@v1.11.13 (Protocol Version: 38) +[v0.6.12] AvalancheGo@v1.11.13/v1.12.0-fuji (Protocol Version: 38) ``` ## API diff --git a/go.mod b/go.mod index 7426edd516..798b9df04b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.13-rc.1 + github.com/ava-labs/avalanchego v1.11.13 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index fc29608ae3..5970cf1ed8 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.11.13-rc.1 h1:lHAkvMo98y/4xvsRKZ4GUTEj0yPnRbGC4U68rC06/rs= -github.com/ava-labs/avalanchego v1.11.13-rc.1/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= +github.com/ava-labs/avalanchego v1.11.13 h1:1lcDZ9ILZgeiv7IwL4TuFTyglgZMr9QBOnpLHX+Qy5k= +github.com/ava-labs/avalanchego v1.11.13/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/scripts/versions.sh b/scripts/versions.sh index 197c0b978d..9051e59873 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,7 +4,7 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.13-rc.1'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.13'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} # This won't be used, but it's here to make code syncs easier From f339ea44ee5039f9e3a08965f4a846254038e702 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 19 Nov 2024 14:05:30 -0800 Subject: [PATCH 12/57] specify arm64 version in goreleaser (#1392) --- .github/workflows/release.yml | 3 ++- .goreleaser.yml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84113cffe1..7d1c4f57cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: "Tag to include in artifact name" + description: "Tag to checkout & release" required: true push: tags: @@ -20,6 +20,7 @@ jobs: with: fetch-depth: 0 path: subnet-evm + ref: ${{ github.event.inputs.tag }} - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/.goreleaser.yml b/.goreleaser.yml index bec5952578..62808b512d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,10 +18,12 @@ builds: overrides: - goos: linux goarch: arm64 + goarm64: v8.0 env: - CC=aarch64-linux-gnu-gcc - goos: darwin goarch: arm64 + goarm64: v8.0 env: - CC=oa64-clang - goos: darwin From 5853b9cf5e01d9bfe260fbcee31dc40ce220aaeb Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 20 Nov 2024 16:48:50 +0300 Subject: [PATCH 13/57] bump to latest avago (#1394) --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3540ea2616..784c5b4a78 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -140,7 +140,7 @@ jobs: shell: bash run: ./scripts/build.sh - name: Run Warp E2E Tests - uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.11-monitoring-url + uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.13 with: run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/run_ginkgo_warp.sh prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} @@ -172,7 +172,7 @@ jobs: shell: bash run: ./scripts/build.sh - name: Run E2E Load Tests - uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.11-monitoring-url + uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.13 with: run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/run_ginkgo_load.sh prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} From 7eb3f81f79d11698fb0d5ca61dc444041af1f653 Mon Sep 17 00:00:00 2001 From: mountcount <166301065+mountcount@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:38:57 +0800 Subject: [PATCH 14/57] chore: fix problematic const name and some typos in comment (#1400) Signed-off-by: mountcount --- core/state/pruner/pruner.go | 2 +- miner/worker.go | 2 +- plugin/evm/vm_test.go | 2 +- sync/statesync/trie_sync_stats.go | 2 +- warp/aggregator/aggregator_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 96e27d28cd..c290171b06 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -52,7 +52,7 @@ const ( // stateBloomFilePrefix is the filename prefix of state bloom filter. stateBloomFilePrefix = "statebloom" - // stateBloomFilePrefix is the filename suffix of state bloom filter. + // stateBloomFileSuffix is the filename suffix of state bloom filter. stateBloomFileSuffix = "bf.gz" // stateBloomFileTempSuffix is the filename suffix of state bloom filter diff --git a/miner/worker.go b/miner/worker.go index 4fd7919553..a7c2a43665 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -430,7 +430,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac continue } // Abort transaction if it won't fit in the block and continue to search for a smaller - // transction that will fit. + // transaction that will fit. if totalTxsSize := env.size + tx.Size(); totalTxsSize > targetTxsSize { log.Trace("Skipping transaction that would exceed target size", "hash", tx.Hash(), "totalTxsSize", totalTxsSize, "txSize", tx.Size()) txs.Pop() diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 55fde2844f..194b3ff183 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -1919,7 +1919,7 @@ func TestConfigureLogLevel(t *testing.T) { } } - // If the VM was not initialized, do not attept to shut it down + // If the VM was not initialized, do not attempt to shut it down if err == nil { shutdownChan := make(chan error, 1) shutdownFunc := func() { diff --git a/sync/statesync/trie_sync_stats.go b/sync/statesync/trie_sync_stats.go index 217c649181..490dc88cce 100644 --- a/sync/statesync/trie_sync_stats.go +++ b/sync/statesync/trie_sync_stats.go @@ -79,7 +79,7 @@ func (t *trieSyncStats) incLeafs(segment *trieSegment, count uint64, remaining u } } -// estimateSegmentsInProgressTime retrns the ETA for all trie segments +// estimateSegmentsInProgressTime returns the ETA for all trie segments // in progress to finish (uses the one with most remaining leafs to estimate). func (t *trieSyncStats) estimateSegmentsInProgressTime() time.Duration { if len(t.remainingLeafs) == 0 { diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index a423f75b25..1fedec6d36 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -230,7 +230,7 @@ func TestAggregateSignatures(t *testing.T) { expectedErr: nil, }, { - name: "early termination of signature fetching on parent context cancelation", + name: "early termination of signature fetching on parent context cancellation", contextWithCancelFunc: func() (context.Context, context.CancelFunc) { ctx, cancel := context.WithCancel(context.Background()) cancel() From ee040d046f6b44dbebda3626692fe613faa0e432 Mon Sep 17 00:00:00 2001 From: chuangjinglu Date: Mon, 2 Dec 2024 18:47:04 +0800 Subject: [PATCH 15/57] chore: fix some function names in interface comment (#1397) Signed-off-by: chuangjinglu Co-authored-by: Ceyhun Onur --- precompile/contract/interfaces.go | 2 +- rpc/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index 7bc5d750a5..2cb3693fbd 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -60,7 +60,7 @@ type ConfigurationBlockContext interface { type BlockContext interface { ConfigurationBlockContext - // GetResults returns an arbitrary byte array result of verifying the predicates + // GetPredicateResults returns an arbitrary byte array result of verifying the predicates // of the given transaction, precompile address pair. GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte } diff --git a/rpc/types.go b/rpc/types.go index b2f5b98528..c96e7bc74f 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -62,7 +62,7 @@ type ServerCodec interface { type jsonWriter interface { // writeJSON writes a message to the connection. writeJSON(ctx context.Context, msg interface{}, isError bool) error - // writeJSON writes a message to the connection with the option of skipping the deadline. + // writeJSONSkipDeadline writes a message to the connection with the option of skipping the deadline. writeJSONSkipDeadline(ctx context.Context, msg interface{}, isError bool, skip bool) error // Closed returns a channel which is closed when the connection is closed. closed() <-chan interface{} From 6c98da796f359335f2dcfd1151af191584be8d74 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 5 Dec 2024 19:50:27 +0300 Subject: [PATCH 16/57] bump avago to v1.12.0 (#1402) --- go.mod | 4 ++-- go.sum | 4 ++-- scripts/versions.sh | 5 +---- tests/antithesis/main.go | 31 ++++++++++++++++++++----------- tests/utils/subnet.go | 10 +++------- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 798b9df04b..f1710880af 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.13 + github.com/ava-labs/avalanchego v1.12.0 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -41,6 +41,7 @@ require ( github.com/urfave/cli/v2 v2.25.7 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 + go.uber.org/zap v1.26.0 golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e golang.org/x/sync v0.8.0 @@ -153,7 +154,6 @@ require ( 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 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/term v0.23.0 // indirect diff --git a/go.sum b/go.sum index 5970cf1ed8..5aee53a511 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.11.13 h1:1lcDZ9ILZgeiv7IwL4TuFTyglgZMr9QBOnpLHX+Qy5k= -github.com/ava-labs/avalanchego v1.11.13/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= +github.com/ava-labs/avalanchego v1.12.0 h1:NBx0vSOY1dCT0PeJzojIhNhx0NMQNem4GgTEN+v8Sx4= +github.com/ava-labs/avalanchego v1.12.0/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/scripts/versions.sh b/scripts/versions.sh index 9051e59873..8d0d7d8cc6 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,8 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.13'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.0'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} - -# This won't be used, but it's here to make code syncs easier -LATEST_CORETH_VERSION='v0.13.7' diff --git a/tests/antithesis/main.go b/tests/antithesis/main.go index a58893a1be..7442f44f2d 100644 --- a/tests/antithesis/main.go +++ b/tests/antithesis/main.go @@ -8,7 +8,6 @@ import ( "crypto/ecdsa" "crypto/rand" "fmt" - "log" "math/big" "path/filepath" "time" @@ -18,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" + "go.uber.org/zap" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/tests/antithesis" @@ -31,13 +31,15 @@ import ( "github.com/ava-labs/subnet-evm/tests/utils" ago_tests "github.com/ava-labs/avalanchego/tests" + "github.com/ava-labs/avalanchego/utils/logging" timerpkg "github.com/ava-labs/avalanchego/utils/timer" ) const NumKeys = 5 func main() { - tc := ago_tests.NewTestContext() + logger := ago_tests.NewDefaultLogger("") + tc := ago_tests.NewTestContext(logger) defer tc.Cleanup() require := require.New(tc) @@ -60,7 +62,9 @@ func main() { ctx := ago_tests.DefaultNotifyContext(c.Duration, tc.DeferCleanup) require.Len(c.ChainIDs, 1) - log.Printf("CHAIN IDS: %v", c.ChainIDs) + logger.Info("Starting testing", + zap.Strings("chainIDs", c.ChainIDs), + ) chainID, err := ids.FromString(c.ChainIDs[0]) require.NoError(err, "failed to parse chainID") @@ -69,6 +73,7 @@ func main() { genesisKey := tmpnet.HardhatKey.ToECDSA() genesisWorkload := &workload{ id: 0, + log: ago_tests.NewDefaultLogger(fmt.Sprintf("worker %d", 0)), client: genesisClient, key: genesisKey, uris: c.URIs, @@ -82,13 +87,14 @@ func main() { key, err := crypto.ToECDSA(crypto.Keccak256([]byte{uint8(i)})) require.NoError(err, "failed to generate key") - require.NoError(transferFunds(ctx, genesisClient, genesisKey, crypto.PubkeyToAddress(key.PublicKey), initialAmount)) + require.NoError(transferFunds(ctx, genesisClient, genesisKey, crypto.PubkeyToAddress(key.PublicKey), initialAmount, logger)) client, err := ethclient.Dial(getChainURI(c.URIs[i%len(c.URIs)], chainID.String())) require.NoError(err, "failed to dial chain") workloads[i] = &workload{ id: i, + log: ago_tests.NewDefaultLogger(fmt.Sprintf("worker %d", i)), client: client, key: key, uris: c.URIs, @@ -109,6 +115,7 @@ func main() { type workload struct { id int client ethclient.Client + log logging.Logger key *ecdsa.PrivateKey uris []string } @@ -116,7 +123,7 @@ type workload struct { func (w *workload) run(ctx context.Context) { timer := timerpkg.StoppedTimer() - tc := ago_tests.NewTestContext() + tc := ago_tests.NewTestContext(w.log) defer tc.Cleanup() require := require.New(tc) @@ -132,12 +139,14 @@ func (w *workload) run(ctx context.Context) { for { // TODO(marun) Exercise a wider variety of transactions recipientEthAddress := crypto.PubkeyToAddress(w.key.PublicKey) - err := transferFunds(ctx, w.client, w.key, recipientEthAddress, txAmount) + err := transferFunds(ctx, w.client, w.key, recipientEthAddress, txAmount, w.log) if err != nil { // Log the error and continue since the problem may be // transient. require.NoError is only for errors that should stop // execution. - log.Printf("failed to transfer funds: %s", err) + w.log.Info("failed to transfer funds", + zap.Error(err), + ) } val, err := rand.Int(rand.Reader, big.NewInt(int64(time.Second))) @@ -156,7 +165,7 @@ func getChainURI(nodeURI string, blockchainID string) string { return fmt.Sprintf("%s/ext/bc/%s/rpc", nodeURI, blockchainID) } -func transferFunds(ctx context.Context, client ethclient.Client, key *ecdsa.PrivateKey, recipientAddress common.Address, txAmount uint64) error { +func transferFunds(ctx context.Context, client ethclient.Client, key *ecdsa.PrivateKey, recipientAddress common.Address, txAmount uint64, log logging.Logger) error { chainID, err := client.ChainID(ctx) if err != nil { return fmt.Errorf("failed to fetch chainID: %w", err) @@ -188,17 +197,17 @@ func transferFunds(ctx context.Context, client ethclient.Client, key *ecdsa.Priv return fmt.Errorf("failed to format transaction: %w", err) } - log.Printf("sending transaction with ID %s and nonce %d\n", tx.Hash(), acceptedNonce) + log.Info("sending transaction", zap.Stringer("txID", tx.Hash()), zap.Uint64("nonce", acceptedNonce)) err = client.SendTransaction(ctx, tx) if err != nil { return fmt.Errorf("failed to send transaction: %w", err) } - log.Printf("waiting for acceptance of transaction with ID %s\n", tx.Hash()) + log.Info("waiting for acceptance of transaction", zap.Stringer("txID", tx.Hash())) if _, err := bind.WaitMined(ctx, client, tx); err != nil { return fmt.Errorf("failed to wait for receipt: %v", err) } - log.Printf("confirmed acceptance of transaction with ID %s\n", tx.Hash()) + log.Info("confirmed acceptance of transaction", zap.Stringer("txID", tx.Hash())) return nil } diff --git a/tests/utils/subnet.go b/tests/utils/subnet.go index 6f7aea3500..66358bf7dc 100644 --- a/tests/utils/subnet.go +++ b/tests/utils/subnet.go @@ -64,7 +64,7 @@ func CreateSubnetsSuite(genesisFiles map[string]string) *SubnetSuite { // each test case. Each test case has its own subnet, therefore all tests // can run in parallel without any issue. // - var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { + _ = ginkgo.SynchronizedBeforeSuite(func() []byte { ctx, cancel := context.WithTimeout(context.Background(), BootAvalancheNodeTimeout) defer cancel() @@ -101,7 +101,7 @@ func CreateSubnetsSuite(genesisFiles map[string]string) *SubnetSuite { // SynchronizedAfterSuite() takes two functions, the first runs after each test suite is done and the second // function is executed once when all the tests are done. This function is used // to gracefully shutdown the AvalancheGo node. - var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { + _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { require.NotNil(startCmd) require.NoError(startCmd.Stop()) }) @@ -118,11 +118,7 @@ func CreateNewSubnet(ctx context.Context, genesisFilePath string) string { // MakeWallet fetches the available UTXOs owned by [kc] on the network // that [LocalAPIURI] is hosting. - wallet, err := wallet.MakeWallet(ctx, &wallet.WalletConfig{ - URI: DefaultLocalNodeURI, - AVAXKeychain: kc, - EthKeychain: kc, - }) + wallet, err := wallet.MakeWallet(ctx, DefaultLocalNodeURI, kc, kc, wallet.WalletConfig{}) require.NoError(err) pWallet := wallet.P() From 843e3f7a8305f7c0dc4d20efaf37a3fd842615a1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 10 Dec 2024 20:20:57 +0300 Subject: [PATCH 17/57] close database on shutdown (#1403) * bump avago to v1.12.0 * close database on shutdown * update release notes * rename verdb * close only if standalone db * bump avago branch version * bump avago --- RELEASES.md | 8 ++++++++ go.mod | 2 +- go.sum | 4 ++-- plugin/evm/block.go | 4 ++-- plugin/evm/database/database.go | 21 ++++++++++++++++++++- plugin/evm/syncervm_test.go | 2 +- plugin/evm/vm.go | 18 +++++++++++++++--- plugin/evm/vm_database.go | 8 +++++--- scripts/versions.sh | 2 +- 9 files changed, 55 insertions(+), 14 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 9781320c7d..c4beb051e1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,14 @@ ## Pending Release +## Updates + +- Changed default write option from `Sync` to `NoSync` in PebbleDB + +## Fixes + +- Fixed database close on shutdown + ## [v0.6.11](https://github.com/ava-labs/subnet-evm/releases/tag/v0.6.11) This release focuses on Standalone DB and database configs. diff --git a/go.mod b/go.mod index f1710880af..1c7a2a6a7e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.0 + github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 5aee53a511..e1c2cc5199 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.0 h1:NBx0vSOY1dCT0PeJzojIhNhx0NMQNem4GgTEN+v8Sx4= -github.com/ava-labs/avalanchego v1.12.0/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= +github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync h1:HvtadR1VLxsOe7Y2PSnNmLUXPvp1RnTgeWtk5cOuGYA= +github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/plugin/evm/block.go b/plugin/evm/block.go index fac7689768..1c37e98086 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -55,7 +55,7 @@ func (b *Block) Accept(context.Context) error { // Although returning an error from Accept is considered fatal, it is good // practice to cleanup the batch we were modifying in the case of an error. - defer vm.db.Abort() + defer vm.versiondb.Abort() log.Debug(fmt.Sprintf("Accepting block %s (%s) at height %d", b.ID().Hex(), b.ID(), b.Height())) @@ -74,7 +74,7 @@ func (b *Block) Accept(context.Context) error { return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err) } - return b.vm.db.Commit() + return b.vm.versiondb.Commit() } // handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements diff --git a/plugin/evm/database/database.go b/plugin/evm/database/database.go index 536bb9c36a..2226a1c895 100644 --- a/plugin/evm/database/database.go +++ b/plugin/evm/database/database.go @@ -4,6 +4,7 @@ package database import ( + "encoding/json" "fmt" "path/filepath" @@ -16,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/database/versiondb" avalanchenode "github.com/ava-labs/avalanchego/node" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/prometheus/client_golang/prometheus" ) const ( @@ -44,7 +46,7 @@ func NewStandaloneDatabase(dbConfig avalanchenode.DatabaseConfig, gatherer metri db = memdb.New() case pebbledb.Name: dbPath := filepath.Join(dbConfig.Path, pebbledb.Name) - db, err = pebbledb.New(dbPath, dbConfig.Config, logger, dbRegisterer) + db, err = NewPebbleDB(dbPath, dbConfig.Config, logger, dbRegisterer) if err != nil { return nil, fmt.Errorf("couldn't create %s at %s: %w", pebbledb.Name, dbPath, err) } @@ -77,3 +79,20 @@ func NewStandaloneDatabase(dbConfig avalanchenode.DatabaseConfig, gatherer metri return db, nil } + +func NewPebbleDB(file string, configBytes []byte, log logging.Logger, dbRegisterer prometheus.Registerer) (database.Database, error) { + cfg := pebbledb.DefaultConfig + // Use no sync for pebble db + cfg.Sync = false + if len(configBytes) > 0 { + if err := json.Unmarshal(configBytes, &cfg); err != nil { + return nil, err + } + } + // Marshal the config back to bytes to ensure that new defaults are applied + newCfgBytes, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + return pebbledb.New(file, newCfgBytes, log, dbRegisterer) +} diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 823452ea78..349647b525 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -431,7 +431,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if test.expectedErr != nil { require.ErrorIs(err, test.expectedErr) // Note we re-open the database here to avoid a closed error when the test is for a shutdown VM. - chaindb := database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, syncerVM.db)) + chaindb := database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, syncerVM.versiondb)) assertSyncPerformedHeights(t, chaindb, map[uint64]struct{}{}) return } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 4966114884..edf3228f88 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -195,8 +195,11 @@ type VM struct { blockChain *core.BlockChain miner *miner.Miner - // [db] is the VM's current database managed by ChainState - db *versiondb.Database + // [versiondb] is the VM's current versioned database + versiondb *versiondb.Database + + // [db] is the VM's current database + db database.Database // metadataDB is used to store one off keys. metadataDB database.Database @@ -204,6 +207,8 @@ type VM struct { // [chaindb] is the database supplied to the Ethereum backend chaindb ethdb.Database + usingStandaloneDB bool + // [acceptedBlockDB] is the database to store the last accepted // block. acceptedBlockDB database.Database @@ -630,7 +635,7 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { chaindb: vm.chaindb, metadataDB: vm.metadataDB, acceptedBlockDB: vm.acceptedBlockDB, - db: vm.db, + db: vm.versiondb, toEngine: vm.toEngine, }) @@ -880,6 +885,13 @@ func (vm *VM) Shutdown(context.Context) error { } vm.eth.Stop() log.Info("Ethereum backend stop completed") + if vm.usingStandaloneDB { + if err := vm.db.Close(); err != nil { + log.Error("failed to close database: %w", err) + } else { + log.Info("Database closed") + } + } vm.shutdownWg.Wait() log.Info("Subnet-EVM Shutdown completed") return nil diff --git a/plugin/evm/vm_database.go b/plugin/evm/vm_database.go index 54d37ed8e0..01385ab721 100644 --- a/plugin/evm/vm_database.go +++ b/plugin/evm/vm_database.go @@ -46,14 +46,16 @@ func (vm *VM) initializeDBs(avaDB avalanchedatabase.Database) error { if err != nil { return err } + vm.usingStandaloneDB = true } } // Use NewNested rather than New so that the structure of the database // remains the same regardless of the provided baseDB type. vm.chaindb = rawdb.NewDatabase(database.WrapDatabase(prefixdb.NewNested(ethDBPrefix, db))) - vm.db = versiondb.New(db) - vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) - vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) + vm.versiondb = versiondb.New(db) + vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.versiondb) + vm.metadataDB = prefixdb.New(metadataPrefix, vm.versiondb) + vm.db = db // Note warpDB and validatorsDB are not part of versiondb because it is not necessary // that they are committed to the database atomically with // the last accepted block. diff --git a/scripts/versions.sh b/scripts/versions.sh index 8d0d7d8cc6..14b407c24f 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.0'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.0-config-pebble-sync'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} From ae4064a0f463e83d944287b8c2506504df9f88f2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 10 Dec 2024 20:36:24 +0300 Subject: [PATCH 18/57] bump versions (#1406) * bump avago to v1.12.0 * close database on shutdown * update release notes * rename verdb * close only if standalone db * bump avago branch version * bump avago * bump versions --------- Signed-off-by: Ceyhun Onur --- README.md | 3 ++- compatibility.json | 1 + plugin/evm/version.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 568bfbf7dc..624c12837d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.6.9] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.10] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.11] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) -[v0.6.12] AvalancheGo@v1.11.13/v1.12.0-fuji (Protocol Version: 38) +[v0.6.12] AvalancheGo@v1.11.13/v1.12.0 (Protocol Version: 38) +[v0.7.0] AvalancheGo@v1.12.0 (Protocol Version: 38) ``` ## API diff --git a/compatibility.json b/compatibility.json index f735a579fa..ec56a217d2 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.7.0": 38, "v0.6.12": 38, "v0.6.11": 37, "v0.6.10": 37, diff --git a/plugin/evm/version.go b/plugin/evm/version.go index 619db5485a..c3387afeb5 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.6.12" + Version string = "v0.7.0" ) func init() { From ea8a88149362e3b82e2f83a0a7bbb593e728da17 Mon Sep 17 00:00:00 2001 From: guqicun Date: Thu, 12 Dec 2024 16:13:09 +0800 Subject: [PATCH 19/57] chore: fix some function names in comment (#1405) Signed-off-by: guqicun Co-authored-by: Ceyhun Onur --- eth/tracers/js/tracer_test.go | 2 +- eth/tracers/logger/access_list_tracer.go | 2 +- internal/ethapi/transaction_args.go | 2 +- rpc/handler.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index d2fb8a4cf3..26a619fd2a 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -206,7 +206,7 @@ func TestHaltBetweenSteps(t *testing.T) { } } -// testNoStepExec tests a regular value transfer (no exec), and accessing the statedb +// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb // in 'result' func TestNoStepExec(t *testing.T) { execTracer := func(code string) []byte { diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index cccd497d86..ddd20034eb 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -87,7 +87,7 @@ func (al accessList) equal(other accessList) bool { return true } -// accesslist converts the accesslist to a types.AccessList. +// accessList converts the accesslist to a types.AccessList. func (al accessList) accessList() types.AccessList { acl := make(types.AccessList, 0, len(al)) for addr, slots := range al { diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 3be1289947..ae9c59b60a 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -277,7 +277,7 @@ func (args *TransactionArgs) setCancunFeeDefaults(ctx context.Context, head *typ return nil } -// setSubnetEVMFeeDefaults fills in reasonable default fee values for unspecified fields. +// setSubnetEVMFeeDefault fills in reasonable default fee values for unspecified fields. func (args *TransactionArgs) setSubnetEVMFeeDefault(ctx context.Context, head *types.Header, b feeBackend) error { // Set maxPriorityFeePerGas if it is missing. if args.MaxPriorityFeePerGas == nil { diff --git a/rpc/handler.go b/rpc/handler.go index b4f54e1a91..e498c590a4 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -479,7 +479,7 @@ func (h *handler) startCallProc(fn func(*callProc)) { } } -// handleResponse processes method call responses. +// handleResponses processes method call responses. func (h *handler) handleResponses(batch []*jsonrpcMessage, handleCall func(*jsonrpcMessage)) { var resolvedops []*requestOp handleResp := func(msg *jsonrpcMessage) { From 72cacc3217572fa5fa93a1b7ce395499a801246f Mon Sep 17 00:00:00 2001 From: Dmytrol <46675332+Dimitrolito@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:13:24 +0200 Subject: [PATCH 20/57] Fix Typos in Documentation (#1404) * typos README.md Signed-off-by: Dmytrol <46675332+Dimitrolito@users.noreply.github.com> * typos README.md Signed-off-by: Dmytrol <46675332+Dimitrolito@users.noreply.github.com> --------- Signed-off-by: Dmytrol <46675332+Dimitrolito@users.noreply.github.com> Co-authored-by: Ceyhun Onur --- consensus/dummy/README.md | 2 +- precompile/contracts/warp/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/dummy/README.md b/consensus/dummy/README.md index 4375dff5ab..f2269e3019 100644 --- a/consensus/dummy/README.md +++ b/consensus/dummy/README.md @@ -18,7 +18,7 @@ The dynamic fee algorithm aims to adjust the base fee to handle network congesti - EIP-1559 is intended for Ethereum where a block is produced roughly every 10s - The dynamic fee algorithm needs to handle the case that the network quiesces and there are no blocks for a long period of time -- Since Subnet-EVM produces blocks at a different cadence, it adapts EIP-1559 to sum the amount of gas consumed within a 10 second interval instead of using only the amount of gas consumed in the parent block +- Since Subnet-EVM produces blocks at a different cadence, it adapts EIP-1559 to sum the amount of gas consumed within a 10-second interval instead of using only the amount of gas consumed in the parent block ## Consensus Engine Callbacks diff --git a/precompile/contracts/warp/README.md b/precompile/contracts/warp/README.md index 10e1daaa38..73ca165224 100644 --- a/precompile/contracts/warp/README.md +++ b/precompile/contracts/warp/README.md @@ -57,7 +57,7 @@ To use this function, the transaction must include the signed Avalanche Warp Mes This leads to the following advantages: 1. The EVM execution does not need to verify the Warp Message at runtime (no signature verification or external calls to the P-Chain) -2. The EVM can deterministically re-execute and re-verify blocks assuming the predicate was verified by the network (eg., in bootstrapping) +2. The EVM can deterministically re-execute and re-verify blocks assuming the predicate was verified by the network (e.g., in bootstrapping) This pre-verification is performed using the ProposerVM Block header during [block verification](../../../plugin/evm/block.go#L220) and [block building](../../../miner/worker.go#L200). From 665486f8cb89043ee237b583d3edc5dcf1014566 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 06:22:48 -0800 Subject: [PATCH 21/57] mark flaky: TestTimedUnlock (#1411) --- scripts/known_flakes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/known_flakes.txt b/scripts/known_flakes.txt index 6f39599d6c..063a16def2 100644 --- a/scripts/known_flakes.txt +++ b/scripts/known_flakes.txt @@ -6,6 +6,7 @@ TestGolangBindings TestMempoolEthTxsAppGossipHandling TestResumeSyncAccountsTrieInterrupted TestResyncNewRootAfterDeletes +TestTimedUnlock TestTransactionSkipIndexing TestVMShutdownWhileSyncing TestWaitDeployedCornerCases From b465ad54e937df7e6a7cada2bc7b552237f1cf53 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 06:40:44 -0800 Subject: [PATCH 22/57] bump avalanchego to master (updates x/crypto) (#1410) * bump avalanchego to master (updates x/crypto) * fix * Encapsulate Signer (#693) * Remove the bls dependency --------- Co-authored-by: Darioush Jalali * bump to v1.12.1 --------- Co-authored-by: Richard Pringle --- go.mod | 14 ++++---- go.sum | 28 ++++++++-------- plugin/evm/vm_warp_test.go | 20 ++++++------ precompile/contracts/warp/predicate_test.go | 10 +++--- .../warp/signature_verification_test.go | 32 +++++++++---------- scripts/versions.sh | 2 +- utils/snow.go | 4 +-- warp/aggregator/aggregator_test.go | 16 +++++----- warp/backend_test.go | 10 +++--- warp/handlers/signature_request_test.go | 4 +-- warp/verifier_backend_test.go | 6 ++-- 11 files changed, 73 insertions(+), 73 deletions(-) diff --git a/go.mod b/go.mod index 1c7a2a6a7e..bc313fa633 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync + github.com/ava-labs/avalanchego v1.12.1 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -42,11 +42,11 @@ require ( go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e - golang.org/x/sync v0.8.0 - golang.org/x/sys v0.24.0 - golang.org/x/text v0.17.0 + golang.org/x/sync v0.10.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.3.0 google.golang.org/protobuf v1.34.2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 @@ -55,7 +55,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.13.9-rc.1 // indirect + github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -156,7 +156,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect diff --git a/go.sum b/go.sum index e1c2cc5199..75eb7e874e 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync h1:HvtadR1VLxsOe7Y2PSnNmLUXPvp1RnTgeWtk5cOuGYA= -github.com/ava-labs/avalanchego v1.12.0-config-pebble-sync/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= -github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= -github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= +github.com/ava-labs/avalanchego v1.12.1 h1:NL04K5+gciC2XqGZbDcIu0nuVApEddzc6YyujRBv+u8= +github.com/ava-labs/avalanchego v1.12.1/go.mod h1:xnVvN86jhxndxfS8e0U7v/0woyfx9BhX/feld7XDjDE= +github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer h1:mRB03tLPUvgNko4nP4VwWQdiHeHaLHtdwsnqwxrsGec= +github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer/go.mod h1:tqRAe+7bGLo2Rq/Ph4iYMSch72ag/Jn0DiDMDz1Xa9E= 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= @@ -679,8 +679,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -788,8 +788,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= @@ -857,12 +857,12 @@ 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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -873,8 +873,8 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 2131b79d94..5a448c8ce4 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -280,16 +280,16 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned defer logsSub.Unsubscribe() nodeID1 := ids.GenerateTestNodeID() - blsSecretKey1, err := bls.NewSecretKey() + blsSecretKey1, err := bls.NewSigner() require.NoError(err) - blsPublicKey1 := bls.PublicFromSecretKey(blsSecretKey1) - blsSignature1 := bls.Sign(blsSecretKey1, unsignedMessage.Bytes()) + blsPublicKey1 := blsSecretKey1.PublicKey() + blsSignature1 := blsSecretKey1.Sign(unsignedMessage.Bytes()) nodeID2 := ids.GenerateTestNodeID() - blsSecretKey2, err := bls.NewSecretKey() + blsSecretKey2, err := bls.NewSigner() require.NoError(err) - blsPublicKey2 := bls.PublicFromSecretKey(blsSecretKey2) - blsSignature2 := bls.Sign(blsSecretKey2, unsignedMessage.Bytes()) + blsPublicKey2 := blsSecretKey2.PublicKey() + blsSignature2 := blsSecretKey2.Sign(unsignedMessage.Bytes()) blsAggregatedSignature, err := bls.AggregateSignatures([]*bls.Signature{blsSignature1, blsSignature2}) require.NoError(err) @@ -545,18 +545,18 @@ func testReceiveWarpMessage( type signer struct { networkID ids.ID nodeID ids.NodeID - secret *bls.SecretKey + secret bls.Signer signature *bls.Signature weight uint64 } newSigner := func(networkID ids.ID, weight uint64) signer { - secret, err := bls.NewSecretKey() + secret, err := bls.NewSigner() require.NoError(err) return signer{ networkID: networkID, nodeID: ids.GenerateTestNodeID(), secret: secret, - signature: bls.Sign(secret, unsignedMessage.Bytes()), + signature: secret.Sign(unsignedMessage.Bytes()), weight: weight, } } @@ -604,7 +604,7 @@ func testReceiveWarpMessage( for _, s := range signers { vdrOutput[s.nodeID] = &validators.GetValidatorOutput{ NodeID: s.nodeID, - PublicKey: bls.PublicFromSecretKey(s.secret), + PublicKey: s.secret.PublicKey(), Weight: s.weight, } } diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 1c7b08a7c0..65d202de09 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -93,7 +93,7 @@ func init() { } for _, testVdr := range testVdrs { - blsSignature := bls.Sign(testVdr.sk, unsignedMsg.Bytes()) + blsSignature := testVdr.sk.Sign(unsignedMsg.Bytes()) blsSignatures = append(blsSignatures, blsSignature) } @@ -102,7 +102,7 @@ func init() { type testValidator struct { nodeID ids.NodeID - sk *bls.SecretKey + sk bls.Signer vdr *avalancheWarp.Validator } @@ -111,13 +111,13 @@ func (v *testValidator) Compare(o *testValidator) int { } func newTestValidator() *testValidator { - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() if err != nil { panic(err) } nodeID := ids.GenerateTestNodeID() - pk := bls.PublicFromSecretKey(sk) + pk := sk.PublicKey() return &testValidator{ nodeID: nodeID, sk: sk, @@ -240,7 +240,7 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner PublicKey: testVdrs[i].vdr.PublicKey, } getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput - blsSignatures = append(blsSignatures, bls.Sign(testVdrs[i].sk, unsignedMsg.Bytes())) + blsSignatures = append(blsSignatures, testVdrs[i].sk.Sign(unsignedMsg.Bytes())) } aggregateSignature, err := bls.AggregateSignatures(blsSignatures) require.NoError(err) diff --git a/precompile/contracts/warp/signature_verification_test.go b/precompile/contracts/warp/signature_verification_test.go index 5b54cd29b8..d52f0a0f89 100644 --- a/precompile/contracts/warp/signature_verification_test.go +++ b/precompile/contracts/warp/signature_verification_test.go @@ -212,8 +212,8 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes) - vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes) + vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) + vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -284,7 +284,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes) + vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig)) @@ -323,10 +323,10 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes) + vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) // Give sig from vdr[2] even though the bit vector says it // should be from vdr[1] - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -367,7 +367,7 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes) + vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) // Don't give the sig from vdr[1] aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig)) @@ -407,11 +407,11 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := bls.Sign(testVdrs[0].sk, unsignedBytes) - vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes) + vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) + vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) // Give sig from vdr[2] even though the bit vector doesn't have // it - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -454,8 +454,8 @@ func TestSignatureVerification(t *testing.T) { signers.Add(2) unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes) - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -498,8 +498,8 @@ func TestSignatureVerification(t *testing.T) { signers.Add(2) unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes) - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -559,8 +559,8 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) // vdr[2] unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := bls.Sign(testVdrs[1].sk, unsignedBytes) - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -621,7 +621,7 @@ func TestSignatureVerification(t *testing.T) { unsignedBytes := unsignedMsg.Bytes() // Because vdr[1] and vdr[2] share a key, only one of them sign. - vdr2Sig := bls.Sign(testVdrs[2].sk, unsignedBytes) + vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr2Sig)) diff --git a/scripts/versions.sh b/scripts/versions.sh index 14b407c24f..559289b412 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.0-config-pebble-sync'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.1'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} diff --git a/utils/snow.go b/utils/snow.go index 00901fbad5..fe33a56512 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -26,11 +26,11 @@ var ( ) func TestSnowContext() *snow.Context { - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() if err != nil { panic(err) } - pk := bls.PublicFromSecretKey(sk) + pk := sk.PublicKey() networkID := constants.UnitTestID chainID := testChainID diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 1fedec6d36..055d3edfa8 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -17,10 +17,10 @@ import ( avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) -func newValidator(t testing.TB, weight uint64) (*bls.SecretKey, *avalancheWarp.Validator) { - sk, err := bls.NewSecretKey() +func newValidator(t testing.TB, weight uint64) (bls.Signer, *avalancheWarp.Validator) { + sk, err := bls.NewSigner() require.NoError(t, err) - pk := bls.PublicFromSecretKey(sk) + pk := sk.PublicKey() return sk, &avalancheWarp.Validator{ PublicKey: pk, PublicKeyBytes: bls.PublicKeyToCompressedBytes(pk), @@ -43,17 +43,17 @@ func TestAggregateSignatures(t *testing.T) { vdr1sk, vdr1 := newValidator(t, vdrWeight) vdr2sk, vdr2 := newValidator(t, vdrWeight+1) vdr3sk, vdr3 := newValidator(t, vdrWeight-1) - sig1 := bls.Sign(vdr1sk, unsignedMsg.Bytes()) - sig2 := bls.Sign(vdr2sk, unsignedMsg.Bytes()) - sig3 := bls.Sign(vdr3sk, unsignedMsg.Bytes()) + sig1 := vdr1sk.Sign(unsignedMsg.Bytes()) + sig2 := vdr2sk.Sign(unsignedMsg.Bytes()) + sig3 := vdr3sk.Sign(unsignedMsg.Bytes()) vdrToSig := map[*avalancheWarp.Validator]*bls.Signature{ vdr1: sig1, vdr2: sig2, vdr3: sig3, } - nonVdrSk, err := bls.NewSecretKey() + nonVdrSk, err := bls.NewSigner() require.NoError(t, err) - nonVdrSig := bls.Sign(nonVdrSk, unsignedMsg.Bytes()) + nonVdrSig := nonVdrSk.Sign(unsignedMsg.Bytes()) vdrs := []*avalancheWarp.Validator{ { PublicKey: vdr1.PublicKey, diff --git a/warp/backend_test.go b/warp/backend_test.go index cb98a2351c..764957882c 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -41,7 +41,7 @@ func init() { func TestAddAndGetValidMessage(t *testing.T) { db := memdb.New() - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -64,7 +64,7 @@ func TestAddAndGetValidMessage(t *testing.T) { func TestAddAndGetUnknownMessage(t *testing.T) { db := memdb.New() - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -83,7 +83,7 @@ func TestGetBlockSignature(t *testing.T) { blockClient := warptest.MakeBlockClient(blkID) db := memdb.New() - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() require.NoError(err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -108,7 +108,7 @@ func TestGetBlockSignature(t *testing.T) { func TestZeroSizedCache(t *testing.T) { db := memdb.New() - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) @@ -136,7 +136,7 @@ func TestOffChainMessages(t *testing.T) { check func(require *require.Assertions, b Backend) err error } - sk, err := bls.NewSecretKey() + sk, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index aa265d9b33..ac2a364a78 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -23,7 +23,7 @@ import ( func TestMessageSignatureHandler(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSecretKey() + blsSecretKey, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -127,7 +127,7 @@ func TestMessageSignatureHandler(t *testing.T) { func TestBlockSignatureHandler(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSecretKey() + blsSecretKey, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index 747d757d5e..d58e9e6c90 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -30,7 +30,7 @@ import ( func TestAddressedCallSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSecretKey() + blsSecretKey, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -144,7 +144,7 @@ func TestAddressedCallSignatures(t *testing.T) { func TestBlockSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSecretKey() + blsSecretKey, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -262,7 +262,7 @@ func TestBlockSignatures(t *testing.T) { func TestUptimeSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSecretKey() + blsSecretKey, err := bls.NewSigner() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) From a99bc8068f6a1d94baff69ab8f729ac910c74444 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Fri, 27 Dec 2024 14:02:45 +0100 Subject: [PATCH 23/57] chore(ci): remove `check-latest: true` to ensure the specified Go version is used (#1414) --- .github/workflows/bench.yml | 1 - .github/workflows/release.yml | 1 - .github/workflows/tests.yml | 7 ------- 3 files changed, 9 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 7c7c833eb6..71f2fc2135 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -12,7 +12,6 @@ jobs: - uses: actions/setup-go@v4 with: go-version: "~1.22.8" - check-latest: true - run: go mod download shell: bash - run: ./scripts/build_bench_precompiles.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d1c4f57cb..5af8b527c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,6 @@ jobs: uses: actions/setup-go@v5 with: go-version: "~1.22.8" - check-latest: true - name: Set up arm64 cross compiler run: | sudo apt-get -y update diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 784c5b4a78..896b97c969 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,6 @@ jobs: - uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: @@ -49,7 +48,6 @@ jobs: - uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: Set timeout on Windows # Windows UT run slower and need a longer timeout shell: bash if: matrix.os == 'windows-latest' @@ -77,7 +75,6 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: Use Node.js uses: actions/setup-node@v4 with: @@ -119,7 +116,6 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: Use Node.js uses: actions/setup-node@v4 with: @@ -164,7 +160,6 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: Install AvalancheGo Release shell: bash run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh @@ -192,7 +187,6 @@ jobs: - uses: actions/setup-go@v4 with: go-version: ${{ env.min_go_version }} - check-latest: true - shell: bash run: scripts/mock.gen.sh - shell: bash @@ -213,7 +207,6 @@ jobs: - uses: actions/setup-go@v5 with: go-version: ${{ env.min_go_version }} - check-latest: true - name: Install AvalancheGo Release shell: bash run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh From e0381a9cde145a5bb656663481af098f9467f9b9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 10:03:14 -0800 Subject: [PATCH 24/57] refactor trie_prefetcher to be similar to upstream structurally (#1395) Co-authored-by: Quentin McGaw Co-authored-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- RELEASES.md | 8 +- core/blockchain.go | 20 +- core/state/statedb.go | 22 +- core/state/trie_prefetcher.go | 538 ++++++----------------- core/state/trie_prefetcher.libevm.go | 126 ++++++ core/state/trie_prefetcher_extra_test.go | 189 ++++++++ core/state/trie_prefetcher_test.go | 6 +- libevm/options/options.go | 42 ++ libevm/sync/sync.go | 52 +++ miner/worker.go | 7 +- 10 files changed, 590 insertions(+), 420 deletions(-) create mode 100644 core/state/trie_prefetcher.libevm.go create mode 100644 core/state/trie_prefetcher_extra_test.go create mode 100644 libevm/options/options.go create mode 100644 libevm/sync/sync.go diff --git a/RELEASES.md b/RELEASES.md index c4beb051e1..29febf1347 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,11 +2,15 @@ ## Pending Release -## Updates +* Refactored trie_prefetcher.go to be structurally similar to upstream. + +## [v0.7.0](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.0) + +### Updates - Changed default write option from `Sync` to `NoSync` in PebbleDB -## Fixes +### Fixes - Fixed database close on shutdown diff --git a/core/blockchain.go b/core/blockchain.go index 7fc6c62688..19615c80ba 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1349,16 +1349,6 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { blockContentValidationTimer.Inc(time.Since(substart).Milliseconds()) // No validation errors for the block - var activeState *state.StateDB - defer func() { - // The chain importer is starting and stopping trie prefetchers. If a bad - // block or other error is hit however, an early return may not properly - // terminate the background threads. This defer ensures that we clean up - // and dangling prefetcher, without deferring each and holding on live refs. - if activeState != nil { - activeState.StopPrefetcher() - } - }() // Retrieve the parent block to determine which root to build state on substart = time.Now() @@ -1377,8 +1367,8 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { blockStateInitTimer.Inc(time.Since(substart).Milliseconds()) // Enable prefetching to pull in trie node paths while processing transactions - statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) - activeState = statedb + statedb.StartPrefetcher("chain", state.WithConcurrentWorkers(bc.cacheConfig.TriePrefetcherParallelism)) + defer statedb.StopPrefetcher() // Process block using the parent state as reference point pstart := time.Now() @@ -1736,10 +1726,8 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) } // Enable prefetching to pull in trie node paths while processing transactions - statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) - defer func() { - statedb.StopPrefetcher() - }() + statedb.StartPrefetcher("chain", state.WithConcurrentWorkers(bc.cacheConfig.TriePrefetcherParallelism)) + defer statedb.StopPrefetcher() // Process previously stored block receipts, _, usedGas, err := bc.processor.Process(current, parent.Header(), statedb, vm.Config{}) diff --git a/core/state/statedb.go b/core/state/statedb.go index e73cf9accb..b4c2da9566 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/trie/trienode" "github.com/ava-labs/subnet-evm/trie/triestate" + "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -200,16 +201,33 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St return sdb, nil } +type workerPool struct { + *utils.BoundedWorkers +} + +func (wp *workerPool) Done() { + // Done is guaranteed to only be called after all work is already complete, + // so Wait()ing is redundant, but it also releases resources. + wp.BoundedWorkers.Wait() +} + +func WithConcurrentWorkers(prefetchers int) PrefetcherOption { + pool := &workerPool{ + BoundedWorkers: utils.NewBoundedWorkers(prefetchers), + } + return WithWorkerPools(func() WorkerPool { return pool }) +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. -func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { +func (s *StateDB) StartPrefetcher(namespace string, opts ...PrefetcherOption) { if s.prefetcher != nil { s.prefetcher.close() s.prefetcher = nil } if s.snap != nil { - s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, opts...) } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 6c6ddeab07..8465deae2a 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -1,13 +1,3 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** // Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // @@ -28,16 +18,17 @@ package state import ( "sync" - "time" + "github.com/ava-labs/subnet-evm/libevm/options" "github.com/ava-labs/subnet-evm/metrics" - "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) -// triePrefetchMetricsPrefix is the prefix under which to publish the metrics. -const triePrefetchMetricsPrefix = "trie/prefetch/" +var ( + // triePrefetchMetricsPrefix is the prefix under which to publish the metrics. + triePrefetchMetricsPrefix = "trie/prefetch/" +) // triePrefetcher is an active prefetcher, which receives accounts or storage // items and does trie-loading of them. The goal is to get as much useful content @@ -50,91 +41,61 @@ type triePrefetcher struct { fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies. fetchers map[string]*subfetcher // Subfetchers for each trie - maxConcurrency int - workers *utils.BoundedWorkers - - subfetcherWorkersMeter metrics.Meter - subfetcherWaitTimer metrics.Counter - subfetcherCopiesMeter metrics.Meter - + deliveryMissMeter metrics.Meter accountLoadMeter metrics.Meter accountDupMeter metrics.Meter accountSkipMeter metrics.Meter accountWasteMeter metrics.Meter + storageLoadMeter metrics.Meter + storageDupMeter metrics.Meter + storageSkipMeter metrics.Meter + storageWasteMeter metrics.Meter - storageFetchersMeter metrics.Meter - storageLoadMeter metrics.Meter - storageLargestLoadMeter metrics.Meter - storageDupMeter metrics.Meter - storageSkipMeter metrics.Meter - storageWasteMeter metrics.Meter + options []PrefetcherOption } -func newTriePrefetcher(db Database, root common.Hash, namespace string, maxConcurrency int) *triePrefetcher { +func newTriePrefetcher(db Database, root common.Hash, namespace string, opts ...PrefetcherOption) *triePrefetcher { prefix := triePrefetchMetricsPrefix + namespace - return &triePrefetcher{ + p := &triePrefetcher{ db: db, root: root, fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map - maxConcurrency: maxConcurrency, - workers: utils.NewBoundedWorkers(maxConcurrency), // Scale up as needed to [maxConcurrency] - - subfetcherWorkersMeter: metrics.GetOrRegisterMeter(prefix+"/subfetcher/workers", nil), - subfetcherWaitTimer: metrics.GetOrRegisterCounter(prefix+"/subfetcher/wait", nil), - subfetcherCopiesMeter: metrics.GetOrRegisterMeter(prefix+"/subfetcher/copies", nil), - + deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil), accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), + storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), + storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), + storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), + storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), - storageFetchersMeter: metrics.GetOrRegisterMeter(prefix+"/storage/fetchers", nil), - storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), - storageLargestLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/lload", nil), - storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), - storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), - storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), + options: opts, } + return p } // close iterates over all the subfetchers, aborts any that were left spinning // and reports the stats to the metrics subsystem. func (p *triePrefetcher) close() { - // If the prefetcher is an inactive one, bail out - if p.fetches != nil { - return - } - - // Collect stats from all fetchers - var ( - storageFetchers int64 - largestLoad int64 - ) for _, fetcher := range p.fetchers { - fetcher.abort() // safe to call multiple times (should be a no-op on happy path) + fetcher.abort() // safe to do multiple times if metrics.Enabled { - p.subfetcherCopiesMeter.Mark(int64(fetcher.copies())) - if fetcher.root == p.root { p.accountLoadMeter.Mark(int64(len(fetcher.seen))) p.accountDupMeter.Mark(int64(fetcher.dups)) - p.accountSkipMeter.Mark(int64(fetcher.skips())) + p.accountSkipMeter.Mark(int64(len(fetcher.tasks))) for _, key := range fetcher.used { delete(fetcher.seen, string(key)) } p.accountWasteMeter.Mark(int64(len(fetcher.seen))) } else { - storageFetchers++ - oseen := int64(len(fetcher.seen)) - if oseen > largestLoad { - largestLoad = oseen - } - p.storageLoadMeter.Mark(oseen) + p.storageLoadMeter.Mark(int64(len(fetcher.seen))) p.storageDupMeter.Mark(int64(fetcher.dups)) - p.storageSkipMeter.Mark(int64(fetcher.skips())) + p.storageSkipMeter.Mark(int64(len(fetcher.tasks))) for _, key := range fetcher.used { delete(fetcher.seen, string(key)) @@ -143,20 +104,7 @@ func (p *triePrefetcher) close() { } } } - if metrics.Enabled { - p.storageFetchersMeter.Mark(storageFetchers) - p.storageLargestLoadMeter.Mark(largestLoad) - } - - // Stop all workers once fetchers are aborted (otherwise - // could stop while waiting) - // - // Record number of workers that were spawned during this run - workersUsed := int64(p.workers.Wait()) - if metrics.Enabled { - p.subfetcherWorkersMeter.Mark(workersUsed) - } - + p.releaseWorkerPools() // Clear out all fetchers (will crash on a second call, deliberate) p.fetchers = nil } @@ -169,23 +117,19 @@ func (p *triePrefetcher) copy() *triePrefetcher { copy := &triePrefetcher{ db: p.db, root: p.root, - fetches: make(map[string]Trie), // Active prefetchers use the fetchers map - - subfetcherWorkersMeter: p.subfetcherWorkersMeter, - subfetcherWaitTimer: p.subfetcherWaitTimer, - subfetcherCopiesMeter: p.subfetcherCopiesMeter, + fetches: make(map[string]Trie), // Active prefetchers use the fetches map + deliveryMissMeter: p.deliveryMissMeter, accountLoadMeter: p.accountLoadMeter, accountDupMeter: p.accountDupMeter, accountSkipMeter: p.accountSkipMeter, accountWasteMeter: p.accountWasteMeter, + storageLoadMeter: p.storageLoadMeter, + storageDupMeter: p.storageDupMeter, + storageSkipMeter: p.storageSkipMeter, + storageWasteMeter: p.storageWasteMeter, - storageFetchersMeter: p.storageFetchersMeter, - storageLoadMeter: p.storageLoadMeter, - storageLargestLoadMeter: p.storageLargestLoadMeter, - storageDupMeter: p.storageDupMeter, - storageSkipMeter: p.storageSkipMeter, - storageWasteMeter: p.storageWasteMeter, + options: p.options, } // If the prefetcher is already a copy, duplicate the data if p.fetches != nil { @@ -210,12 +154,11 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm if p.fetches != nil { return } - // Active fetcher, schedule the retrievals id := p.trieID(owner, root) fetcher := p.fetchers[id] if fetcher == nil { - fetcher = newSubfetcher(p, owner, root, addr) + fetcher = newSubfetcher(p.db, p.root, owner, root, addr, p.options...) p.fetchers[id] = fetcher } fetcher.schedule(keys) @@ -229,27 +172,24 @@ func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { if p.fetches != nil { trie := p.fetches[id] if trie == nil { + p.deliveryMissMeter.Mark(1) return nil } return p.db.CopyTrie(trie) } - // Otherwise the prefetcher is active, bail if no trie was prefetched for this root fetcher := p.fetchers[id] if fetcher == nil { + p.deliveryMissMeter.Mark(1) return nil } + // Interrupt the prefetcher if it's by any chance still running and return + // a copy of any pre-loaded trie. + fetcher.abort() // safe to do multiple times - // Wait for the fetcher to finish and shutdown orchestrator, if it exists - start := time.Now() - fetcher.wait() - if metrics.Enabled { - p.subfetcherWaitTimer.Inc(time.Since(start).Milliseconds()) - } - - // Return a copy of one of the prefetched tries trie := fetcher.peek() if trie == nil { + p.deliveryMissMeter.Mark(1) return nil } return trie @@ -276,365 +216,175 @@ func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { // main prefetcher is paused and either all requested items are processed or if // the trie being worked on is retrieved from the prefetcher. type subfetcher struct { - p *triePrefetcher - db Database // Database to load trie nodes through state common.Hash // Root hash of the state to prefetch owner common.Hash // Owner of the trie, usually account hash root common.Hash // Root hash of the trie to prefetch addr common.Address // Address of the account that the trie belongs to + trie Trie // Trie being populated with nodes + + tasks [][]byte // Items queued up for retrieval + lock sync.Mutex // Lock protecting the task queue - to *trieOrchestrator // Orchestrate concurrent fetching of a single trie + wake chan struct{} // Wake channel if a new task is scheduled + stop chan struct{} // Channel to interrupt processing + term chan struct{} // Channel to signal interruption + copy chan chan Trie // Channel to request a copy of the current trie seen map[string]struct{} // Tracks the entries already loaded dups int // Number of duplicate preload tasks used [][]byte // Tracks the entries used in the end + + pool *subfetcherPool } // newSubfetcher creates a goroutine to prefetch state items belonging to a // particular root hash. -func newSubfetcher(p *triePrefetcher, owner common.Hash, root common.Hash, addr common.Address) *subfetcher { +func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address, opts ...PrefetcherOption) *subfetcher { sf := &subfetcher{ - p: p, - db: p.db, - state: p.root, + db: db, + state: state, owner: owner, root: root, addr: addr, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + term: make(chan struct{}), + copy: make(chan chan Trie), seen: make(map[string]struct{}), } - sf.to = newTrieOrchestrator(sf) - if sf.to != nil { - go sf.to.processTasks() - } - // We return [sf] here to ensure we don't try to re-create if - // we aren't able to setup a [newTrieOrchestrator] the first time. + options.As[prefetcherConfig](opts...).applyTo(sf) + go sf.loop() return sf } // schedule adds a batch of trie keys to the queue to prefetch. -// This should never block, so an array is used instead of a channel. -// -// This is not thread-safe. func (sf *subfetcher) schedule(keys [][]byte) { // Append the tasks to the current queue - tasks := make([][]byte, 0, len(keys)) - for _, key := range keys { - // Check if keys already seen - sk := string(key) - if _, ok := sf.seen[sk]; ok { - sf.dups++ - continue - } - sf.seen[sk] = struct{}{} - tasks = append(tasks, key) - } + sf.lock.Lock() + sf.tasks = append(sf.tasks, keys...) + sf.lock.Unlock() - // After counting keys, exit if they can't be prefetched - if sf.to == nil { - return + // Notify the prefetcher, it's fine if it's already terminated + select { + case sf.wake <- struct{}{}: + default: } - - // Add tasks to queue for prefetching - sf.to.enqueueTasks(tasks) } // peek tries to retrieve a deep copy of the fetcher's trie in whatever form it // is currently. func (sf *subfetcher) peek() Trie { - if sf.to == nil { - return nil - } - return sf.to.copyBase() -} + ch := make(chan Trie) + select { + case sf.copy <- ch: + // Subfetcher still alive, return copy from it + return <-ch -// wait must only be called if [triePrefetcher] has not been closed. If this happens, -// workers will not finish. -func (sf *subfetcher) wait() { - if sf.to == nil { - // Unable to open trie - return + case <-sf.term: + // Subfetcher already terminated, return a copy directly + if sf.trie == nil { + return nil + } + return sf.db.CopyTrie(sf.trie) } - sf.to.wait() } +// abort interrupts the subfetcher immediately. It is safe to call abort multiple +// times but it is not thread safe. func (sf *subfetcher) abort() { - if sf.to == nil { - // Unable to open trie - return - } - sf.to.abort() -} - -func (sf *subfetcher) skips() int { - if sf.to == nil { - // Unable to open trie - return 0 - } - return sf.to.skipCount() -} - -func (sf *subfetcher) copies() int { - if sf.to == nil { - // Unable to open trie - return 0 + select { + case <-sf.stop: + default: + close(sf.stop) } - return sf.to.copies + <-sf.term } -// trieOrchestrator is not thread-safe. -type trieOrchestrator struct { - sf *subfetcher - - // base is an unmodified Trie we keep for - // creating copies for each worker goroutine. - // - // We care more about quick copies than good copies - // because most (if not all) of the nodes that will be populated - // in the copy will come from the underlying triedb cache. Ones - // that don't come from this cache probably had to be fetched - // from disk anyways. - base Trie - baseLock sync.Mutex - - tasksAllowed bool - skips int // number of tasks skipped - pendingTasks [][]byte - taskLock sync.Mutex - - processingTasks sync.WaitGroup - - wake chan struct{} - stop chan struct{} - stopOnce sync.Once - loopTerm chan struct{} - - copies int - copyChan chan Trie - copySpawner chan struct{} -} +// loop waits for new tasks to be scheduled and keeps loading them until it runs +// out of tasks or its underlying trie is retrieved for committing. +func (sf *subfetcher) loop() { + // No matter how the loop stops, signal anyone waiting that it's terminated + defer func() { + sf.pool.wait() + close(sf.term) + }() -func newTrieOrchestrator(sf *subfetcher) *trieOrchestrator { // Start by opening the trie and stop processing if it fails - var ( - base Trie - err error - ) if sf.owner == (common.Hash{}) { - base, err = sf.db.OpenTrie(sf.root) + trie, err := sf.db.OpenTrie(sf.root) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) - return nil + return } + sf.trie = trie } else { // The trie argument can be nil as verkle doesn't support prefetching // yet. TODO FIX IT(rjl493456442), otherwise code will panic here. - base, err = sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) + trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) - return nil + return } + sf.trie = trie } + // Trie opened successfully, keep prefetching items + for { + select { + case <-sf.wake: + // Subfetcher was woken up, retrieve any tasks to avoid spinning the lock + sf.lock.Lock() + tasks := sf.tasks + sf.tasks = nil + sf.lock.Unlock() + + // Prefetch any tasks until the loop is interrupted + for _, task := range tasks { + select { + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) + + default: + // No termination request yet, prefetch the next entry + if _, ok := sf.seen[string(task)]; ok { + sf.dups++ + } else { + if len(task) == common.AddressLength { + sf.pool.GetAccount(common.BytesToAddress(task)) + } else { + sf.pool.GetStorage(sf.addr, task) + } + sf.seen[string(task)] = struct{}{} + } + } + } - // Instantiate trieOrchestrator - to := &trieOrchestrator{ - sf: sf, - base: base, - - tasksAllowed: true, - wake: make(chan struct{}, 1), - stop: make(chan struct{}), - loopTerm: make(chan struct{}), - - copyChan: make(chan Trie, sf.p.maxConcurrency), - copySpawner: make(chan struct{}, sf.p.maxConcurrency), - } - - // Create initial trie copy - to.copies++ - to.copySpawner <- struct{}{} - to.copyChan <- to.copyBase() - return to -} - -func (to *trieOrchestrator) copyBase() Trie { - to.baseLock.Lock() - defer to.baseLock.Unlock() - - return to.sf.db.CopyTrie(to.base) -} - -func (to *trieOrchestrator) skipCount() int { - to.taskLock.Lock() - defer to.taskLock.Unlock() - - return to.skips -} - -func (to *trieOrchestrator) enqueueTasks(tasks [][]byte) { - to.taskLock.Lock() - defer to.taskLock.Unlock() - - if len(tasks) == 0 { - return - } - - // Add tasks to [pendingTasks] - if !to.tasksAllowed { - to.skips += len(tasks) - return - } - to.processingTasks.Add(len(tasks)) - to.pendingTasks = append(to.pendingTasks, tasks...) - - // Wake up processor - select { - case to.wake <- struct{}{}: - default: - } -} - -func (to *trieOrchestrator) handleStop(remaining int) { - to.taskLock.Lock() - to.skips += remaining - to.taskLock.Unlock() - to.processingTasks.Add(-remaining) -} + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) -func (to *trieOrchestrator) processTasks() { - defer close(to.loopTerm) + case <-sf.stop: + //libevm:start + // + // This is copied, with alteration, from ethereum/go-ethereum#29519 + // and can be deleted once we update to include that change. - for { - // Determine if we should process or exit - select { - case <-to.wake: - case <-to.stop: - return - } + // Termination is requested, abort if no more tasks are pending. If + // there are some, exhaust them first. + sf.lock.Lock() + done := len(sf.tasks) == 0 + sf.lock.Unlock() - // Get current tasks - to.taskLock.Lock() - tasks := to.pendingTasks - to.pendingTasks = nil - to.taskLock.Unlock() - - // Enqueue more work as soon as trie copies are available - lt := len(tasks) - for i := 0; i < lt; i++ { - // Try to stop as soon as possible, if channel is closed - remaining := lt - i - select { - case <-to.stop: - to.handleStop(remaining) + if done { return - default: } - // Try to create to get an active copy first (select is non-deterministic, - // so we may end up creating a new copy when we don't need to) - var t Trie select { - case t = <-to.copyChan: + case sf.wake <- struct{}{}: default: - // Wait for an available copy or create one, if we weren't - // able to get a previously created copy - select { - case <-to.stop: - to.handleStop(remaining) - return - case t = <-to.copyChan: - case to.copySpawner <- struct{}{}: - to.copies++ - t = to.copyBase() - } - } - - // Enqueue work, unless stopped. - fTask := tasks[i] - f := func() { - // Perform task - var err error - if len(fTask) == common.AddressLength { - _, err = t.GetAccount(common.BytesToAddress(fTask)) - } else { - _, err = t.GetStorage(to.sf.addr, fTask) - } - if err != nil { - log.Error("Trie prefetcher failed fetching", "root", to.sf.root, "err", err) - } - to.processingTasks.Done() - - // Return copy when we are done with it, so someone else can use it - // - // channel is buffered and will not block - to.copyChan <- t } - - // Enqueue task for processing (may spawn new goroutine - // if not at [maxConcurrency]) - // - // If workers are stopped before calling [Execute], this function may - // panic. - to.sf.p.workers.Execute(f) + //libevm:end } } } - -func (to *trieOrchestrator) stopAcceptingTasks() { - to.taskLock.Lock() - defer to.taskLock.Unlock() - - if !to.tasksAllowed { - return - } - to.tasksAllowed = false - - // We don't clear [to.pendingTasks] here because - // it will be faster to prefetch them even though we - // are still waiting. -} - -// wait stops accepting new tasks and waits for ongoing tasks to complete. If -// wait is called, it is not necessary to call [abort]. -// -// It is safe to call wait multiple times. -func (to *trieOrchestrator) wait() { - // Prevent more tasks from being enqueued - to.stopAcceptingTasks() - - // Wait for processing tasks to complete - to.processingTasks.Wait() - - // Stop orchestrator loop - to.stopOnce.Do(func() { - close(to.stop) - }) - <-to.loopTerm -} - -// abort stops any ongoing tasks and shuts down the orchestrator loop. If abort -// is called, it is not necessary to call [wait]. -// -// It is safe to call abort multiple times. -func (to *trieOrchestrator) abort() { - // Prevent more tasks from being enqueued - to.stopAcceptingTasks() - - // Stop orchestrator loop - to.stopOnce.Do(func() { - close(to.stop) - }) - <-to.loopTerm - - // Capture any dangling pending tasks (processTasks - // may exit before enqueing all pendingTasks) - to.taskLock.Lock() - pendingCount := len(to.pendingTasks) - to.skips += pendingCount - to.pendingTasks = nil - to.taskLock.Unlock() - to.processingTasks.Add(-pendingCount) - - // Wait for processing tasks to complete - to.processingTasks.Wait() -} diff --git a/core/state/trie_prefetcher.libevm.go b/core/state/trie_prefetcher.libevm.go new file mode 100644 index 0000000000..d59bd4eb54 --- /dev/null +++ b/core/state/trie_prefetcher.libevm.go @@ -0,0 +1,126 @@ +// Copyright 2024 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package state + +import ( + "github.com/ava-labs/subnet-evm/libevm/options" + "github.com/ava-labs/subnet-evm/libevm/sync" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// A PrefetcherOption configures behaviour of trie prefetching. +type PrefetcherOption = options.Option[prefetcherConfig] + +type prefetcherConfig struct { + newWorkers func() WorkerPool +} + +// A WorkerPool executes functions asynchronously. Done() is called to signal +// that the pool is no longer needed and that Execute() is guaranteed to not be +// called again. +type WorkerPool interface { + Execute(func()) + Done() +} + +// WithWorkerPools configures trie prefetching to execute asynchronously. The +// provided constructor is called once for each trie being fetched but it MAY +// return the same pool. +func WithWorkerPools(ctor func() WorkerPool) PrefetcherOption { + return options.Func[prefetcherConfig](func(c *prefetcherConfig) { + c.newWorkers = ctor + }) +} + +type subfetcherPool struct { + workers WorkerPool + tries sync.Pool[Trie] + wg sync.WaitGroup +} + +// applyTo configures the [subfetcher] to use a [WorkerPool] if one was provided +// with a [PrefetcherOption]. +func (c *prefetcherConfig) applyTo(sf *subfetcher) { + sf.pool = &subfetcherPool{ + tries: sync.Pool[Trie]{ + // Although the workers may be shared between all subfetchers, each + // MUST have its own Trie pool. + New: func() Trie { + return sf.db.CopyTrie(sf.trie) + }, + }, + } + if c.newWorkers != nil { + sf.pool.workers = c.newWorkers() + } +} + +// releaseWorkerPools calls Done() on all [WorkerPool]s. This MUST only be +// called after [subfetcher.abort] returns on ALL fetchers as a pool is allowed +// to be shared between them. This is because we guarantee in the public API +// that no further calls will be made to Execute() after a call to Done(). +func (p *triePrefetcher) releaseWorkerPools() { + for _, f := range p.fetchers { + if w := f.pool.workers; w != nil { + w.Done() + } + } +} + +func (p *subfetcherPool) wait() { + p.wg.Wait() +} + +// execute runs the provided function with a copy of the subfetcher's Trie. +// Copies are stored in a [sync.Pool] to reduce creation overhead. If p was +// configured with a [WorkerPool] then it is used for function execution, +// otherwise `fn` is just called directly. +func (p *subfetcherPool) execute(fn func(Trie)) { + p.wg.Add(1) + do := func() { + t := p.tries.Get() + fn(t) + p.tries.Put(t) + p.wg.Done() + } + + if w := p.workers; w != nil { + w.Execute(do) + } else { + do() + } +} + +// GetAccount optimistically pre-fetches an account, dropping the returned value +// and logging errors. See [subfetcherPool.execute] re worker pools. +func (p *subfetcherPool) GetAccount(addr common.Address) { + p.execute(func(t Trie) { + if _, err := t.GetAccount(addr); err != nil { + log.Error("account prefetching failed", "address", addr, "err", err) + } + }) +} + +// GetStorage is the storage equivalent of [subfetcherPool.GetAccount]. +func (p *subfetcherPool) GetStorage(addr common.Address, key []byte) { + p.execute(func(t Trie) { + if _, err := t.GetStorage(addr, key); err != nil { + log.Error("storage prefetching failed", "address", addr, "key", key, "err", err) + } + }) +} diff --git a/core/state/trie_prefetcher_extra_test.go b/core/state/trie_prefetcher_extra_test.go new file mode 100644 index 0000000000..5ac1d1c5c2 --- /dev/null +++ b/core/state/trie_prefetcher_extra_test.go @@ -0,0 +1,189 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "os" + "path" + "strconv" + "testing" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state/snapshot" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/metrics" + "github.com/ava-labs/subnet-evm/triedb" + "github.com/ava-labs/subnet-evm/triedb/hashdb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/stretchr/testify/require" +) + +const namespace = "chain" + +// BenchmarkPrefetcherDatabase benchmarks the performance of the trie +// prefetcher. By default, a state with 100k storage keys is created and stored +// in a temporary directory. Setting the TEST_DB_KVS and TEST_DB_DIR environment +// variables modifies the defaults. The benchmark measures the time to update +// the trie after 100, 200, and 500 storage slot updates per iteration, +// simulating a block with that number of storage slot updates. For performance +// reasons, when making changes involving the trie prefetcher, this benchmark +// should be run against a state including around 100m storage entries. +func BenchmarkPrefetcherDatabase(b *testing.B) { + require := require.New(b) + + dir := b.TempDir() + if env := os.Getenv("TEST_DB_DIR"); env != "" { + dir = env + } + wantKVs := 100_000 + if env := os.Getenv("TEST_DB_KVS"); env != "" { + var err error + wantKVs, err = strconv.Atoi(env) + require.NoError(err) + } + + levelDB, err := rawdb.NewLevelDBDatabase(path.Join(dir, "level.db"), 0, 0, "", false) + require.NoError(err) + + root := types.EmptyRootHash + count := uint64(0) + block := uint64(0) + + rootKey := []byte("root") + countKey := []byte("count") + blockKey := []byte("block") + got, err := levelDB.Get(rootKey) + if err == nil { // on success + root = common.BytesToHash(got) + } + got, err = levelDB.Get(countKey) + if err == nil { // on success + count = binary.BigEndian.Uint64(got) + } + got, err = levelDB.Get(blockKey) + if err == nil { // on success + block = binary.BigEndian.Uint64(got) + } + + // Make a trie on the levelDB + address1 := common.Address{42} + address2 := common.Address{43} + addBlock := func(db Database, snaps *snapshot.Tree, kvsPerBlock int, prefetchers int) { + _, root, err = addKVs(db, snaps, address1, address2, root, block, kvsPerBlock, prefetchers) + require.NoError(err) + count += uint64(kvsPerBlock) + block++ + } + + lastCommit := block + commit := func(levelDB ethdb.Database, snaps *snapshot.Tree, db Database) { + require.NoError(db.TrieDB().Commit(root, false)) + + for i := lastCommit + 1; i <= block; i++ { + require.NoError(snaps.Flatten(fakeHash(i))) + } + lastCommit = block + + // update the tracking keys + require.NoError(levelDB.Put(rootKey, root.Bytes())) + require.NoError(database.PutUInt64(levelDB, blockKey, block)) + require.NoError(database.PutUInt64(levelDB, countKey, count)) + } + + tdbConfig := &triedb.Config{ + HashDB: &hashdb.Config{ + CleanCacheSize: 3 * 1024 * 1024 * 1024, + }, + } + db := NewDatabaseWithConfig(levelDB, tdbConfig) + snaps := snapshot.NewTestTree(levelDB, fakeHash(block), root) + for count < uint64(wantKVs) { + previous := root + addBlock(db, snaps, 100_000, 0) // Note this updates root and count + b.Logf("Root: %v, kvs: %d, block: %d", root, count, block) + + // Commit every 10 blocks or on the last iteration + if block%10 == 0 || count >= uint64(wantKVs) { + commit(levelDB, snaps, db) + b.Logf("Root: %v, kvs: %d, block: %d (committed)", root, count, block) + } + if previous != root { + require.NoError(db.TrieDB().Dereference(previous)) + } else { + b.Fatal("root did not change") + } + } + require.NoError(levelDB.Close()) + b.Logf("Starting benchmarks") + b.Logf("Root: %v, kvs: %d, block: %d", root, count, block) + for _, updates := range []int{100, 200, 500} { + for _, prefetchers := range []int{0, 1, 4, 16} { + b.Run(fmt.Sprintf("updates_%d_prefetchers_%d", updates, prefetchers), func(b *testing.B) { + startRoot, startBlock, startCount := root, block, count + defer func() { root, block, count = startRoot, startBlock, startCount }() + + levelDB, err := rawdb.NewLevelDBDatabase(path.Join(dir, "level.db"), 0, 0, "", false) + require.NoError(err) + snaps := snapshot.NewTestTree(levelDB, fakeHash(block), root) + db := NewDatabaseWithConfig(levelDB, tdbConfig) + getMetric := func(metric string) int64 { + meter := metrics.GetOrRegisterMeter(triePrefetchMetricsPrefix+namespace+"/storage/"+metric, nil) + return meter.Snapshot().Count() + } + startLoads := getMetric("load") + for i := 0; i < b.N; i++ { + addBlock(db, snaps, updates, prefetchers) + } + require.NoError(levelDB.Close()) + b.ReportMetric(float64(getMetric("load")-startLoads)/float64(b.N), "loads") + }) + } + } +} + +func fakeHash(block uint64) common.Hash { + return common.BytesToHash(binary.BigEndian.AppendUint64(nil, block)) +} + +// addKVs adds count random key-value pairs to the state trie of address1 and +// address2 (each count/2) and returns the new state db and root. +func addKVs( + db Database, snaps *snapshot.Tree, + address1, address2 common.Address, root common.Hash, block uint64, + count int, prefetchers int, +) (*StateDB, common.Hash, error) { + snap := snaps.Snapshot(root) + if snap == nil { + return nil, common.Hash{}, fmt.Errorf("snapshot not found") + } + statedb, err := NewWithSnapshot(root, db, snap) + if err != nil { + return nil, common.Hash{}, fmt.Errorf("creating state with snapshot: %w", err) + } + if prefetchers > 0 { + statedb.StartPrefetcher(namespace, WithConcurrentWorkers(prefetchers)) + defer statedb.StopPrefetcher() + } + for _, address := range []common.Address{address1, address2} { + statedb.SetNonce(address, 1) + for i := 0; i < count/2; i++ { + key := make([]byte, 32) + value := make([]byte, 32) + rand.Read(key) + rand.Read(value) + + statedb.SetState(address, common.BytesToHash(key), common.BytesToHash(value)) + } + } + root, err = statedb.CommitWithSnap(block+1, true, snaps, fakeHash(block+1), fakeHash(block)) + if err != nil { + return nil, common.Hash{}, fmt.Errorf("committing with snap: %w", err) + } + return statedb, root, nil +} diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go index b8edcbb6a8..999fcfc789 100644 --- a/core/state/trie_prefetcher_test.go +++ b/core/state/trie_prefetcher_test.go @@ -59,7 +59,7 @@ func filledStateDB() *StateDB { func TestCopyAndClose(t *testing.T) { db := filledStateDB() - prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", maxConcurrency) + prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", WithConcurrentWorkers(maxConcurrency)) skey := common.HexToHash("aaa") prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) @@ -84,7 +84,7 @@ func TestCopyAndClose(t *testing.T) { func TestUseAfterClose(t *testing.T) { db := filledStateDB() - prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", maxConcurrency) + prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", WithConcurrentWorkers(maxConcurrency)) skey := common.HexToHash("aaa") prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) a := prefetcher.trie(common.Hash{}, db.originalRoot) @@ -100,7 +100,7 @@ func TestUseAfterClose(t *testing.T) { func TestCopyClose(t *testing.T) { db := filledStateDB() - prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", maxConcurrency) + prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", WithConcurrentWorkers(maxConcurrency)) skey := common.HexToHash("aaa") prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}) cpy := prefetcher.copy() diff --git a/libevm/options/options.go b/libevm/options/options.go new file mode 100644 index 0000000000..af7bc751a9 --- /dev/null +++ b/libevm/options/options.go @@ -0,0 +1,42 @@ +// Copyright 2024 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +// Package options provides a generic mechanism for defining configuration of +// arbitrary types. +package options + +// An Option configures values of arbitrary type. +type Option[T any] interface { + Configure(*T) +} + +// As applies Options to a zero-value T, which it then returns. +func As[T any](opts ...Option[T]) *T { + var t T + for _, o := range opts { + o.Configure(&t) + } + return &t +} + +// A Func converts a function into an [Option], using itself as the Configure +// method. +type Func[T any] func(*T) + +var _ Option[struct{}] = Func[struct{}](nil) + +// Configure implements the [Option] interface. +func (f Func[T]) Configure(t *T) { f(t) } diff --git a/libevm/sync/sync.go b/libevm/sync/sync.go new file mode 100644 index 0000000000..7621e20d79 --- /dev/null +++ b/libevm/sync/sync.go @@ -0,0 +1,52 @@ +// Copyright 2024 the subnet-evm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +// Package sync extends the standard library's sync package. +package sync + +import "sync" + +// Aliases of stdlib sync's types to avoid having to import it alongside this +// package. +type ( + Cond = sync.Cond + Locker = sync.Locker + Map = sync.Map + Mutex = sync.Mutex + Once = sync.Once + RWMutex = sync.RWMutex + WaitGroup = sync.WaitGroup +) + +// A Pool is a type-safe wrapper around [sync.Pool]. +type Pool[T any] struct { + New func() T + pool sync.Pool + once Once +} + +// Get is equivalent to [sync.Pool.Get]. +func (p *Pool[T]) Get() T { + p.once.Do(func() { // Do() guarantees at least once, not just only once + p.pool.New = func() any { return p.New() } + }) + return p.pool.Get().(T) //nolint:forcetypeassert +} + +// Put is equivalent to [sync.Pool.Put]. +func (p *Pool[T]) Put(t T) { + p.pool.Put(t) +} diff --git a/miner/worker.go b/miner/worker.go index a7c2a43665..b7b5e7b9b8 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -280,14 +280,15 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte } func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.PredicateContext, parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) { - state, err := w.chain.StateAt(parent.Root) + currentState, err := w.chain.StateAt(parent.Root) if err != nil { return nil, err } - state.StartPrefetcher("miner", w.eth.BlockChain().CacheConfig().TriePrefetcherParallelism) + numPrefetchers := w.chain.CacheConfig().TriePrefetcherParallelism + currentState.StartPrefetcher("miner", state.WithConcurrentWorkers(numPrefetchers)) return &environment{ signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), - state: state, + state: currentState, parent: parent, header: header, tcount: 0, From e60bb753a6e32a07a7904315a869e7ae40eeedcd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 31 Dec 2024 00:42:09 +0300 Subject: [PATCH 25/57] add validators pkg readme (#1398) * add validators pkg readme * Apply suggestions from code review Co-authored-by: Meaghan FitzGerald Signed-off-by: Ceyhun Onur * Apply suggestions from code review Co-authored-by: Meaghan FitzGerald Signed-off-by: Ceyhun Onur * Apply suggestions from code review Co-authored-by: Meaghan FitzGerald Signed-off-by: Ceyhun Onur * nits --------- Signed-off-by: Ceyhun Onur Co-authored-by: Meaghan FitzGerald --- plugin/evm/validators/README.md | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 plugin/evm/validators/README.md diff --git a/plugin/evm/validators/README.md b/plugin/evm/validators/README.md new file mode 100644 index 0000000000..84055b04ab --- /dev/null +++ b/plugin/evm/validators/README.md @@ -0,0 +1,61 @@ +# Validators + +The Validators package is a collection of structs and functions to manage the state and uptime of validators in the Subnet-EVM. It consists of the following components: + +- State package : The state package stores the validator state and uptime information. +- Uptime package: The uptime package manages the uptime tracking of the validators. +- Manager struct: The manager struct is responsible for managing the state and uptime of the validators. + +## State Package + +The state package stores the validator state and uptime information. The state package implements a CRUD interface for validators. The implementation tracks validators by their validationIDs and assumes they're unique per node and their validation period. The state implementation also assumes NodeIDs are unique in the tracked set. The state implementation only allows existing validator's `weight` and `IsActive` fields to be updated; all other fields should be constant and if any other field changes, the state manager errors and does not update the validator. + +For L1 validators, an `active` status implies the validator balance on the P-Chain is sufficient to cover the continuous validation fee. When an L1 validator balance is depleted, it is marked as `inactive` on the P-Chain and this information is passed to the Subnet-EVM's state. + +The state interface allows a listener to register state changes including validator addition, removal, and active status change. The listener always receives the full state when it first subscribes. + +The package defines how to serialize the data according to the codec. It can read and write the validator state and uptime information within the database. + +## Uptime Package + +The uptime package manages the uptime tracking of the L1 validators. It wraps [AvalancheGo's uptime tracking manager](https://pkg.go.dev/github.com/ava-labs/avalanchego/snow/uptime) under the hood and additionally introduces pausable uptime manager interface. The pausable uptime manager interface allows the manager to pause the uptime tracking for a specific validator when it becomes `inactive` and resume it when it becomes `active` again. + +The uptime package must be run on at least one L1 node, referred to in this document as the "tracker node". + +Uptime tracking works as follows: + +### StartTracking + +Nodes can start uptime tracking with the `StartTracking` method once they're bootstrapped. This method updates the uptime of up-to-date validators by adding the duration between their last updated time and tracker node's initializing time to their uptime. This effectively adds the tracker node's offline duration to the validator's uptime and optimistically assumes that the validators are online during this period. Subnet-EVM's pausable manager does not directly modify this behavior and it also updates validators that were paused/inactive before the node initialized. The pausable uptime manager assumes peers are online and `active` when the tracker nodes are offline. + +### Connected + +The AvalancheGo uptime manager records the time when a peer is connected to the tracker node. When a paused/ `inactive` validator is connected, the pausable uptime manager does not directly invoke the `Connected` method on the AvalancheGo uptime manager, thus the connection time is not directly recorded. Instead, the pausable uptime manager waits for the validator to increase its continuous validation fee balance and resume operation. When the validator resumes, the tracker node records the resumed time and starts tracking the uptime of the validator. + +Note: The uptime manager does not check if the connected peer is a validator or not. It records the connection time assuming that a non-validator peer can become a validator whilst they're connected to the uptime manager. + +### Disconnected + +When a peer validator is disconnected, the AvalancheGo uptime manager updates the uptime of the validator by adding the duration between the connection time and the disconnection time to the uptime of the validator. When a validator is paused/`inactive`, the pausable uptime manager handles the `inactive` peers as if they were disconnected. Thus the uptime manager assumes that no paused peers can be disconnected again from the pausable uptime manager. + +### Pause + +The pausable uptime manager can listen for validator status changes by subscribing to the state. When the state invokes the `OnValidatorStatusChange` method, the pausable uptime manager pauses the uptime tracking of the validator if the validator is currently `inactive`. When a validator is paused, it is treated as if it is disconnected from the tracker node; thus, its uptime is updated from the connection time to the pause time, and uptime manager stops tracking the uptime of the validator. + +### Resume + +When a paused validator peer resumes, meaning its status becomes `active`, the pausable uptime manager resumes the uptime tracking of the validator. It treats the peer as if it is connected to the tracker node. + +Note: The pausable uptime manager holds the set of connected peers that tracks the connected peers in the p2p layer. This set is used to start tracking the uptime of the paused/`inactive` validators when they resume; this is because the AvalancheGo uptime manager thinks that the peer is completely disconnected when it is paused. The pausable uptime manager is able to reconnect them to the inner manager by using this additional connected set. + +### CalculateUptime + +The `CalculateUptime` method calculates a node's uptime based on its connection status, connected time, and the current time. It first retrieves the node's current uptime and last update time from the state, returning an error if retrieval fails. If tracking hasn’t started, it assumes the node has been online since the last update, adding this duration to its uptime. If the node is not connected and tracking is `active`, uptime remains unchanged and returned. For connected nodes, the method ensures the connection time does not predate the last update to avoid double counting. Finally, it adds the duration since the last connection time to the node's uptime and returns the updated values. + +## Manager Struct + +`Manager` struct in `validators` package is responsible for managing the state of the validators by fetching the information from P-Chain state (via `GetCurrentValidatorSet` in chain context) and updating the state accordingly. It dispatches a `goroutine` to sync the validator state every 60 seconds. The manager fetches the up-to-date validator set from P-Chain and performs the sync operation. The sync operation first performs removing the validators from the state that are not in the P-Chain validator set. Then it adds new validators and updates the existing validators in the state. This order of operations ensures that the uptimes of validators being removed and re-added under same nodeIDs are updated in the same sync operation despite having different validationIDs. + +P-Chain's `GetCurrentValidatorSet` can report both L1 and Subnet validators. Subnet-EVM's uptime manager also tracks both of these validator types. So even if a the Subnet has not yet been converted to an L1, the uptime and validator state tracking is still performed by Subnet-EVM. + +Validator Manager persists the state to disk at the end of every sync operation. The VM also persists the validator database when the node is shutting down. \ No newline at end of file From 9d9cf92a0c0b2674f60ec1dfc34b4f7268246438 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 6 Jan 2025 19:12:05 +0300 Subject: [PATCH 26/57] add multiatch docker build to support arm (#1417) * add multiatch docker build to support arm * fix build img id * Update .github/workflows/publish_docker.yml Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur * Update .github/workflows/publish_docker.yml Co-authored-by: Quentin McGaw Signed-off-by: Ceyhun Onur * fix vm id * remove debug echo * revert format change --------- Signed-off-by: Ceyhun Onur Co-authored-by: Quentin McGaw --- .github/workflows/publish_docker.yml | 12 ++++-- .github/workflows/publish_docker_image.sh | 35 --------------- scripts/build_docker_image.sh | 52 +++++++++++++++++++++-- 3 files changed, 56 insertions(+), 43 deletions(-) delete mode 100755 .github/workflows/publish_docker_image.sh diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 5a16349cf5..d1764edc66 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -17,13 +17,17 @@ on: jobs: publish_docker_image: - name: Publish Docker Image - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Publish image to Dockerhub + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - name: Build and publish images to DockerHub env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} DOCKER_REPO: "avaplatform/subnet-evm" - run: .github/workflows/publish_docker_image.sh ${{ inputs.vm_id }} + VM_ID: ${{ inputs.vm_id }} + PUBLISH: 1 + PLATFORMS: "linux/amd64,linux/arm64" + run: scripts/build_docker_image.sh diff --git a/.github/workflows/publish_docker_image.sh b/.github/workflows/publish_docker_image.sh deleted file mode 100755 index 71bce26c12..0000000000 --- a/.github/workflows/publish_docker_image.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -# If this is not a trusted build (Docker Credentials are not set) -if [[ -z "$DOCKER_USERNAME" ]]; then - exit 0; -fi - -# Avalanche root directory -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../.. && pwd ) - -# Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Set the vm id if provided -if [[ $# -eq 1 ]]; then - VM_ID=$1 -fi - -# Buld the docker image -source "$SUBNET_EVM_PATH"/scripts/build_docker_image.sh - -if [[ $CURRENT_BRANCH == "master" ]]; then - echo "Tagging current image as $DOCKERHUB_REPO:latest" - docker tag "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" "$DOCKERHUB_REPO:latest" -fi - -echo "Pushing $DOCKERHUB_REPO:$BUILD_IMAGE_ID" - -echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin - -docker push -a "$DOCKERHUB_REPO" diff --git a/scripts/build_docker_image.sh b/scripts/build_docker_image.sh index 9de30cd7e7..93474e4a36 100755 --- a/scripts/build_docker_image.sh +++ b/scripts/build_docker_image.sh @@ -2,8 +2,25 @@ set -euo pipefail +# If set to non-empty, prompts the building of a multi-arch image when the image +# name indicates use of a registry. +# +# A registry is required to build a multi-arch image since a multi-arch image is +# not really an image at all. A multi-arch image (also called a manifest) is +# basically a list of arch-specific images available from the same registry that +# hosts the manifest. Manifests are not supported for local images. +# +# Reference: https://docs.docker.com/build/building/multi-platform/ +PLATFORMS="${PLATFORMS:-}" + +# If set to non-empty, the image will be published to the registry. +PUBLISH="${PUBLISH:-}" + # Directory above this script -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) # Load the constants source "$SUBNET_EVM_PATH"/scripts/constants.sh @@ -14,8 +31,30 @@ source "$SUBNET_EVM_PATH"/scripts/versions.sh # WARNING: this will use the most recent commit even if there are un-committed changes present BUILD_IMAGE_ID=${BUILD_IMAGE_ID:-"${CURRENT_BRANCH}"} +# buildx (BuildKit) improves the speed and UI of builds over the legacy builder and +# simplifies creation of multi-arch images. +# +# Reference: https://docs.docker.com/build/buildkit/ +DOCKER_CMD="docker buildx build" + +if [[ -n "${PUBLISH}" ]]; then + DOCKER_CMD="${DOCKER_CMD} --push" + + echo "Pushing $DOCKERHUB_REPO:$BUILD_IMAGE_ID" + + # A populated DOCKER_USERNAME env var triggers login + if [[ -n "${DOCKER_USERNAME:-}" ]]; then + echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin + fi +fi + +# Build a multi-arch image if requested +if [[ -n "${PLATFORMS}" ]]; then + DOCKER_CMD="${DOCKER_CMD} --platform=${PLATFORMS}" +fi + VM_ID=${VM_ID:-"${DEFAULT_VM_ID}"} -if [[ "${VM_ID}" != "${DEFAULT_VM_ID}" ]]; then +if [[ "${VM_ID}" != "${DEFAULT_VM_ID}" ]]; then DOCKERHUB_TAG="${VM_ID}-${DOCKERHUB_TAG}" fi @@ -23,9 +62,14 @@ fi AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_NODE_IMAGE:-${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION}}" echo "Building Docker Image: $DOCKERHUB_REPO:$BUILD_IMAGE_ID based of AvalancheGo@$AVALANCHE_VERSION" -docker build -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" -t "$DOCKERHUB_REPO:${DOCKERHUB_TAG}" \ - "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ +${DOCKER_CMD} -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" -t "$DOCKERHUB_REPO:${DOCKERHUB_TAG}" \ + "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ --build-arg AVALANCHEGO_NODE_IMAGE="$AVALANCHEGO_NODE_IMAGE" \ --build-arg SUBNET_EVM_COMMIT="$SUBNET_EVM_COMMIT" \ --build-arg CURRENT_BRANCH="$CURRENT_BRANCH" \ --build-arg VM_ID="$VM_ID" + +if [[ -n "${PUBLISH}" && $CURRENT_BRANCH == "master" ]]; then + echo "Tagging current image as $DOCKERHUB_REPO:latest" + docker buildx imagetools create -t "$DOCKERHUB_REPO:latest" "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" +fi From 1fdaab7e4a78e44d718a06d9c72cbb81ac969657 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Tue, 7 Jan 2025 13:21:54 +0100 Subject: [PATCH 27/57] chore(all): simplify mocks generation (#1413) - Localized mockgen commands in the package where they are needed - Generate mocks from your IDE directly - Platform independent way of generating mocks - Check automatically mocks are kept up to date with their matching interfaces - Check automatically mocks all have a matching source controlled generation command - Remove license headers for generated mocks - Generating mocks section added in contributing document - Use same mockgen version as specified in go.mod --- .github/CONTRIBUTING.md | 36 +++++++++++++++- .github/workflows/check-clean-branch.sh | 9 ---- .github/workflows/tests.yml | 19 ++++----- go.mod | 1 + go.sum | 2 + .../state/interfaces/mock_listener.go | 2 +- .../state/interfaces/mocks_generate_test.go | 3 ++ precompile/contract/mocks.go | 2 +- precompile/contract/mocks_generate_test.go | 3 ++ precompile/precompileconfig/mocks.go | 2 +- .../precompileconfig/mocks_generate_test.go | 3 ++ scripts/mock.gen.sh | 41 ------------------- scripts/mocks.mockgen.txt | 3 -- tools.go | 8 ++++ warp/aggregator/mock_signature_getter.go | 15 ++++--- warp/aggregator/mocks_generate_test.go | 3 ++ 16 files changed, 78 insertions(+), 74 deletions(-) delete mode 100755 .github/workflows/check-clean-branch.sh create mode 100644 plugin/evm/validators/state/interfaces/mocks_generate_test.go create mode 100644 precompile/contract/mocks_generate_test.go create mode 100644 precompile/precompileconfig/mocks_generate_test.go delete mode 100755 scripts/mock.gen.sh delete mode 100644 scripts/mocks.mockgen.txt create mode 100644 tools.go create mode 100644 warp/aggregator/mocks_generate_test.go diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1dc750fb60..c53c8ab6ae 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,6 +27,40 @@ guidelines: - Commit messages should be prefixed with the package(s) they modify. - E.g. "eth, rpc: make trace configs optional" +### Mocks + +Mocks are auto-generated using [mockgen](https://pkg.go.dev/go.uber.org/mock/mockgen) and `//go:generate` commands in the code. + +- To **re-generate all mocks**, use the command below from the root of the project: + + ```sh + go generate -run "go.uber.org/mock/mockgen" ./... + ``` + +- To **add** an interface that needs a corresponding mock generated: + - if the file `mocks_generate_test.go` exists in the package where the interface is located, either: + - modify its `//go:generate go run go.uber.org/mock/mockgen` to generate a mock for your interface (preferred); or + - add another `//go:generate go run go.uber.org/mock/mockgen` to generate a mock for your interface according to specific mock generation settings + - if the file `mocks_generate_test.go` does not exist in the package where the interface is located, create it with content (adapt as needed): + + ```go + // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. + // See the file LICENSE for licensing terms. + + package mypackage + + //go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE} -destination=mocks_test.go . YourInterface + ``` + + Notes: + 1. Ideally generate all mocks to `mocks_test.go` for the package you need to use the mocks for and do not export mocks to other packages. This reduces package dependencies, reduces production code pollution and forces to have locally defined narrow interfaces. + 1. Prefer using reflect mode to generate mocks than source mode, unless you need a mock for an unexported interface, which should be rare. +- To **remove** an interface from having a corresponding mock generated: + 1. Edit the `mocks_generate_test.go` file in the directory where the interface is defined + 1. If the `//go:generate` mockgen command line: + - generates a mock file for multiple interfaces, remove your interface from the line + - generates a mock file only for the interface, remove the entire line. If the file is empty, remove `mocks_generate_test.go` as well. + ## Documentation guidelines - Code should be well commented, so it is easier to read and maintain. @@ -35,7 +69,7 @@ guidelines: [commentary](https://go.dev/doc/effective_go#commentary) guidelines. - Changes with user facing impact (e.g., addition or modification of flags and options) should be accompanied by a link to a pull request to the [avalanche-docs](https://github.com/ava-labs/avalanche-docs) - repository. [example](https://github.com/ava-labs/avalanche-docs/pull/1119/files). + repository. [example](https://github.com/ava-labs/avalanche-docs/pull/1119/files). - Changes that modify a package significantly or add new features should either update the existing or include a new `README.md` file in that package. diff --git a/.github/workflows/check-clean-branch.sh b/.github/workflows/check-clean-branch.sh deleted file mode 100755 index 1eec74803a..0000000000 --- a/.github/workflows/check-clean-branch.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# Exits if any uncommitted changes are found. - -set -o errexit -set -o nounset -set -o pipefail - -git update-index --really-refresh >> /dev/null -git diff-index --quiet HEAD diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 896b97c969..693b148805 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,6 +54,13 @@ jobs: run: echo "TIMEOUT=1200s" >> "$GITHUB_ENV" - run: go mod download shell: bash + - name: Mocks are up to date + shell: bash + run: | + grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r rm + go generate -run "go.uber.org/mock/mockgen" ./... + git add --intent-to-add --all + git diff --exit-code - run: ./scripts/build.sh shell: bash - run: ./scripts/build_test.sh @@ -179,18 +186,6 @@ jobs: if: always() with: name: load-tmpnet-data - mock_gen: - name: MockGen Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: ${{ env.min_go_version }} - - shell: bash - run: scripts/mock.gen.sh - - shell: bash - run: .github/workflows/check-clean-branch.sh test_build_image: name: Image build runs-on: ubuntu-latest diff --git a/go.mod b/go.mod index bc313fa633..c53bd1a4a6 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e + golang.org/x/mod v0.17.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 diff --git a/go.sum b/go.sum index 75eb7e874e..e989b5c81d 100644 --- a/go.sum +++ b/go.sum @@ -719,6 +719,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= diff --git a/plugin/evm/validators/state/interfaces/mock_listener.go b/plugin/evm/validators/state/interfaces/mock_listener.go index 053a371d16..d556ebcab8 100644 --- a/plugin/evm/validators/state/interfaces/mock_listener.go +++ b/plugin/evm/validators/state/interfaces/mock_listener.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen -package=interfaces -destination=plugin/evm/validators/state/interfaces/mock_listener.go github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces StateCallbackListener +// mockgen -package=interfaces -destination=mock_listener.go . StateCallbackListener // // Package interfaces is a generated GoMock package. diff --git a/plugin/evm/validators/state/interfaces/mocks_generate_test.go b/plugin/evm/validators/state/interfaces/mocks_generate_test.go new file mode 100644 index 0000000000..fb08084d17 --- /dev/null +++ b/plugin/evm/validators/state/interfaces/mocks_generate_test.go @@ -0,0 +1,3 @@ +package interfaces + +//go:generate go run go.uber.org/mock/mockgen -package=$GOPACKAGE -destination=mock_listener.go . StateCallbackListener diff --git a/precompile/contract/mocks.go b/precompile/contract/mocks.go index 86943cf6e4..c82db74e3f 100644 --- a/precompile/contract/mocks.go +++ b/precompile/contract/mocks.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen -package=contract -destination=precompile/contract/mocks.go github.com/ava-labs/subnet-evm/precompile/contract BlockContext,AccessibleState,StateDB +// mockgen -package=contract -destination=mocks.go . BlockContext,AccessibleState,StateDB // // Package contract is a generated GoMock package. diff --git a/precompile/contract/mocks_generate_test.go b/precompile/contract/mocks_generate_test.go new file mode 100644 index 0000000000..4903b47c7c --- /dev/null +++ b/precompile/contract/mocks_generate_test.go @@ -0,0 +1,3 @@ +package contract + +//go:generate go run go.uber.org/mock/mockgen -package=$GOPACKAGE -destination=mocks.go . BlockContext,AccessibleState,StateDB diff --git a/precompile/precompileconfig/mocks.go b/precompile/precompileconfig/mocks.go index 614ec5a522..17ef64a708 100644 --- a/precompile/precompileconfig/mocks.go +++ b/precompile/precompileconfig/mocks.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen -package=precompileconfig -destination=precompile/precompileconfig/mocks.go github.com/ava-labs/subnet-evm/precompile/precompileconfig Predicater,Config,ChainConfig,Accepter +// mockgen -package=precompileconfig -destination=mocks.go . Predicater,Config,ChainConfig,Accepter // // Package precompileconfig is a generated GoMock package. diff --git a/precompile/precompileconfig/mocks_generate_test.go b/precompile/precompileconfig/mocks_generate_test.go new file mode 100644 index 0000000000..7dcdbbdde0 --- /dev/null +++ b/precompile/precompileconfig/mocks_generate_test.go @@ -0,0 +1,3 @@ +package precompileconfig + +//go:generate go run go.uber.org/mock/mockgen -package=$GOPACKAGE -destination=mocks.go . Predicater,Config,ChainConfig,Accepter diff --git a/scripts/mock.gen.sh b/scripts/mock.gen.sh deleted file mode 100755 index 3550cba16f..0000000000 --- a/scripts/mock.gen.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Root directory -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -if ! [[ "$0" =~ scripts/mock.gen.sh ]]; then - echo "must be run from repository root" - exit 255 -fi - -# https://github.com/uber-go/mock -go install -v go.uber.org/mock/mockgen@v0.4.0 - -if ! command -v go-license &>/dev/null; then - echo "go-license not found, installing..." - # https://github.com/palantir/go-license - go install -v github.com/palantir/go-license@v1.25.0 -fi - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -# tuples of (source interface import path, comma-separated interface names, output file path) -input="scripts/mocks.mockgen.txt" -while IFS= read -r line; do - IFS='=' read -r src_import_path interface_name output_path <<<"${line}" - package_name=$(basename "$(dirname "$output_path")") - echo "Generating ${output_path}..." - mockgen -package="${package_name}" -destination="${output_path}" "${src_import_path}" "${interface_name}" - - go-license \ - --config=./header.yml \ - "${output_path}" -done <"$input" - -echo "SUCCESS" diff --git a/scripts/mocks.mockgen.txt b/scripts/mocks.mockgen.txt deleted file mode 100644 index aba87c80da..0000000000 --- a/scripts/mocks.mockgen.txt +++ /dev/null @@ -1,3 +0,0 @@ -github.com/ava-labs/subnet-evm/precompile/precompileconfig=Predicater,Config,ChainConfig,Accepter=precompile/precompileconfig/mocks.go -github.com/ava-labs/subnet-evm/precompile/contract=BlockContext,AccessibleState,StateDB=precompile/contract/mocks.go -github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces=StateCallbackListener=plugin/evm/validators/state/interfaces/mock_listener.go diff --git a/tools.go b/tools.go new file mode 100644 index 0000000000..312a2d6302 --- /dev/null +++ b/tools.go @@ -0,0 +1,8 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package subnetevm + +import ( + _ "golang.org/x/mod/modfile" // golang.org/x/mod to satisfy requirement for go.uber.org/mock/mockgen@v0.4 +) diff --git a/warp/aggregator/mock_signature_getter.go b/warp/aggregator/mock_signature_getter.go index 537e3ae2e1..dcc88564b5 100644 --- a/warp/aggregator/mock_signature_getter.go +++ b/warp/aggregator/mock_signature_getter.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/subnet-evm/warp/aggregator (interfaces: SignatureGetter) +// Source: signature_getter.go +// +// Generated by this command: +// +// mockgen -package=aggregator -source=signature_getter.go -destination=mock_signature_getter.go -exclude_interfaces=NetworkClient +// // Package aggregator is a generated GoMock package. package aggregator @@ -38,16 +43,16 @@ func (m *MockSignatureGetter) EXPECT() *MockSignatureGetterMockRecorder { } // GetSignature mocks base method. -func (m *MockSignatureGetter) GetSignature(arg0 context.Context, arg1 ids.NodeID, arg2 *warp.UnsignedMessage) (*bls.Signature, error) { +func (m *MockSignatureGetter) GetSignature(ctx context.Context, nodeID ids.NodeID, unsignedWarpMessage *warp.UnsignedMessage) (*bls.Signature, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSignature", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "GetSignature", ctx, nodeID, unsignedWarpMessage) ret0, _ := ret[0].(*bls.Signature) ret1, _ := ret[1].(error) return ret0, ret1 } // GetSignature indicates an expected call of GetSignature. -func (mr *MockSignatureGetterMockRecorder) GetSignature(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSignatureGetterMockRecorder) GetSignature(ctx, nodeID, unsignedWarpMessage any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignature", reflect.TypeOf((*MockSignatureGetter)(nil).GetSignature), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSignature", reflect.TypeOf((*MockSignatureGetter)(nil).GetSignature), ctx, nodeID, unsignedWarpMessage) } diff --git a/warp/aggregator/mocks_generate_test.go b/warp/aggregator/mocks_generate_test.go new file mode 100644 index 0000000000..de176f9074 --- /dev/null +++ b/warp/aggregator/mocks_generate_test.go @@ -0,0 +1,3 @@ +package aggregator + +//go:generate go run go.uber.org/mock/mockgen -package=$GOPACKAGE -source=signature_getter.go -destination=mock_signature_getter.go -exclude_interfaces=NetworkClient From 9d4ef1a774f84137bfddfb26e0e7315748b25fb9 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 7 Jan 2025 23:25:58 +0300 Subject: [PATCH 28/57] Coreth sync (#1418) * fix test generator (#724) * add go mod tidy check --- .github/workflows/tests.yml | 4 ++++ triedb/pathdb/database_test.go | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 693b148805..dfd3f0358c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,6 +35,10 @@ jobs: - name: Run actionlint shell: bash run: scripts/actionlint.sh + - name: go mod tidy + run: | + go mod tidy + git diff --exit-code unit_test: name: Golang Unit Tests (${{ matrix.os }}) diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go index b452ac2a44..928041e02b 100644 --- a/triedb/pathdb/database_test.go +++ b/triedb/pathdb/database_test.go @@ -228,7 +228,11 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode dirties = make(map[common.Hash]struct{}) ) for i := 0; i < 20; i++ { - switch rand.Intn(opLen) { + op := createAccountOp + if i > 0 { + op = rand.Intn(opLen) + } + switch op { case createAccountOp: // account creation addr := testutil.RandomAddress() From 387bbc1cab40b40cd06d87d38db71b425df2d01e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 7 Jan 2025 23:48:11 +0300 Subject: [PATCH 29/57] bump versions (#1419) --- README.md | 3 ++- compatibility.json | 1 + plugin/evm/version.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 624c12837d..30bdfb31ab 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.6.10] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.11] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) [v0.6.12] AvalancheGo@v1.11.13/v1.12.0 (Protocol Version: 38) -[v0.7.0] AvalancheGo@v1.12.0 (Protocol Version: 38) +[v0.7.0] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) +[v0.7.1] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) ``` ## API diff --git a/compatibility.json b/compatibility.json index ec56a217d2..435fda56ee 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.7.1": 38, "v0.7.0": 38, "v0.6.12": 38, "v0.6.11": 37, diff --git a/plugin/evm/version.go b/plugin/evm/version.go index c3387afeb5..158c6e1510 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.7.0" + Version string = "v0.7.1" ) func init() { From bc91e407b4f934fd2eb83a3121f08b7ce1355bc0 Mon Sep 17 00:00:00 2001 From: Radovenchyk Date: Thu, 16 Jan 2025 19:52:59 +0200 Subject: [PATCH 30/57] docs(readme): fix build and tests workflow badge link (#1425) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30bdfb31ab..b48d9ca2ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Subnet EVM -[![Build + Test + Release](https://github.com/ava-labs/subnet-evm/actions/workflows/lint-tests-release.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/lint-tests-release.yml) +[![CI](https://github.com/ava-labs/subnet-evm/actions/workflows/ci.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/ci.yml) [![CodeQL](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml) [Avalanche](https://docs.avax.network/overview/getting-started/avalanche-platform) is a network composed of multiple blockchains. From c16a9dc160c6efd4011d42a53d00d2d7ee0d0e93 Mon Sep 17 00:00:00 2001 From: Radovenchyk Date: Thu, 16 Jan 2025 20:39:26 +0200 Subject: [PATCH 31/57] chore: replaced the link with avalanche (#1423) Co-authored-by: Darioush Jalali --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b48d9ca2ea..9b22a32acc 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CI](https://github.com/ava-labs/subnet-evm/actions/workflows/ci.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/ci.yml) [![CodeQL](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml) -[Avalanche](https://docs.avax.network/overview/getting-started/avalanche-platform) is a network composed of multiple blockchains. +[Avalanche](https://docs.avax.network/avalanche-l1s) is a network composed of multiple blockchains. Each blockchain is an instance of a Virtual Machine (VM), much like an object in an object-oriented language is an instance of a class. That is, the VM defines the behavior of the blockchain. From 68711e03580d32d63386fc02b2e2b1252db28a1d Mon Sep 17 00:00:00 2001 From: arturrez <56270896+arturrez@users.noreply.github.com> Date: Wed, 22 Jan 2025 03:17:52 -0800 Subject: [PATCH 32/57] deprecate avago keystore (#1426) * deprecate avago keystore * lint --- plugin/evm/vm_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 194b3ff183..cb06575af6 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -24,7 +24,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/api/keystore" "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" @@ -36,7 +35,6 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/enginetest" "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/formatting" - "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/chain" accountKeystore "github.com/ava-labs/subnet-evm/accounts/keystore" @@ -68,8 +66,6 @@ var ( testMinGasPrice int64 = 225_000_000_000 testKeys []*ecdsa.PrivateKey testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i] - username = "Johns" - password = "CjasdjhiPeirbSenfeI13" // #nosec G101 firstTxAmount = new(big.Int).Mul(big.NewInt(testMinGasPrice), big.NewInt(21000*100)) genesisBalance = new(big.Int).Mul(big.NewInt(testMinGasPrice), big.NewInt(21000*1000)) @@ -160,12 +156,6 @@ func setupGenesis( // The caller of this function is responsible for unlocking. ctx.Lock.Lock() - userKeystore := keystore.New(logging.NoLog{}, memdb.New()) - if err := userKeystore.CreateUser(username, password); err != nil { - t.Fatal(err) - } - ctx.Keystore = userKeystore.NewBlockchainKeyStore(ctx.ChainID) - issuer := make(chan commonEng.Message, 1) prefixedDB := prefixdb.New([]byte{1}, baseDB) return ctx, prefixedDB, genesisBytes, issuer, atomicMemory From 195f473ac9a61d270532345a17778febfbd245d0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 28 Jan 2025 06:42:28 -0800 Subject: [PATCH 33/57] refactor: align accounts/abi/bind with coreth+upstream (#1427) Co-authored-by: Quentin McGaw --- accounts/abi/bind/bind.go | 90 ++++++++------------------------- accounts/abi/bind/bind_extra.go | 70 +++++++++++++++++++++++++ accounts/abi/bind/template.go | 24 ++++----- cmd/abigen/namefilter.go | 1 - cmd/abigen/namefilter_test.go | 1 - triedb/pathdb/database_test.go | 2 +- 6 files changed, 104 insertions(+), 84 deletions(-) create mode 100644 accounts/abi/bind/bind_extra.go diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 7ad368e5ad..a4dd0963d7 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -43,9 +43,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// BindHook is a callback function that can be used to customize the binding. -type BindHook func(lang Lang, pkg string, types []string, contracts map[string]*TmplContract, structs map[string]*TmplStruct) (data interface{}, templateSource string, err error) - // Lang is a target programming language selector to generate bindings for. type Lang int @@ -53,7 +50,7 @@ const ( LangGo Lang = iota ) -func IsKeyWord(arg string) bool { +func isKeyWord(arg string) bool { switch arg { case "break": case "case": @@ -101,10 +98,10 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, bindHook BindHook) (string, error) { var ( // contracts is the map of each individual contract requested binding - contracts = make(map[string]*TmplContract) + contracts = make(map[string]*tmplContract) // structs is the map of all redeclared structs shared by passed contracts. - structs = make(map[string]*TmplStruct) + structs = make(map[string]*tmplStruct) // isLib is the map used to flag each encountered library as such isLib = make(map[string]struct{}) @@ -125,11 +122,11 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // Extract the call and transact methods; events, struct definitions; and sort them alphabetically var ( - calls = make(map[string]*TmplMethod) - transacts = make(map[string]*TmplMethod) + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) events = make(map[string]*tmplEvent) - fallback *TmplMethod - receive *TmplMethod + fallback *tmplMethod + receive *tmplMethod // identifiers are used to detect duplicated identifiers of functions // and events. For all calls, transacts and events, abigen will generate @@ -172,7 +169,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || IsKeyWord(input.Name) { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } if hasStruct(input.Type) { @@ -191,9 +188,9 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Append the methods to the call or transact lists if original.IsConstant() { - calls[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } else { - transacts[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } } for _, original := range evmABI.Events { @@ -224,7 +221,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || IsKeyWord(input.Name) { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } // Event is a bit special, we need to define event struct in binding, @@ -245,12 +242,12 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Add two special fallback functions if they exist if evmABI.HasFallback() { - fallback = &TmplMethod{Original: evmABI.Fallback} + fallback = &tmplMethod{Original: evmABI.Fallback} } if evmABI.HasReceive() { - receive = &TmplMethod{Original: evmABI.Receive} + receive = &tmplMethod{Original: evmABI.Receive} } - contracts[types[i]] = &TmplContract{ + contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), @@ -341,14 +338,10 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // bindType is a set of type binders that convert Solidity types to some supported // programming language types. -var bindType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindTypeGo, } -var bindTypeNew = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ - LangGo: bindTypeNewGo, -} - // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. func bindBasicTypeGo(kind abi.Type) string { switch kind.T { @@ -373,43 +366,10 @@ func bindBasicTypeGo(kind abi.Type) string { } } -// bindTypeNewGo converts new types to Go ones. -func bindTypeNewGo(kind abi.Type, structs map[string]*TmplStruct) string { - switch kind.T { - case abi.TupleTy: - return structs[kind.TupleRawName+kind.String()].Name + "{}" - case abi.ArrayTy: - return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + "{}" - case abi.SliceTy: - return "nil" - case abi.AddressTy: - return "common.Address{}" - case abi.IntTy, abi.UintTy: - parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) - switch parts[2] { - case "8", "16", "32", "64": - return "0" - } - return "new(big.Int)" - case abi.FixedBytesTy: - return fmt.Sprintf("[%d]byte", kind.Size) + "{}" - case abi.BytesTy: - return "[]byte{}" - case abi.FunctionTy: - return "[24]byte{}" - case abi.BoolTy: - return "false" - case abi.StringTy: - return `""` - default: - return "nil" - } -} - // bindTypeGo converts solidity types to Go ones. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: return structs[kind.TupleRawName+kind.String()].Name @@ -424,13 +384,13 @@ func bindTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { // bindTopicType is a set of type binders that convert Solidity types to some // supported programming language topic types. -var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindTopicTypeGo, } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same // functionality as for simple types, but dynamic types get converted to hashes. -func bindTopicTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeGo(kind, structs) // todo(rjl493456442) according solidity documentation, indexed event @@ -447,14 +407,14 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { // bindStructType is a set of type binders that convert Solidity tuple types to some supported // programming language struct definition. -var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindStructTypeGo, } // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping // in the given map. // Notably, this function will resolve and record nested struct recursively. -func bindStructTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: // We compose a raw struct name and a canonical parameter expression @@ -483,7 +443,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { } name = capitalise(name) - structs[id] = &TmplStruct{ + structs[id] = &tmplStruct{ Name: name, Fields: fields, } @@ -568,11 +528,3 @@ func hasStruct(t abi.Type) bool { return false } } - -func mkList(args ...interface{}) []interface{} { - return args -} - -func add(a, b int) int { - return a + b -} diff --git a/accounts/abi/bind/bind_extra.go b/accounts/abi/bind/bind_extra.go new file mode 100644 index 0000000000..a622058edc --- /dev/null +++ b/accounts/abi/bind/bind_extra.go @@ -0,0 +1,70 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bind + +import ( + "fmt" + "regexp" + + "github.com/ava-labs/subnet-evm/accounts/abi" +) + +type ( + // These types are exported for use in bind/precompilebind + TmplContract = tmplContract + TmplMethod = tmplMethod + TmplStruct = tmplStruct +) + +// BindHook is a callback function that can be used to customize the binding. +type BindHook func(lang Lang, pkg string, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data any, templateSource string, err error) + +func IsKeyWord(arg string) bool { + return isKeyWord(arg) +} + +var bindTypeNew = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTypeNewGo, +} + +// bindTypeNewGo converts new types to Go ones. +func bindTypeNewGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + "{}" + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + "{}" + case abi.SliceTy: + return "nil" + case abi.AddressTy: + return "common.Address{}" + case abi.IntTy, abi.UintTy: + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + switch parts[2] { + case "8", "16", "32", "64": + return "0" + } + return "new(big.Int)" + case abi.FixedBytesTy: + return fmt.Sprintf("[%d]byte", kind.Size) + "{}" + case abi.BytesTy: + return "[]byte{}" + case abi.FunctionTy: + return "[24]byte{}" + case abi.BoolTy: + return "false" + case abi.StringTy: + return `""` + default: + return "nil" + } +} + +func mkList(args ...any) []any { + return args +} + +func add(a, b int) int { + return a + b +} diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index a39311d774..0ee3946569 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -31,30 +31,30 @@ import "github.com/ava-labs/subnet-evm/accounts/abi" // tmplData is the data structure required to fill the binding template. type tmplData struct { Package string // Name of the package to place the generated file in - Contracts map[string]*TmplContract // List of contracts to generate into this file + Contracts map[string]*tmplContract // List of contracts to generate into this file Libraries map[string]string // Map the bytecode's link pattern to the library name - Structs map[string]*TmplStruct // Contract struct type definitions + Structs map[string]*tmplStruct // Contract struct type definitions } -// TmplContract contains the data needed to generate an individual contract binding. -type TmplContract struct { +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from InputBin string // Optional EVM bytecode used to generate deploy code from FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization - Calls map[string]*TmplMethod // Contract calls that only read state data - Transacts map[string]*TmplMethod // Contract calls that write state data - Fallback *TmplMethod // Additional special fallback function - Receive *TmplMethod // Additional special receive function + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function Events map[string]*tmplEvent // Contract events accessors Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs Library bool // Indicator whether the contract is a library } -// TmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed // and cached data fields. -type TmplMethod struct { +type tmplMethod struct { Original abi.Method // Original method as parsed by the abi package Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) Structured bool // Whether the returns should be accumulated into a struct @@ -75,9 +75,9 @@ type tmplField struct { SolKind abi.Type // Raw abi type information } -// TmplStruct is a wrapper around an abi.tuple and contains an auto-generated +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated // struct name. -type TmplStruct struct { +type tmplStruct struct { Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. Fields []*tmplField // Struct fields definition depends on the binding language. } diff --git a/cmd/abigen/namefilter.go b/cmd/abigen/namefilter.go index e43cdf38e0..5fcf6e17a0 100644 --- a/cmd/abigen/namefilter.go +++ b/cmd/abigen/namefilter.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package main import ( diff --git a/cmd/abigen/namefilter_test.go b/cmd/abigen/namefilter_test.go index 43dd28707e..d6c9a0a932 100644 --- a/cmd/abigen/namefilter_test.go +++ b/cmd/abigen/namefilter_test.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package main import ( diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go index 928041e02b..0d7ce2fd57 100644 --- a/triedb/pathdb/database_test.go +++ b/triedb/pathdb/database_test.go @@ -319,7 +319,7 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin, nil) } -// lastRoot returns the latest root hash, or empty if nothing is cached. +// lastHash returns the latest root hash, or empty if nothing is cached. func (t *tester) lastHash() common.Hash { if len(t.roots) == 0 { return common.Hash{} From 464889307b0fbf9118a5b18c6f251530585fd94f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 29 Jan 2025 07:14:55 -0800 Subject: [PATCH 34/57] sync coreth changes up to 3148809 (#1434) * move config package (coreth c167754ba) * refactor: Split config package so it doesn't import core, core/vm (#734) Co-authored-by: Quentin McGaw * bump avalanchego (same as coreth's 4765329f) * Apply upstream fixes (up to d3cc618) (#766) Co-authored-by: maskpp Co-authored-by: Aaron Chen Co-authored-by: Shiming Zhang Co-authored-by: Felix Lange Co-authored-by: Nathan Co-authored-by: Eric <45141191+zlacfzy@users.noreply.github.com> * fix panic if config duration is set to 0 (#765) * fix mocks * antithesis script patch: change dir for avalanchego build (for go.mod) --------- Co-authored-by: Quentin McGaw Co-authored-by: maskpp Co-authored-by: Aaron Chen Co-authored-by: Shiming Zhang Co-authored-by: Felix Lange Co-authored-by: Nathan Co-authored-by: Eric <45141191+zlacfzy@users.noreply.github.com> --- core/state/snapshot/snapshot.go | 4 +- core/state/statedb.go | 27 +- eth/gasprice/feehistory.go | 7 + go.mod | 15 +- go.sum | 30 +- plugin/evm/admin.go | 13 +- plugin/evm/{ => client}/client.go | 35 +- plugin/evm/client_interface_test.go | 17 - plugin/evm/config.go | 373 +---------------- plugin/evm/config/config.go | 377 ++++++++++++++++++ plugin/evm/{ => config}/config_test.go | 2 +- plugin/evm/database/database.go | 2 +- plugin/evm/gossip.go | 2 +- plugin/evm/imports_test.go | 71 ++++ plugin/evm/service.go | 27 +- plugin/evm/validators.go | 19 + .../state/interfaces/mock_listener.go | 25 +- plugin/evm/vm.go | 37 +- plugin/evm/vm_database.go | 5 +- plugin/evm/vm_test.go | 21 +- precompile/contract/mocks.go | 35 +- precompile/precompileconfig/mocks.go | 36 +- rpc/http.go | 2 +- scripts/build_antithesis_images.sh | 2 +- scripts/versions.sh | 2 +- warp/aggregator/mock_signature_getter.go | 1 + 26 files changed, 650 insertions(+), 537 deletions(-) rename plugin/evm/{ => client}/client.go (76%) delete mode 100644 plugin/evm/client_interface_test.go create mode 100644 plugin/evm/config/config.go rename plugin/evm/{ => config}/config_test.go (99%) create mode 100644 plugin/evm/imports_test.go create mode 100644 plugin/evm/validators.go diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 3490d743bf..520a522042 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -891,6 +891,8 @@ func (t *Tree) disklayer() *diskLayer { case *diskLayer: return layer case *diffLayer: + layer.lock.RLock() + defer layer.lock.RUnlock() return layer.origin default: panic(fmt.Sprintf("%T: undefined layer", snap)) @@ -922,7 +924,7 @@ func (t *Tree) generating() (bool, error) { return layer.genMarker != nil, nil } -// DiskRoot is a external helper function to return the disk layer root. +// DiskRoot is an external helper function to return the disk layer root. func (t *Tree) DiskRoot() common.Hash { t.lock.Lock() defer t.lock.Unlock() diff --git a/core/state/statedb.go b/core/state/statedb.go index b4c2da9566..a0517004da 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -29,6 +29,7 @@ package state import ( "fmt" + "maps" "sort" "time" @@ -774,18 +775,18 @@ func (s *StateDB) Copy() *StateDB { db: s.db, trie: s.db.CopyTrie(s.trie), originalRoot: s.originalRoot, - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + accounts: copySet(s.accounts), + storages: copy2DSet(s.storages), + accountsOrigin: copySet(s.accountsOrigin), + storagesOrigin: copy2DSet(s.storagesOrigin), stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), + stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct), refund: s.refund, logs: make(map[common.Hash][]*types.Log, len(s.logs)), logSize: s.logSize, - preimages: make(map[common.Hash][]byte, len(s.preimages)), + preimages: maps.Clone(s.preimages), journal: newJournal(), hasher: crypto.NewKeccakState(), @@ -827,16 +828,6 @@ func (s *StateDB) Copy() *StateDB { } state.stateObjectsDirty[addr] = struct{}{} } - // Deep copy the destruction markers. - for addr, value := range s.stateObjectsDestruct { - state.stateObjectsDestruct[addr] = value - } - // Deep copy the state changes made in the scope of block - // along with their original values. - state.accounts = copySet(s.accounts) - state.storages = copy2DSet(s.storages) - state.accountsOrigin = copySet(state.accountsOrigin) - state.storagesOrigin = copy2DSet(state.storagesOrigin) // Deep copy the logs occurred in the scope of block for hash, logs := range s.logs { @@ -847,10 +838,6 @@ func (s *StateDB) Copy() *StateDB { } state.logs[hash] = cpy } - // Deep copy the preimages occurred in the scope of block - for hash, preimage := range s.preimages { - state.preimages[hash] = preimage - } // Do we need to copy the access list and transient storage? // In practice: No. At the start of a transaction, these two lists are empty. // In practice, we only ever copy state _between_ transactions/blocks, never diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index c85e53308d..ffeddadaf7 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -45,6 +45,10 @@ var ( errBeyondHistoricalLimit = errors.New("request beyond historical limit") ) +const ( + maxQueryLimit = 100 +) + // txGasAndReward is sorted in ascending order based on reward type txGasAndReward struct { gasUsed uint64 @@ -173,6 +177,9 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL if blocks < 1 { return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks } + if len(rewardPercentiles) > maxQueryLimit { + return common.Big0, nil, nil, nil, fmt.Errorf("%w: over the query limit %d", errInvalidPercentile, maxQueryLimit) + } if blocks > oracle.maxCallBlockHistory { log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", oracle.maxCallBlockHistory) blocks = oracle.maxCallBlockHistory diff --git a/go.mod b/go.mod index c53bd1a4a6..51f5f085c2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.1 + github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -40,15 +40,16 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.25.7 go.uber.org/goleak v1.3.0 - go.uber.org/mock v0.4.0 + go.uber.org/mock v0.5.0 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e - golang.org/x/mod v0.17.0 + golang.org/x/mod v0.18.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 golang.org/x/time v0.3.0 + golang.org/x/tools v0.22.0 google.golang.org/protobuf v1.34.2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) @@ -56,7 +57,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer // indirect + github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -101,9 +102,6 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/jackpal/gateway v1.0.6 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.15 // indirect @@ -155,10 +153,9 @@ require ( 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 - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/term v0.27.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/go.sum b/go.sum index e989b5c81d..49e81af762 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.1 h1:NL04K5+gciC2XqGZbDcIu0nuVApEddzc6YyujRBv+u8= -github.com/ava-labs/avalanchego v1.12.1/go.mod h1:xnVvN86jhxndxfS8e0U7v/0woyfx9BhX/feld7XDjDE= -github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer h1:mRB03tLPUvgNko4nP4VwWQdiHeHaLHtdwsnqwxrsGec= -github.com/ava-labs/coreth v0.13.9-rc.2-encapsulate-signer/go.mod h1:tqRAe+7bGLo2Rq/Ph4iYMSch72ag/Jn0DiDMDz1Xa9E= +github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625 h1:sbmfwhpetCKI7Unzw9jJ+2HWLRFM7vq7th0pH2LclCQ= +github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625/go.mod h1:oK/C7ZGo5cAEayBKBoawh2EpOo3E9gD1rpd9NAM0RkQ= +github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35 h1:qBNnMleaJ7yWjNiDdV7wIf/e/PxubB+Ww7Mfx4QN4p8= +github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35/go.mod h1:nvQqJem4MuE0pU93aqBPsaEZx9NnXT0lI8d6rrQS5uY= 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= @@ -363,8 +363,6 @@ 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-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -376,10 +374,6 @@ 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/gateway v1.0.6 h1:/MJORKvJEwNVldtGVJC2p2cwCnsSoLn3hl3zxmZT7tk= -github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= -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/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -660,8 +654,8 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI 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.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +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= @@ -719,8 +713,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -765,8 +759,8 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx 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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -938,8 +932,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index fd8d7f8d6e..37b4e78e12 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/subnet-evm/plugin/evm/client" "github.com/ethereum/go-ethereum/log" ) @@ -65,11 +66,7 @@ func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) err return p.profiler.LockProfile() } -type SetLogLevelArgs struct { - Level string `json:"level"` -} - -func (p *Admin) SetLogLevel(_ *http.Request, args *SetLogLevelArgs, reply *api.EmptyReply) error { +func (p *Admin) SetLogLevel(_ *http.Request, args *client.SetLogLevelArgs, reply *api.EmptyReply) error { log.Info("EVM: SetLogLevel called", "logLevel", args.Level) p.vm.ctx.Lock.Lock() @@ -81,11 +78,7 @@ func (p *Admin) SetLogLevel(_ *http.Request, args *SetLogLevelArgs, reply *api.E return nil } -type ConfigReply struct { - Config *Config `json:"config"` -} - -func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *ConfigReply) error { +func (p *Admin) GetVMConfig(_ *http.Request, _ *struct{}, reply *client.ConfigReply) error { reply.Config = &p.vm.config return nil } diff --git a/plugin/evm/client.go b/plugin/evm/client/client.go similarity index 76% rename from plugin/evm/client.go rename to plugin/evm/client/client.go index 36a0ee675f..53a2122e6a 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client/client.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package client import ( "context" @@ -12,11 +12,24 @@ import ( "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/rpc" + "github.com/ava-labs/subnet-evm/plugin/evm/config" ) // Interface compliance var _ Client = (*client)(nil) +type CurrentValidator struct { + ValidationID ids.ID `json:"validationID"` + NodeID ids.NodeID `json:"nodeID"` + Weight uint64 `json:"weight"` + StartTimestamp uint64 `json:"startTimestamp"` + IsActive bool `json:"isActive"` + IsL1Validator bool `json:"isL1Validator"` + IsConnected bool `json:"isConnected"` + UptimePercentage float32 `json:"uptimePercentage"` + UptimeSeconds uint64 `json:"uptimeSeconds"` +} + // Client interface for interacting with EVM [chain] type Client interface { StartCPUProfiler(ctx context.Context, options ...rpc.Option) error @@ -24,7 +37,7 @@ type Client interface { MemoryProfile(ctx context.Context, options ...rpc.Option) error LockProfile(ctx context.Context, options ...rpc.Option) error SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error - GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) + GetVMConfig(ctx context.Context, options ...rpc.Option) (*config.Config, error) GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) } @@ -63,6 +76,10 @@ func (c *client) LockProfile(ctx context.Context, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.lockProfile", struct{}{}, &api.EmptyReply{}, options...) } +type SetLogLevelArgs struct { + Level string `json:"level"` +} + // SetLogLevel dynamically sets the log level for the C Chain func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.setLogLevel", &SetLogLevelArgs{ @@ -70,13 +87,25 @@ func (c *client) SetLogLevel(ctx context.Context, level slog.Level, options ...r }, &api.EmptyReply{}, options...) } +type ConfigReply struct { + Config *config.Config `json:"config"` +} + // GetVMConfig returns the current config of the VM -func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) { +func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*config.Config, error) { res := &ConfigReply{} err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...) return res.Config, err } +type GetCurrentValidatorsRequest struct { + NodeIDs []ids.NodeID `json:"nodeIDs"` +} + +type GetCurrentValidatorsResponse struct { + Validators []CurrentValidator `json:"validators"` +} + // GetCurrentValidators returns the current validators func (c *client) GetCurrentValidators(ctx context.Context, nodeIDs []ids.NodeID, options ...rpc.Option) ([]CurrentValidator, error) { res := &GetCurrentValidatorsResponse{} diff --git a/plugin/evm/client_interface_test.go b/plugin/evm/client_interface_test.go deleted file mode 100644 index d88c4926b4..0000000000 --- a/plugin/evm/client_interface_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package evm - -import ( - "reflect" - "testing" -) - -func TestInterfaceStructOneToOne(t *testing.T) { - // checks struct provides at least the methods signatures in the interface - var _ Client = (*client)(nil) - // checks interface and struct have the same number of methods - clientType := reflect.TypeOf(&client{}) - ClientType := reflect.TypeOf((*Client)(nil)).Elem() - if clientType.NumMethod() != ClientType.NumMethod() { - t.Fatalf("no 1 to 1 compliance between struct methods (%v) and interface methods (%v)", clientType.NumMethod(), ClientType.NumMethod()) - } -} diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 1daa9339a7..8b779951c6 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -1,370 +1,21 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package evm import ( - "encoding/json" - "fmt" - "time" - - "github.com/ava-labs/avalanchego/database/pebbledb" "github.com/ava-labs/subnet-evm/core/txpool/legacypool" - "github.com/ava-labs/subnet-evm/eth" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/spf13/cast" -) - -const ( - defaultAcceptorQueueLimit = 64 // Provides 2 minutes of buffer (2s block target) for a commit delay - defaultPruningEnabled = true - defaultCommitInterval = 4096 - defaultTrieCleanCache = 512 - defaultTrieDirtyCache = 512 - defaultTrieDirtyCommitTarget = 20 - defaultTriePrefetcherParallelism = 16 - defaultSnapshotCache = 256 - defaultSyncableCommitInterval = defaultCommitInterval * 4 - defaultSnapshotWait = false - defaultRpcGasCap = 50_000_000 // Default to 50M Gas Limit - defaultRpcTxFeeCap = 100 // 100 AVAX - defaultMetricsExpensiveEnabled = true - defaultApiMaxDuration = 0 // Default to no maximum API call duration - defaultWsCpuRefillRate = 0 // Default to no maximum WS CPU usage - defaultWsCpuMaxStored = 0 // Default to no maximum WS CPU usage - defaultMaxBlocksPerRequest = 0 // Default to no maximum on the number of blocks per getLogs request - defaultContinuousProfilerFrequency = 15 * time.Minute - defaultContinuousProfilerMaxFiles = 5 - defaultPushGossipPercentStake = .9 - defaultPushGossipNumValidators = 100 - defaultPushGossipNumPeers = 0 - defaultPushRegossipNumValidators = 10 - defaultPushRegossipNumPeers = 0 - defaultPushGossipFrequency = 100 * time.Millisecond - defaultPullGossipFrequency = 1 * time.Second - defaultRegossipFrequency = 30 * time.Second - defaultOfflinePruningBloomFilterSize uint64 = 512 // Default size (MB) for the offline pruner to use - defaultLogLevel = "info" - defaultLogJSONFormat = false - defaultMaxOutboundActiveRequests = 16 - defaultPopulateMissingTriesParallelism = 1024 - defaultStateSyncServerTrieCache = 64 // MB - defaultAcceptedCacheSize = 32 // blocks - - // defaultStateSyncMinBlocks is the minimum number of blocks the blockchain - // should be ahead of local last accepted to perform state sync. - // This constant is chosen so normal bootstrapping is preferred when it would - // be faster than state sync. - // time assumptions: - // - normal bootstrap processing time: ~14 blocks / second - // - state sync time: ~6 hrs. - defaultStateSyncMinBlocks = 300_000 - defaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request - defaultDBType = pebbledb.Name - defaultValidatorAPIEnabled = true + "github.com/ava-labs/subnet-evm/plugin/evm/config" ) -type PBool bool - -var ( - defaultEnabledAPIs = []string{ - "eth", - "eth-filter", - "net", - "web3", - "internal-eth", - "internal-blockchain", - "internal-transaction", - } - defaultAllowUnprotectedTxHashes = []common.Hash{ - common.HexToHash("0xfefb2da535e927b85fe68eb81cb2e4a5827c905f78381a01ef2322aa9b0aee8e"), // EIP-1820: https://eips.ethereum.org/EIPS/eip-1820 - } -) - -type Duration struct { - time.Duration -} - -// Config ... -type Config struct { - // Airdrop - AirdropFile string `json:"airdrop"` - - // Subnet EVM APIs - SnowmanAPIEnabled bool `json:"snowman-api-enabled"` - ValidatorsAPIEnabled bool `json:"validators-api-enabled"` - AdminAPIEnabled bool `json:"admin-api-enabled"` - AdminAPIDir string `json:"admin-api-dir"` - WarpAPIEnabled bool `json:"warp-api-enabled"` - - // EnabledEthAPIs is a list of Ethereum services that should be enabled - // If none is specified, then we use the default list [defaultEnabledAPIs] - EnabledEthAPIs []string `json:"eth-apis"` - - // Continuous Profiler - ContinuousProfilerDir string `json:"continuous-profiler-dir"` // If set to non-empty string creates a continuous profiler - ContinuousProfilerFrequency Duration `json:"continuous-profiler-frequency"` // Frequency to run continuous profiler if enabled - ContinuousProfilerMaxFiles int `json:"continuous-profiler-max-files"` // Maximum number of files to maintain - - // API Gas/Price Caps - RPCGasCap uint64 `json:"rpc-gas-cap"` - RPCTxFeeCap float64 `json:"rpc-tx-fee-cap"` - - // Cache settings - TrieCleanCache int `json:"trie-clean-cache"` // Size of the trie clean cache (MB) - TrieDirtyCache int `json:"trie-dirty-cache"` // Size of the trie dirty cache (MB) - TrieDirtyCommitTarget int `json:"trie-dirty-commit-target"` // Memory limit to target in the dirty cache before performing a commit (MB) - TriePrefetcherParallelism int `json:"trie-prefetcher-parallelism"` // Max concurrent disk reads trie prefetcher should perform at once - SnapshotCache int `json:"snapshot-cache"` // Size of the snapshot disk layer clean cache (MB) - - // Eth Settings - Preimages bool `json:"preimages-enabled"` - SnapshotWait bool `json:"snapshot-wait"` - SnapshotVerify bool `json:"snapshot-verification-enabled"` - - // Pruning Settings - Pruning bool `json:"pruning-enabled"` // If enabled, trie roots are only persisted every 4096 blocks - AcceptorQueueLimit int `json:"accepted-queue-limit"` // Maximum blocks to queue before blocking during acceptance - CommitInterval uint64 `json:"commit-interval"` // Specifies the commit interval at which to persist EVM and atomic tries. - AllowMissingTries bool `json:"allow-missing-tries"` // If enabled, warnings preventing an incomplete trie index are suppressed - PopulateMissingTries *uint64 `json:"populate-missing-tries,omitempty"` // Sets the starting point for re-populating missing tries. Disables re-generation if nil. - PopulateMissingTriesParallelism int `json:"populate-missing-tries-parallelism"` // Number of concurrent readers to use when re-populating missing tries on startup. - PruneWarpDB bool `json:"prune-warp-db-enabled"` // Determines if the warpDB should be cleared on startup - - // Metric Settings - MetricsExpensiveEnabled bool `json:"metrics-expensive-enabled"` // Debug-level metrics that might impact runtime performance - - // API Settings - LocalTxsEnabled bool `json:"local-txs-enabled"` - - TxPoolPriceLimit uint64 `json:"tx-pool-price-limit"` - TxPoolPriceBump uint64 `json:"tx-pool-price-bump"` - TxPoolAccountSlots uint64 `json:"tx-pool-account-slots"` - TxPoolGlobalSlots uint64 `json:"tx-pool-global-slots"` - TxPoolAccountQueue uint64 `json:"tx-pool-account-queue"` - TxPoolGlobalQueue uint64 `json:"tx-pool-global-queue"` - TxPoolLifetime Duration `json:"tx-pool-lifetime"` - - APIMaxDuration Duration `json:"api-max-duration"` - WSCPURefillRate Duration `json:"ws-cpu-refill-rate"` - WSCPUMaxStored Duration `json:"ws-cpu-max-stored"` - MaxBlocksPerRequest int64 `json:"api-max-blocks-per-request"` - AllowUnfinalizedQueries bool `json:"allow-unfinalized-queries"` - AllowUnprotectedTxs bool `json:"allow-unprotected-txs"` - AllowUnprotectedTxHashes []common.Hash `json:"allow-unprotected-tx-hashes"` - - // Keystore Settings - KeystoreDirectory string `json:"keystore-directory"` // both absolute and relative supported - KeystoreExternalSigner string `json:"keystore-external-signer"` - KeystoreInsecureUnlockAllowed bool `json:"keystore-insecure-unlock-allowed"` - - // Gossip Settings - PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` - PushGossipNumValidators int `json:"push-gossip-num-validators"` - PushGossipNumPeers int `json:"push-gossip-num-peers"` - PushRegossipNumValidators int `json:"push-regossip-num-validators"` - PushRegossipNumPeers int `json:"push-regossip-num-peers"` - PushGossipFrequency Duration `json:"push-gossip-frequency"` - PullGossipFrequency Duration `json:"pull-gossip-frequency"` - RegossipFrequency Duration `json:"regossip-frequency"` - PriorityRegossipAddresses []common.Address `json:"priority-regossip-addresses"` - - // Log - LogLevel string `json:"log-level"` - LogJSONFormat bool `json:"log-json-format"` - - // Address for Tx Fees (must be empty if not supported by blockchain) - FeeRecipient string `json:"feeRecipient"` - - // Offline Pruning Settings - OfflinePruning bool `json:"offline-pruning-enabled"` - OfflinePruningBloomFilterSize uint64 `json:"offline-pruning-bloom-filter-size"` - OfflinePruningDataDirectory string `json:"offline-pruning-data-directory"` - - // VM2VM network - MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` - - // Sync settings - StateSyncEnabled bool `json:"state-sync-enabled"` - StateSyncSkipResume bool `json:"state-sync-skip-resume"` // Forces state sync to use the highest available summary block - StateSyncServerTrieCache int `json:"state-sync-server-trie-cache"` - StateSyncIDs string `json:"state-sync-ids"` - StateSyncCommitInterval uint64 `json:"state-sync-commit-interval"` - StateSyncMinBlocks uint64 `json:"state-sync-min-blocks"` - StateSyncRequestSize uint16 `json:"state-sync-request-size"` - - // Database Settings - InspectDatabase bool `json:"inspect-database"` // Inspects the database on startup if enabled. - - // SkipUpgradeCheck disables checking that upgrades must take place before the last - // accepted block. Skipping this check is useful when a node operator does not update - // their node before the network upgrade and their node accepts blocks that have - // identical state with the pre-upgrade ruleset. - SkipUpgradeCheck bool `json:"skip-upgrade-check"` - - // AcceptedCacheSize is the depth to keep in the accepted headers cache and the - // accepted logs cache at the accepted tip. - // - // This is particularly useful for improving the performance of eth_getLogs - // on RPC nodes. - AcceptedCacheSize int `json:"accepted-cache-size"` - - // TransactionHistory is the maximum number of blocks from head whose tx indices - // are reserved: - // * 0: means no limit - // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes - TransactionHistory uint64 `json:"transaction-history"` - // Deprecated, use 'TransactionHistory' instead. - TxLookupLimit uint64 `json:"tx-lookup-limit"` - - // SkipTxIndexing skips indexing transactions. - // This is useful for validators that don't need to index transactions. - // TxLookupLimit can be still used to control unindexing old transactions. - SkipTxIndexing bool `json:"skip-tx-indexing"` - - // WarpOffChainMessages encodes off-chain messages (unrelated to any on-chain event ie. block or AddressedCall) - // that the node should be willing to sign. - // Note: only supports AddressedCall payloads as defined here: - // https://github.com/ava-labs/avalanchego/tree/7623ffd4be915a5185c9ed5e11fa9be15a6e1f00/vms/platformvm/warp/payload#addressedcall - WarpOffChainMessages []hexutil.Bytes `json:"warp-off-chain-messages"` - - // RPC settings - HttpBodyLimit uint64 `json:"http-body-limit"` - - // Database settings - UseStandaloneDatabase *PBool `json:"use-standalone-database"` - DatabaseConfigContent string `json:"database-config"` - DatabaseConfigFile string `json:"database-config-file"` - DatabaseType string `json:"database-type"` - DatabasePath string `json:"database-path"` - DatabaseReadOnly bool `json:"database-read-only"` -} - -// EthAPIs returns an array of strings representing the Eth APIs that should be enabled -func (c Config) EthAPIs() []string { - return c.EnabledEthAPIs -} - -func (c Config) EthBackendSettings() eth.Settings { - return eth.Settings{MaxBlocksPerRequest: c.MaxBlocksPerRequest} -} - -func (c *Config) SetDefaults() { - c.EnabledEthAPIs = defaultEnabledAPIs - c.RPCGasCap = defaultRpcGasCap - c.RPCTxFeeCap = defaultRpcTxFeeCap - c.MetricsExpensiveEnabled = defaultMetricsExpensiveEnabled - - c.TxPoolPriceLimit = legacypool.DefaultConfig.PriceLimit - c.TxPoolPriceBump = legacypool.DefaultConfig.PriceBump - c.TxPoolAccountSlots = legacypool.DefaultConfig.AccountSlots - c.TxPoolGlobalSlots = legacypool.DefaultConfig.GlobalSlots - c.TxPoolAccountQueue = legacypool.DefaultConfig.AccountQueue - c.TxPoolGlobalQueue = legacypool.DefaultConfig.GlobalQueue - c.TxPoolLifetime.Duration = legacypool.DefaultConfig.Lifetime - - c.APIMaxDuration.Duration = defaultApiMaxDuration - c.WSCPURefillRate.Duration = defaultWsCpuRefillRate - c.WSCPUMaxStored.Duration = defaultWsCpuMaxStored - c.MaxBlocksPerRequest = defaultMaxBlocksPerRequest - c.ContinuousProfilerFrequency.Duration = defaultContinuousProfilerFrequency - c.ContinuousProfilerMaxFiles = defaultContinuousProfilerMaxFiles - c.Pruning = defaultPruningEnabled - c.TrieCleanCache = defaultTrieCleanCache - c.TrieDirtyCache = defaultTrieDirtyCache - c.TrieDirtyCommitTarget = defaultTrieDirtyCommitTarget - c.TriePrefetcherParallelism = defaultTriePrefetcherParallelism - c.SnapshotCache = defaultSnapshotCache - c.AcceptorQueueLimit = defaultAcceptorQueueLimit - c.CommitInterval = defaultCommitInterval - c.SnapshotWait = defaultSnapshotWait - c.PushGossipPercentStake = defaultPushGossipPercentStake - c.PushGossipNumValidators = defaultPushGossipNumValidators - c.PushGossipNumPeers = defaultPushGossipNumPeers - c.PushRegossipNumValidators = defaultPushRegossipNumValidators - c.PushRegossipNumPeers = defaultPushRegossipNumPeers - c.PushGossipFrequency.Duration = defaultPushGossipFrequency - c.PullGossipFrequency.Duration = defaultPullGossipFrequency - c.RegossipFrequency.Duration = defaultRegossipFrequency - c.OfflinePruningBloomFilterSize = defaultOfflinePruningBloomFilterSize - c.LogLevel = defaultLogLevel - c.LogJSONFormat = defaultLogJSONFormat - c.MaxOutboundActiveRequests = defaultMaxOutboundActiveRequests - c.PopulateMissingTriesParallelism = defaultPopulateMissingTriesParallelism - c.StateSyncServerTrieCache = defaultStateSyncServerTrieCache - c.StateSyncCommitInterval = defaultSyncableCommitInterval - c.StateSyncMinBlocks = defaultStateSyncMinBlocks - c.StateSyncRequestSize = defaultStateSyncRequestSize - c.AllowUnprotectedTxHashes = defaultAllowUnprotectedTxHashes - c.AcceptedCacheSize = defaultAcceptedCacheSize - c.DatabaseType = defaultDBType - c.ValidatorsAPIEnabled = defaultValidatorAPIEnabled -} - -func (d *Duration) UnmarshalJSON(data []byte) (err error) { - var v interface{} - if err := json.Unmarshal(data, &v); err != nil { - return err - } - d.Duration, err = cast.ToDurationE(v) - return err -} - -// String implements the stringer interface. -func (d Duration) String() string { - return d.Duration.String() -} - -// String implements the stringer interface. -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Duration.String()) -} - -// Validate returns an error if this is an invalid config. -func (c *Config) Validate() error { - if c.PopulateMissingTries != nil && (c.OfflinePruning || c.Pruning) { - return fmt.Errorf("cannot enable populate missing tries while offline pruning (enabled: %t)/pruning (enabled: %t) are enabled", c.OfflinePruning, c.Pruning) - } - if c.PopulateMissingTries != nil && c.PopulateMissingTriesParallelism < 1 { - return fmt.Errorf("cannot enable populate missing tries without at least one reader (parallelism: %d)", c.PopulateMissingTriesParallelism) - } - - if !c.Pruning && c.OfflinePruning { - return fmt.Errorf("cannot run offline pruning while pruning is disabled") - } - // If pruning is enabled, the commit interval must be non-zero so the node commits state tries every CommitInterval blocks. - if c.Pruning && c.CommitInterval == 0 { - return fmt.Errorf("cannot use commit interval of 0 with pruning enabled") - } - - if c.PushGossipPercentStake < 0 || c.PushGossipPercentStake > 1 { - return fmt.Errorf("push-gossip-percent-stake is %f but must be in the range [0, 1]", c.PushGossipPercentStake) - } - return nil -} - -func (c *Config) Deprecate() string { - msg := "" - // Deprecate the old config options and set the new ones. - if c.TxLookupLimit != 0 { - msg += "tx-lookup-limit is deprecated, use transaction-history instead. " - c.TransactionHistory = c.TxLookupLimit - } - - return msg -} - -func (p *PBool) String() string { - if p == nil { - return "nil" - } - return fmt.Sprintf("%t", *p) -} - -func (p *PBool) Bool() bool { - if p == nil { - return false - } - return bool(*p) +// defaultTxPoolConfig uses [legacypool.DefaultConfig] to make a [config.TxPoolConfig] +// that can be passed to [config.Config.SetDefaults]. +var defaultTxPoolConfig = config.TxPoolConfig{ + PriceLimit: legacypool.DefaultConfig.PriceLimit, + PriceBump: legacypool.DefaultConfig.PriceBump, + AccountSlots: legacypool.DefaultConfig.AccountSlots, + GlobalSlots: legacypool.DefaultConfig.GlobalSlots, + AccountQueue: legacypool.DefaultConfig.AccountQueue, + GlobalQueue: legacypool.DefaultConfig.GlobalQueue, + Lifetime: legacypool.DefaultConfig.Lifetime, } diff --git a/plugin/evm/config/config.go b/plugin/evm/config/config.go new file mode 100644 index 0000000000..d848d39c95 --- /dev/null +++ b/plugin/evm/config/config.go @@ -0,0 +1,377 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package config + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/ava-labs/avalanchego/database/pebbledb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/spf13/cast" +) + +const ( + defaultAcceptorQueueLimit = 64 // Provides 2 minutes of buffer (2s block target) for a commit delay + defaultPruningEnabled = true + defaultCommitInterval = 4096 + defaultTrieCleanCache = 512 + defaultTrieDirtyCache = 512 + defaultTrieDirtyCommitTarget = 20 + defaultTriePrefetcherParallelism = 16 + defaultSnapshotCache = 256 + defaultSyncableCommitInterval = defaultCommitInterval * 4 + defaultSnapshotWait = false + defaultRpcGasCap = 50_000_000 // Default to 50M Gas Limit + defaultRpcTxFeeCap = 100 // 100 AVAX + defaultMetricsExpensiveEnabled = true + defaultApiMaxDuration = 0 // Default to no maximum API call duration + defaultWsCpuRefillRate = 0 // Default to no maximum WS CPU usage + defaultWsCpuMaxStored = 0 // Default to no maximum WS CPU usage + defaultMaxBlocksPerRequest = 0 // Default to no maximum on the number of blocks per getLogs request + defaultContinuousProfilerFrequency = 15 * time.Minute + defaultContinuousProfilerMaxFiles = 5 + defaultPushGossipPercentStake = .9 + defaultPushGossipNumValidators = 100 + defaultPushGossipNumPeers = 0 + defaultPushRegossipNumValidators = 10 + defaultPushRegossipNumPeers = 0 + defaultPushGossipFrequency = 100 * time.Millisecond + defaultPullGossipFrequency = 1 * time.Second + defaultRegossipFrequency = 30 * time.Second + defaultOfflinePruningBloomFilterSize uint64 = 512 // Default size (MB) for the offline pruner to use + defaultLogLevel = "info" + defaultLogJSONFormat = false + defaultMaxOutboundActiveRequests = 16 + defaultPopulateMissingTriesParallelism = 1024 + defaultStateSyncServerTrieCache = 64 // MB + defaultAcceptedCacheSize = 32 // blocks + + // defaultStateSyncMinBlocks is the minimum number of blocks the blockchain + // should be ahead of local last accepted to perform state sync. + // This constant is chosen so normal bootstrapping is preferred when it would + // be faster than state sync. + // time assumptions: + // - normal bootstrap processing time: ~14 blocks / second + // - state sync time: ~6 hrs. + defaultStateSyncMinBlocks = 300_000 + defaultStateSyncRequestSize = 1024 // the number of key/values to ask peers for per request + defaultDBType = pebbledb.Name + defaultValidatorAPIEnabled = true +) + +type PBool bool + +var ( + defaultEnabledAPIs = []string{ + "eth", + "eth-filter", + "net", + "web3", + "internal-eth", + "internal-blockchain", + "internal-transaction", + } + defaultAllowUnprotectedTxHashes = []common.Hash{ + common.HexToHash("0xfefb2da535e927b85fe68eb81cb2e4a5827c905f78381a01ef2322aa9b0aee8e"), // EIP-1820: https://eips.ethereum.org/EIPS/eip-1820 + } +) + +type Duration struct { + time.Duration +} + +// Config ... +type Config struct { + // Airdrop + AirdropFile string `json:"airdrop"` + + // Subnet EVM APIs + SnowmanAPIEnabled bool `json:"snowman-api-enabled"` + ValidatorsAPIEnabled bool `json:"validators-api-enabled"` + AdminAPIEnabled bool `json:"admin-api-enabled"` + AdminAPIDir string `json:"admin-api-dir"` + WarpAPIEnabled bool `json:"warp-api-enabled"` + + // EnabledEthAPIs is a list of Ethereum services that should be enabled + // If none is specified, then we use the default list [defaultEnabledAPIs] + EnabledEthAPIs []string `json:"eth-apis"` + + // Continuous Profiler + ContinuousProfilerDir string `json:"continuous-profiler-dir"` // If set to non-empty string creates a continuous profiler + ContinuousProfilerFrequency Duration `json:"continuous-profiler-frequency"` // Frequency to run continuous profiler if enabled + ContinuousProfilerMaxFiles int `json:"continuous-profiler-max-files"` // Maximum number of files to maintain + + // API Gas/Price Caps + RPCGasCap uint64 `json:"rpc-gas-cap"` + RPCTxFeeCap float64 `json:"rpc-tx-fee-cap"` + + // Cache settings + TrieCleanCache int `json:"trie-clean-cache"` // Size of the trie clean cache (MB) + TrieDirtyCache int `json:"trie-dirty-cache"` // Size of the trie dirty cache (MB) + TrieDirtyCommitTarget int `json:"trie-dirty-commit-target"` // Memory limit to target in the dirty cache before performing a commit (MB) + TriePrefetcherParallelism int `json:"trie-prefetcher-parallelism"` // Max concurrent disk reads trie prefetcher should perform at once + SnapshotCache int `json:"snapshot-cache"` // Size of the snapshot disk layer clean cache (MB) + + // Eth Settings + Preimages bool `json:"preimages-enabled"` + SnapshotWait bool `json:"snapshot-wait"` + SnapshotVerify bool `json:"snapshot-verification-enabled"` + + // Pruning Settings + Pruning bool `json:"pruning-enabled"` // If enabled, trie roots are only persisted every 4096 blocks + AcceptorQueueLimit int `json:"accepted-queue-limit"` // Maximum blocks to queue before blocking during acceptance + CommitInterval uint64 `json:"commit-interval"` // Specifies the commit interval at which to persist EVM and atomic tries. + AllowMissingTries bool `json:"allow-missing-tries"` // If enabled, warnings preventing an incomplete trie index are suppressed + PopulateMissingTries *uint64 `json:"populate-missing-tries,omitempty"` // Sets the starting point for re-populating missing tries. Disables re-generation if nil. + PopulateMissingTriesParallelism int `json:"populate-missing-tries-parallelism"` // Number of concurrent readers to use when re-populating missing tries on startup. + PruneWarpDB bool `json:"prune-warp-db-enabled"` // Determines if the warpDB should be cleared on startup + + // Metric Settings + MetricsExpensiveEnabled bool `json:"metrics-expensive-enabled"` // Debug-level metrics that might impact runtime performance + + // API Settings + LocalTxsEnabled bool `json:"local-txs-enabled"` + + TxPoolPriceLimit uint64 `json:"tx-pool-price-limit"` + TxPoolPriceBump uint64 `json:"tx-pool-price-bump"` + TxPoolAccountSlots uint64 `json:"tx-pool-account-slots"` + TxPoolGlobalSlots uint64 `json:"tx-pool-global-slots"` + TxPoolAccountQueue uint64 `json:"tx-pool-account-queue"` + TxPoolGlobalQueue uint64 `json:"tx-pool-global-queue"` + TxPoolLifetime Duration `json:"tx-pool-lifetime"` + + APIMaxDuration Duration `json:"api-max-duration"` + WSCPURefillRate Duration `json:"ws-cpu-refill-rate"` + WSCPUMaxStored Duration `json:"ws-cpu-max-stored"` + MaxBlocksPerRequest int64 `json:"api-max-blocks-per-request"` + AllowUnfinalizedQueries bool `json:"allow-unfinalized-queries"` + AllowUnprotectedTxs bool `json:"allow-unprotected-txs"` + AllowUnprotectedTxHashes []common.Hash `json:"allow-unprotected-tx-hashes"` + + // Keystore Settings + KeystoreDirectory string `json:"keystore-directory"` // both absolute and relative supported + KeystoreExternalSigner string `json:"keystore-external-signer"` + KeystoreInsecureUnlockAllowed bool `json:"keystore-insecure-unlock-allowed"` + + // Gossip Settings + PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` + PushGossipNumValidators int `json:"push-gossip-num-validators"` + PushGossipNumPeers int `json:"push-gossip-num-peers"` + PushRegossipNumValidators int `json:"push-regossip-num-validators"` + PushRegossipNumPeers int `json:"push-regossip-num-peers"` + PushGossipFrequency Duration `json:"push-gossip-frequency"` + PullGossipFrequency Duration `json:"pull-gossip-frequency"` + RegossipFrequency Duration `json:"regossip-frequency"` + PriorityRegossipAddresses []common.Address `json:"priority-regossip-addresses"` + + // Log + LogLevel string `json:"log-level"` + LogJSONFormat bool `json:"log-json-format"` + + // Address for Tx Fees (must be empty if not supported by blockchain) + FeeRecipient string `json:"feeRecipient"` + + // Offline Pruning Settings + OfflinePruning bool `json:"offline-pruning-enabled"` + OfflinePruningBloomFilterSize uint64 `json:"offline-pruning-bloom-filter-size"` + OfflinePruningDataDirectory string `json:"offline-pruning-data-directory"` + + // VM2VM network + MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` + + // Sync settings + StateSyncEnabled bool `json:"state-sync-enabled"` + StateSyncSkipResume bool `json:"state-sync-skip-resume"` // Forces state sync to use the highest available summary block + StateSyncServerTrieCache int `json:"state-sync-server-trie-cache"` + StateSyncIDs string `json:"state-sync-ids"` + StateSyncCommitInterval uint64 `json:"state-sync-commit-interval"` + StateSyncMinBlocks uint64 `json:"state-sync-min-blocks"` + StateSyncRequestSize uint16 `json:"state-sync-request-size"` + + // Database Settings + InspectDatabase bool `json:"inspect-database"` // Inspects the database on startup if enabled. + + // SkipUpgradeCheck disables checking that upgrades must take place before the last + // accepted block. Skipping this check is useful when a node operator does not update + // their node before the network upgrade and their node accepts blocks that have + // identical state with the pre-upgrade ruleset. + SkipUpgradeCheck bool `json:"skip-upgrade-check"` + + // AcceptedCacheSize is the depth to keep in the accepted headers cache and the + // accepted logs cache at the accepted tip. + // + // This is particularly useful for improving the performance of eth_getLogs + // on RPC nodes. + AcceptedCacheSize int `json:"accepted-cache-size"` + + // TransactionHistory is the maximum number of blocks from head whose tx indices + // are reserved: + // * 0: means no limit + // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes + TransactionHistory uint64 `json:"transaction-history"` + // Deprecated, use 'TransactionHistory' instead. + TxLookupLimit uint64 `json:"tx-lookup-limit"` + + // SkipTxIndexing skips indexing transactions. + // This is useful for validators that don't need to index transactions. + // TxLookupLimit can be still used to control unindexing old transactions. + SkipTxIndexing bool `json:"skip-tx-indexing"` + + // WarpOffChainMessages encodes off-chain messages (unrelated to any on-chain event ie. block or AddressedCall) + // that the node should be willing to sign. + // Note: only supports AddressedCall payloads as defined here: + // https://github.com/ava-labs/avalanchego/tree/7623ffd4be915a5185c9ed5e11fa9be15a6e1f00/vms/platformvm/warp/payload#addressedcall + WarpOffChainMessages []hexutil.Bytes `json:"warp-off-chain-messages"` + + // RPC settings + HttpBodyLimit uint64 `json:"http-body-limit"` + + // Database settings + UseStandaloneDatabase *PBool `json:"use-standalone-database"` + DatabaseConfigContent string `json:"database-config"` + DatabaseConfigFile string `json:"database-config-file"` + DatabaseType string `json:"database-type"` + DatabasePath string `json:"database-path"` + DatabaseReadOnly bool `json:"database-read-only"` +} + +// TxPoolConfig contains the transaction pool config to be passed +// to [Config.SetDefaults]. +type TxPoolConfig struct { + PriceLimit uint64 + PriceBump uint64 + AccountSlots uint64 + GlobalSlots uint64 + AccountQueue uint64 + GlobalQueue uint64 + Lifetime time.Duration +} + +// EthAPIs returns an array of strings representing the Eth APIs that should be enabled +func (c Config) EthAPIs() []string { + return c.EnabledEthAPIs +} + +func (c *Config) SetDefaults(txPoolConfig TxPoolConfig) { + c.EnabledEthAPIs = defaultEnabledAPIs + c.RPCGasCap = defaultRpcGasCap + c.RPCTxFeeCap = defaultRpcTxFeeCap + c.MetricsExpensiveEnabled = defaultMetricsExpensiveEnabled + + // TxPool settings + c.TxPoolPriceLimit = txPoolConfig.PriceLimit + c.TxPoolPriceBump = txPoolConfig.PriceBump + c.TxPoolAccountSlots = txPoolConfig.AccountSlots + c.TxPoolGlobalSlots = txPoolConfig.GlobalSlots + c.TxPoolAccountQueue = txPoolConfig.AccountQueue + c.TxPoolGlobalQueue = txPoolConfig.GlobalQueue + c.TxPoolLifetime.Duration = txPoolConfig.Lifetime + + c.APIMaxDuration.Duration = defaultApiMaxDuration + c.WSCPURefillRate.Duration = defaultWsCpuRefillRate + c.WSCPUMaxStored.Duration = defaultWsCpuMaxStored + c.MaxBlocksPerRequest = defaultMaxBlocksPerRequest + c.ContinuousProfilerFrequency.Duration = defaultContinuousProfilerFrequency + c.ContinuousProfilerMaxFiles = defaultContinuousProfilerMaxFiles + c.Pruning = defaultPruningEnabled + c.TrieCleanCache = defaultTrieCleanCache + c.TrieDirtyCache = defaultTrieDirtyCache + c.TrieDirtyCommitTarget = defaultTrieDirtyCommitTarget + c.TriePrefetcherParallelism = defaultTriePrefetcherParallelism + c.SnapshotCache = defaultSnapshotCache + c.AcceptorQueueLimit = defaultAcceptorQueueLimit + c.CommitInterval = defaultCommitInterval + c.SnapshotWait = defaultSnapshotWait + c.PushGossipPercentStake = defaultPushGossipPercentStake + c.PushGossipNumValidators = defaultPushGossipNumValidators + c.PushGossipNumPeers = defaultPushGossipNumPeers + c.PushRegossipNumValidators = defaultPushRegossipNumValidators + c.PushRegossipNumPeers = defaultPushRegossipNumPeers + c.PushGossipFrequency.Duration = defaultPushGossipFrequency + c.PullGossipFrequency.Duration = defaultPullGossipFrequency + c.RegossipFrequency.Duration = defaultRegossipFrequency + c.OfflinePruningBloomFilterSize = defaultOfflinePruningBloomFilterSize + c.LogLevel = defaultLogLevel + c.LogJSONFormat = defaultLogJSONFormat + c.MaxOutboundActiveRequests = defaultMaxOutboundActiveRequests + c.PopulateMissingTriesParallelism = defaultPopulateMissingTriesParallelism + c.StateSyncServerTrieCache = defaultStateSyncServerTrieCache + c.StateSyncCommitInterval = defaultSyncableCommitInterval + c.StateSyncMinBlocks = defaultStateSyncMinBlocks + c.StateSyncRequestSize = defaultStateSyncRequestSize + c.AllowUnprotectedTxHashes = defaultAllowUnprotectedTxHashes + c.AcceptedCacheSize = defaultAcceptedCacheSize + c.DatabaseType = defaultDBType + c.ValidatorsAPIEnabled = defaultValidatorAPIEnabled +} + +func (d *Duration) UnmarshalJSON(data []byte) (err error) { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + d.Duration, err = cast.ToDurationE(v) + return err +} + +// String implements the stringer interface. +func (d Duration) String() string { + return d.Duration.String() +} + +// String implements the stringer interface. +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +// Validate returns an error if this is an invalid config. +func (c *Config) Validate() error { + if c.PopulateMissingTries != nil && (c.OfflinePruning || c.Pruning) { + return fmt.Errorf("cannot enable populate missing tries while offline pruning (enabled: %t)/pruning (enabled: %t) are enabled", c.OfflinePruning, c.Pruning) + } + if c.PopulateMissingTries != nil && c.PopulateMissingTriesParallelism < 1 { + return fmt.Errorf("cannot enable populate missing tries without at least one reader (parallelism: %d)", c.PopulateMissingTriesParallelism) + } + + if !c.Pruning && c.OfflinePruning { + return fmt.Errorf("cannot run offline pruning while pruning is disabled") + } + // If pruning is enabled, the commit interval must be non-zero so the node commits state tries every CommitInterval blocks. + if c.Pruning && c.CommitInterval == 0 { + return fmt.Errorf("cannot use commit interval of 0 with pruning enabled") + } + + if c.PushGossipPercentStake < 0 || c.PushGossipPercentStake > 1 { + return fmt.Errorf("push-gossip-percent-stake is %f but must be in the range [0, 1]", c.PushGossipPercentStake) + } + return nil +} + +func (c *Config) Deprecate() string { + msg := "" + // Deprecate the old config options and set the new ones. + if c.TxLookupLimit != 0 { + msg += "tx-lookup-limit is deprecated, use transaction-history instead. " + c.TransactionHistory = c.TxLookupLimit + } + + return msg +} + +func (p *PBool) String() string { + if p == nil { + return "nil" + } + return fmt.Sprintf("%t", *p) +} + +func (p *PBool) Bool() bool { + if p == nil { + return false + } + return bool(*p) +} diff --git a/plugin/evm/config_test.go b/plugin/evm/config/config_test.go similarity index 99% rename from plugin/evm/config_test.go rename to plugin/evm/config/config_test.go index f5deb297ff..7250eb5f86 100644 --- a/plugin/evm/config_test.go +++ b/plugin/evm/config/config_test.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package evm +package config import ( "encoding/json" diff --git a/plugin/evm/database/database.go b/plugin/evm/database/database.go index 2226a1c895..41f14a9dc4 100644 --- a/plugin/evm/database/database.go +++ b/plugin/evm/database/database.go @@ -9,13 +9,13 @@ import ( "path/filepath" "github.com/ava-labs/avalanchego/api/metrics" + avalanchenode "github.com/ava-labs/avalanchego/config/node" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/meterdb" "github.com/ava-labs/avalanchego/database/pebbledb" "github.com/ava-labs/avalanchego/database/versiondb" - avalanchenode "github.com/ava-labs/avalanchego/node" "github.com/ava-labs/avalanchego/utils/logging" "github.com/prometheus/client_golang/prometheus" ) diff --git a/plugin/evm/gossip.go b/plugin/evm/gossip.go index f10c63bb43..29adc9b1bf 100644 --- a/plugin/evm/gossip.go +++ b/plugin/evm/gossip.go @@ -46,7 +46,7 @@ func newTxGossipHandler[T gossip.Gossipable]( maxMessageSize int, throttlingPeriod time.Duration, throttlingLimit int, - validators *p2p.Validators, + validators p2p.ValidatorSet, ) txGossipHandler { // push gossip messages can be handled from any peer handler := gossip.NewHandler( diff --git a/plugin/evm/imports_test.go b/plugin/evm/imports_test.go new file mode 100644 index 0000000000..6b5e6c1351 --- /dev/null +++ b/plugin/evm/imports_test.go @@ -0,0 +1,71 @@ +// (c) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/tools/go/packages" +) + +// getDependencies takes a fully qualified package name and returns a map of all +// its recursive package imports (including itself) in the same format. +func getDependencies(packageName string) (map[string]struct{}, error) { + // Configure the load mode to include dependencies + cfg := &packages.Config{Mode: packages.NeedDeps | packages.NeedImports | packages.NeedName | packages.NeedModule} + pkgs, err := packages.Load(cfg, packageName) + if err != nil { + return nil, fmt.Errorf("failed to load package: %v", err) + } + + if len(pkgs) == 0 || pkgs[0].Errors != nil { + return nil, fmt.Errorf("failed to load package %s", packageName) + } + + deps := make(map[string]struct{}) + var collectDeps func(pkg *packages.Package) + collectDeps = func(pkg *packages.Package) { + if _, ok := deps[pkg.PkgPath]; ok { + return // Avoid re-processing the same dependency + } + deps[pkg.PkgPath] = struct{}{} + for _, dep := range pkg.Imports { + collectDeps(dep) + } + } + + // Start collecting dependencies + collectDeps(pkgs[0]) + return deps, nil +} + +func TestMustNotImport(t *testing.T) { + withRepo := func(pkg string) string { + const repo = "github.com/ava-labs/subnet-evm" + return fmt.Sprintf("%s/%s", repo, pkg) + } + mustNotImport := map[string][]string{ + // The following sub-packages of plugin/evm must not import core, core/vm + // so clients (e.g., wallets, e2e tests) can import them without pulling in + // the entire VM logic. + // Importing these packages configures libevm globally and it is not + // possible to do so for both coreth and subnet-evm, where the client may + // wish to connect to multiple chains. + "plugin/evm/client": {"core", "core/vm"}, + "plugin/evm/config": {"core", "core/vm"}, + } + + for packageName, forbiddenImports := range mustNotImport { + imports, err := getDependencies(withRepo(packageName)) + require.NoError(t, err) + + for _, forbiddenImport := range forbiddenImports { + fullForbiddenImport := withRepo(forbiddenImport) + _, found := imports[fullForbiddenImport] + require.False(t, found, "package %s must not import %s, check output of go list -f '{{ .Deps }}' \"%s\" ", packageName, fullForbiddenImport, withRepo(packageName)) + } + } +} diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 0ad10a63b6..ea57b15e26 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -10,33 +10,14 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/subnet-evm/plugin/evm/client" ) type ValidatorsAPI struct { vm *VM } -type GetCurrentValidatorsRequest struct { - NodeIDs []ids.NodeID `json:"nodeIDs"` -} - -type GetCurrentValidatorsResponse struct { - Validators []CurrentValidator `json:"validators"` -} - -type CurrentValidator struct { - ValidationID ids.ID `json:"validationID"` - NodeID ids.NodeID `json:"nodeID"` - Weight uint64 `json:"weight"` - StartTimestamp uint64 `json:"startTimestamp"` - IsActive bool `json:"isActive"` - IsL1Validator bool `json:"isL1Validator"` - IsConnected bool `json:"isConnected"` - UptimePercentage float32 `json:"uptimePercentage"` - UptimeSeconds uint64 `json:"uptimeSeconds"` -} - -func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *GetCurrentValidatorsRequest, reply *GetCurrentValidatorsResponse) error { +func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *client.GetCurrentValidatorsRequest, reply *client.GetCurrentValidatorsResponse) error { api.vm.ctx.Lock.RLock() defer api.vm.ctx.Lock.RUnlock() @@ -54,7 +35,7 @@ func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *GetCurrentV vIDs = api.vm.validatorsManager.GetValidationIDs() } - reply.Validators = make([]CurrentValidator, 0, vIDs.Len()) + reply.Validators = make([]client.CurrentValidator, 0, vIDs.Len()) for _, vID := range vIDs.List() { validator, err := api.vm.validatorsManager.GetValidator(vID) @@ -81,7 +62,7 @@ func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *GetCurrentV // with currentValidators in PlatformVM API uptimePercentage := float32(uptimeFloat * 100) - reply.Validators = append(reply.Validators, CurrentValidator{ + reply.Validators = append(reply.Validators, client.CurrentValidator{ ValidationID: validator.ValidationID, NodeID: validator.NodeID, StartTimestamp: validator.StartTimestamp, diff --git a/plugin/evm/validators.go b/plugin/evm/validators.go new file mode 100644 index 0000000000..44c5560267 --- /dev/null +++ b/plugin/evm/validators.go @@ -0,0 +1,19 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "context" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" +) + +type validatorSet struct { + set set.Set[ids.NodeID] +} + +func (v *validatorSet) Has(ctx context.Context, nodeID ids.NodeID) bool { + return v.set.Contains(nodeID) +} diff --git a/plugin/evm/validators/state/interfaces/mock_listener.go b/plugin/evm/validators/state/interfaces/mock_listener.go index d556ebcab8..6ea8e5facf 100644 --- a/plugin/evm/validators/state/interfaces/mock_listener.go +++ b/plugin/evm/validators/state/interfaces/mock_listener.go @@ -20,6 +20,7 @@ import ( type MockStateCallbackListener struct { ctrl *gomock.Controller recorder *MockStateCallbackListenerMockRecorder + isgomock struct{} } // MockStateCallbackListenerMockRecorder is the mock recorder for MockStateCallbackListener. @@ -40,37 +41,37 @@ func (m *MockStateCallbackListener) EXPECT() *MockStateCallbackListenerMockRecor } // OnValidatorAdded mocks base method. -func (m *MockStateCallbackListener) OnValidatorAdded(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64, arg3 bool) { +func (m *MockStateCallbackListener) OnValidatorAdded(vID ids.ID, nodeID ids.NodeID, startTime uint64, isActive bool) { m.ctrl.T.Helper() - m.ctrl.Call(m, "OnValidatorAdded", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "OnValidatorAdded", vID, nodeID, startTime, isActive) } // OnValidatorAdded indicates an expected call of OnValidatorAdded. -func (mr *MockStateCallbackListenerMockRecorder) OnValidatorAdded(arg0, arg1, arg2, arg3 any) *gomock.Call { +func (mr *MockStateCallbackListenerMockRecorder) OnValidatorAdded(vID, nodeID, startTime, isActive any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorAdded", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorAdded), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorAdded", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorAdded), vID, nodeID, startTime, isActive) } // OnValidatorRemoved mocks base method. -func (m *MockStateCallbackListener) OnValidatorRemoved(arg0 ids.ID, arg1 ids.NodeID) { +func (m *MockStateCallbackListener) OnValidatorRemoved(vID ids.ID, nodeID ids.NodeID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "OnValidatorRemoved", arg0, arg1) + m.ctrl.Call(m, "OnValidatorRemoved", vID, nodeID) } // OnValidatorRemoved indicates an expected call of OnValidatorRemoved. -func (mr *MockStateCallbackListenerMockRecorder) OnValidatorRemoved(arg0, arg1 any) *gomock.Call { +func (mr *MockStateCallbackListenerMockRecorder) OnValidatorRemoved(vID, nodeID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorRemoved", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorRemoved), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorRemoved", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorRemoved), vID, nodeID) } // OnValidatorStatusUpdated mocks base method. -func (m *MockStateCallbackListener) OnValidatorStatusUpdated(arg0 ids.ID, arg1 ids.NodeID, arg2 bool) { +func (m *MockStateCallbackListener) OnValidatorStatusUpdated(vID ids.ID, nodeID ids.NodeID, isActive bool) { m.ctrl.T.Helper() - m.ctrl.Call(m, "OnValidatorStatusUpdated", arg0, arg1, arg2) + m.ctrl.Call(m, "OnValidatorStatusUpdated", vID, nodeID, isActive) } // OnValidatorStatusUpdated indicates an expected call of OnValidatorStatusUpdated. -func (mr *MockStateCallbackListenerMockRecorder) OnValidatorStatusUpdated(arg0, arg1, arg2 any) *gomock.Call { +func (mr *MockStateCallbackListenerMockRecorder) OnValidatorStatusUpdated(vID, nodeID, isActive any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorStatusUpdated", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorStatusUpdated), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnValidatorStatusUpdated", reflect.TypeOf((*MockStateCallbackListener)(nil).OnValidatorStatusUpdated), vID, nodeID, isActive) } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index edf3228f88..e0e47b6923 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -38,6 +38,7 @@ import ( "github.com/ava-labs/subnet-evm/node" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/peer" + "github.com/ava-labs/subnet-evm/plugin/evm/config" "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/plugin/evm/validators" "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" @@ -182,7 +183,7 @@ type VM struct { // with an efficient caching layer. *chain.State - config Config + config config.Config networkID uint64 genesisHash common.Hash @@ -277,7 +278,7 @@ func (vm *VM) Initialize( fxs []*commonEng.Fx, appSender commonEng.AppSender, ) error { - vm.config.SetDefaults() + vm.config.SetDefaults(defaultTxPoolConfig) if len(configBytes) > 0 { if err := json.Unmarshal(configBytes, &vm.config); err != nil { return fmt.Errorf("failed to unmarshal config %s: %w", string(configBytes), err) @@ -578,7 +579,7 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash, ethConfig ethconfig. &vm.ethConfig, &EthPushGossiper{vm: vm}, vm.chaindb, - vm.config.EthBackendSettings(), + eth.Settings{MaxBlocksPerRequest: vm.config.MaxBlocksPerRequest}, lastAcceptedHash, dummy.NewFakerWithClock(&vm.clock), &vm.clock, @@ -791,6 +792,11 @@ func (vm *VM) onNormalOperationsStarted() error { vm.builder.awaitSubmittedTxs() vm.Network.SetGossipHandler(NewGossipHandler(vm, gossipStats)) + var p2pValidators p2p.ValidatorSet = &validatorSet{} + if vm.config.PullGossipFrequency.Duration > 0 { + p2pValidators = vm.p2pValidators + } + if vm.ethTxGossipHandler == nil { vm.ethTxGossipHandler = newTxGossipHandler[*GossipEthTx]( vm.ctx.Log, @@ -800,7 +806,7 @@ func (vm *VM) onNormalOperationsStarted() error { txGossipTargetMessageSize, txGossipThrottlingPeriod, txGossipThrottlingLimit, - vm.p2pValidators, + p2pValidators, ) } @@ -825,15 +831,20 @@ func (vm *VM) onNormalOperationsStarted() error { } } - vm.shutdownWg.Add(2) - go func() { - gossip.Every(ctx, vm.ctx.Log, ethTxPushGossiper, vm.config.PushGossipFrequency.Duration) - vm.shutdownWg.Done() - }() - go func() { - gossip.Every(ctx, vm.ctx.Log, vm.ethTxPullGossiper, vm.config.PullGossipFrequency.Duration) - vm.shutdownWg.Done() - }() + if vm.config.PushGossipFrequency.Duration > 0 { + vm.shutdownWg.Add(1) + go func() { + gossip.Every(ctx, vm.ctx.Log, ethTxPushGossiper, vm.config.PushGossipFrequency.Duration) + vm.shutdownWg.Done() + }() + } + if vm.config.PullGossipFrequency.Duration > 0 { + vm.shutdownWg.Add(1) + go func() { + gossip.Every(ctx, vm.ctx.Log, vm.ethTxPullGossiper, vm.config.PullGossipFrequency.Duration) + vm.shutdownWg.Done() + }() + } return nil } diff --git a/plugin/evm/vm_database.go b/plugin/evm/vm_database.go index 01385ab721..d2f1271fd0 100644 --- a/plugin/evm/vm_database.go +++ b/plugin/evm/vm_database.go @@ -10,12 +10,13 @@ import ( "path/filepath" "time" + "github.com/ava-labs/avalanchego/config/node" avalanchedatabase "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" - "github.com/ava-labs/avalanchego/node" avalancheconstants "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/plugin/evm/config" "github.com/ava-labs/subnet-evm/plugin/evm/database" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -110,7 +111,7 @@ func (vm *VM) useStandaloneDatabase(acceptedDB avalanchedatabase.Database) (bool // getDatabaseConfig returns the database configuration for the chain // to be used by separate, standalone database. -func getDatabaseConfig(config Config, chainDataDir string) (node.DatabaseConfig, error) { +func getDatabaseConfig(config config.Config, chainDataDir string) (node.DatabaseConfig, error) { var ( configBytes []byte err error diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index cb06575af6..963b2b75ef 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -47,6 +47,7 @@ import ( "github.com/ava-labs/subnet-evm/eth" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/plugin/evm/config" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" @@ -218,8 +219,8 @@ func TestVMConfigDefaults(t *testing.T) { configJSON := fmt.Sprintf(`{"rpc-tx-fee-cap": %g,"eth-apis": %s}`, txFeeCap, fmt.Sprintf("[%q]", enabledEthAPIs[0])) _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") - var vmConfig Config - vmConfig.SetDefaults() + var vmConfig config.Config + vmConfig.SetDefaults(defaultTxPoolConfig) vmConfig.RPCTxFeeCap = txFeeCap vmConfig.EnabledEthAPIs = enabledEthAPIs require.Equal(t, vmConfig, vm.config, "VM Config should match default with overrides") @@ -230,8 +231,8 @@ func TestVMNilConfig(t *testing.T) { _, vm, _, _ := GenesisVM(t, false, "", "", "") // VM Config should match defaults if no config is passed in - var vmConfig Config - vmConfig.SetDefaults() + var vmConfig config.Config + vmConfig.SetDefaults(defaultTxPoolConfig) require.Equal(t, vmConfig, vm.config, "VM Config should match default config") require.NoError(t, vm.Shutdown(context.Background())) } @@ -2612,8 +2613,8 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { } etherBase := common.HexToAddress("0x0123456789") - c := Config{} - c.SetDefaults() + c := config.Config{} + c.SetDefaults(defaultTxPoolConfig) c.FeeRecipient = etherBase.String() configJSON, err := json.Marshal(c) if err != nil { @@ -2672,8 +2673,8 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { require.NoError(t, err) etherBase := common.HexToAddress("0x0123456789") // give custom ether base - c := Config{} - c.SetDefaults() + c := config.Config{} + c.SetDefaults(defaultTxPoolConfig) c.FeeRecipient = etherBase.String() configJSON, err := json.Marshal(c) require.NoError(t, err) @@ -2813,8 +2814,8 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) etherBase := common.HexToAddress("0x0123456789") // give custom ether base - c := Config{} - c.SetDefaults() + c := config.Config{} + c.SetDefaults(defaultTxPoolConfig) c.FeeRecipient = etherBase.String() configJSON, err := json.Marshal(c) require.NoError(t, err) diff --git a/precompile/contract/mocks.go b/precompile/contract/mocks.go index c82db74e3f..a68e65aeb6 100644 --- a/precompile/contract/mocks.go +++ b/precompile/contract/mocks.go @@ -24,6 +24,7 @@ import ( type MockBlockContext struct { ctrl *gomock.Controller recorder *MockBlockContextMockRecorder + isgomock struct{} } // MockBlockContextMockRecorder is the mock recorder for MockBlockContext. @@ -44,17 +45,17 @@ func (m *MockBlockContext) EXPECT() *MockBlockContextMockRecorder { } // GetPredicateResults mocks base method. -func (m *MockBlockContext) GetPredicateResults(arg0 common.Hash, arg1 common.Address) []byte { +func (m *MockBlockContext) GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPredicateResults", arg0, arg1) + ret := m.ctrl.Call(m, "GetPredicateResults", txHash, precompileAddress) ret0, _ := ret[0].([]byte) return ret0 } // GetPredicateResults indicates an expected call of GetPredicateResults. -func (mr *MockBlockContextMockRecorder) GetPredicateResults(arg0, arg1 any) *gomock.Call { +func (mr *MockBlockContextMockRecorder) GetPredicateResults(txHash, precompileAddress any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateResults", reflect.TypeOf((*MockBlockContext)(nil).GetPredicateResults), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateResults", reflect.TypeOf((*MockBlockContext)(nil).GetPredicateResults), txHash, precompileAddress) } // Number mocks base method. @@ -89,6 +90,7 @@ func (mr *MockBlockContextMockRecorder) Timestamp() *gomock.Call { type MockAccessibleState struct { ctrl *gomock.Controller recorder *MockAccessibleStateMockRecorder + isgomock struct{} } // MockAccessibleStateMockRecorder is the mock recorder for MockAccessibleState. @@ -168,6 +170,7 @@ func (mr *MockAccessibleStateMockRecorder) GetStateDB() *gomock.Call { type MockStateDB struct { ctrl *gomock.Controller recorder *MockStateDBMockRecorder + isgomock struct{} } // MockStateDBMockRecorder is the mock recorder for MockStateDB. @@ -200,15 +203,15 @@ func (mr *MockStateDBMockRecorder) AddBalance(arg0, arg1 any) *gomock.Call { } // AddLog mocks base method. -func (m *MockStateDB) AddLog(arg0 common.Address, arg1 []common.Hash, arg2 []byte, arg3 uint64) { +func (m *MockStateDB) AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) { m.ctrl.T.Helper() - m.ctrl.Call(m, "AddLog", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "AddLog", addr, topics, data, blockNumber) } // AddLog indicates an expected call of AddLog. -func (mr *MockStateDBMockRecorder) AddLog(arg0, arg1, arg2, arg3 any) *gomock.Call { +func (mr *MockStateDBMockRecorder) AddLog(addr, topics, data, blockNumber any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLog", reflect.TypeOf((*MockStateDB)(nil).AddLog), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLog", reflect.TypeOf((*MockStateDB)(nil).AddLog), addr, topics, data, blockNumber) } // CreateAccount mocks base method. @@ -281,18 +284,18 @@ func (mr *MockStateDBMockRecorder) GetNonce(arg0 any) *gomock.Call { } // GetPredicateStorageSlots mocks base method. -func (m *MockStateDB) GetPredicateStorageSlots(arg0 common.Address, arg1 int) ([]byte, bool) { +func (m *MockStateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPredicateStorageSlots", arg0, arg1) + ret := m.ctrl.Call(m, "GetPredicateStorageSlots", address, index) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(bool) return ret0, ret1 } // GetPredicateStorageSlots indicates an expected call of GetPredicateStorageSlots. -func (mr *MockStateDBMockRecorder) GetPredicateStorageSlots(arg0, arg1 any) *gomock.Call { +func (mr *MockStateDBMockRecorder) GetPredicateStorageSlots(address, index any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).GetPredicateStorageSlots), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).GetPredicateStorageSlots), address, index) } // GetState mocks base method. @@ -348,15 +351,15 @@ func (mr *MockStateDBMockRecorder) SetNonce(arg0, arg1 any) *gomock.Call { } // SetPredicateStorageSlots mocks base method. -func (m *MockStateDB) SetPredicateStorageSlots(arg0 common.Address, arg1 [][]byte) { +func (m *MockStateDB) SetPredicateStorageSlots(address common.Address, predicates [][]byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetPredicateStorageSlots", arg0, arg1) + m.ctrl.Call(m, "SetPredicateStorageSlots", address, predicates) } // SetPredicateStorageSlots indicates an expected call of SetPredicateStorageSlots. -func (mr *MockStateDBMockRecorder) SetPredicateStorageSlots(arg0, arg1 any) *gomock.Call { +func (mr *MockStateDBMockRecorder) SetPredicateStorageSlots(address, predicates any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).SetPredicateStorageSlots), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).SetPredicateStorageSlots), address, predicates) } // SetState mocks base method. diff --git a/precompile/precompileconfig/mocks.go b/precompile/precompileconfig/mocks.go index 17ef64a708..43b49b588c 100644 --- a/precompile/precompileconfig/mocks.go +++ b/precompile/precompileconfig/mocks.go @@ -21,6 +21,7 @@ import ( type MockPredicater struct { ctrl *gomock.Controller recorder *MockPredicaterMockRecorder + isgomock struct{} } // MockPredicaterMockRecorder is the mock recorder for MockPredicater. @@ -41,38 +42,39 @@ func (m *MockPredicater) EXPECT() *MockPredicaterMockRecorder { } // PredicateGas mocks base method. -func (m *MockPredicater) PredicateGas(arg0 []byte) (uint64, error) { +func (m *MockPredicater) PredicateGas(predicateBytes []byte) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PredicateGas", arg0) + ret := m.ctrl.Call(m, "PredicateGas", predicateBytes) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // PredicateGas indicates an expected call of PredicateGas. -func (mr *MockPredicaterMockRecorder) PredicateGas(arg0 any) *gomock.Call { +func (mr *MockPredicaterMockRecorder) PredicateGas(predicateBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PredicateGas", reflect.TypeOf((*MockPredicater)(nil).PredicateGas), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PredicateGas", reflect.TypeOf((*MockPredicater)(nil).PredicateGas), predicateBytes) } // VerifyPredicate mocks base method. -func (m *MockPredicater) VerifyPredicate(arg0 *PredicateContext, arg1 []byte) error { +func (m *MockPredicater) VerifyPredicate(predicateContext *PredicateContext, predicateBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyPredicate", arg0, arg1) + ret := m.ctrl.Call(m, "VerifyPredicate", predicateContext, predicateBytes) ret0, _ := ret[0].(error) return ret0 } // VerifyPredicate indicates an expected call of VerifyPredicate. -func (mr *MockPredicaterMockRecorder) VerifyPredicate(arg0, arg1 any) *gomock.Call { +func (mr *MockPredicaterMockRecorder) VerifyPredicate(predicateContext, predicateBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyPredicate", reflect.TypeOf((*MockPredicater)(nil).VerifyPredicate), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyPredicate", reflect.TypeOf((*MockPredicater)(nil).VerifyPredicate), predicateContext, predicateBytes) } // MockConfig is a mock of Config interface. type MockConfig struct { ctrl *gomock.Controller recorder *MockConfigMockRecorder + isgomock struct{} } // MockConfigMockRecorder is the mock recorder for MockConfig. @@ -166,6 +168,7 @@ func (mr *MockConfigMockRecorder) Verify(arg0 any) *gomock.Call { type MockChainConfig struct { ctrl *gomock.Controller recorder *MockChainConfigMockRecorder + isgomock struct{} } // MockChainConfigMockRecorder is the mock recorder for MockChainConfig. @@ -214,23 +217,24 @@ func (mr *MockChainConfigMockRecorder) GetFeeConfig() *gomock.Call { } // IsDurango mocks base method. -func (m *MockChainConfig) IsDurango(arg0 uint64) bool { +func (m *MockChainConfig) IsDurango(time uint64) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsDurango", arg0) + ret := m.ctrl.Call(m, "IsDurango", time) ret0, _ := ret[0].(bool) return ret0 } // IsDurango indicates an expected call of IsDurango. -func (mr *MockChainConfigMockRecorder) IsDurango(arg0 any) *gomock.Call { +func (mr *MockChainConfigMockRecorder) IsDurango(time any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDurango", reflect.TypeOf((*MockChainConfig)(nil).IsDurango), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDurango", reflect.TypeOf((*MockChainConfig)(nil).IsDurango), time) } // MockAccepter is a mock of Accepter interface. type MockAccepter struct { ctrl *gomock.Controller recorder *MockAccepterMockRecorder + isgomock struct{} } // MockAccepterMockRecorder is the mock recorder for MockAccepter. @@ -251,15 +255,15 @@ func (m *MockAccepter) EXPECT() *MockAccepterMockRecorder { } // Accept mocks base method. -func (m *MockAccepter) Accept(arg0 *AcceptContext, arg1 common.Hash, arg2 uint64, arg3 common.Hash, arg4 int, arg5 []common.Hash, arg6 []byte) error { +func (m *MockAccepter) Accept(acceptCtx *AcceptContext, blockHash common.Hash, blockNumber uint64, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Accept", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret := m.ctrl.Call(m, "Accept", acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData) ret0, _ := ret[0].(error) return ret0 } // Accept indicates an expected call of Accept. -func (mr *MockAccepterMockRecorder) Accept(arg0, arg1, arg2, arg3, arg4, arg5, arg6 any) *gomock.Call { +func (mr *MockAccepterMockRecorder) Accept(acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockAccepter)(nil).Accept), arg0, arg1, arg2, arg3, arg4, arg5, arg6) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockAccepter)(nil).Accept), acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData) } diff --git a/rpc/http.go b/rpc/http.go index b8670a9df8..8b18ed3895 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -250,7 +250,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if _, err := buf.ReadFrom(resp.Body); err == nil { body = buf.Bytes() } - + resp.Body.Close() return nil, HTTPError{ Status: resp.Status, StatusCode: resp.StatusCode, diff --git a/scripts/build_antithesis_images.sh b/scripts/build_antithesis_images.sh index b0764d84b9..13bad83552 100755 --- a/scripts/build_antithesis_images.sh +++ b/scripts/build_antithesis_images.sh @@ -47,7 +47,7 @@ source "${AVALANCHEGO_CLONE_PATH}"/scripts/lib_build_antithesis_images.sh build_antithesis_builder_image "${GO_VERSION}" "antithesis-subnet-evm-builder:${IMAGE_TAG}" "${AVALANCHEGO_CLONE_PATH}" "${SUBNET_EVM_PATH}" # Ensure avalanchego and subnet-evm binaries are available to create an initial db state that includes subnets. -"${AVALANCHEGO_CLONE_PATH}"/scripts/build.sh +pushd "${AVALANCHEGO_CLONE_PATH}" && ./scripts/build.sh && popd "${SUBNET_EVM_PATH}"/scripts/build.sh echo "Generating compose configuration" diff --git a/scripts/versions.sh b/scripts/versions.sh index 559289b412..0b985012de 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.1'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'54d8b06b'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} diff --git a/warp/aggregator/mock_signature_getter.go b/warp/aggregator/mock_signature_getter.go index dcc88564b5..c42f858ae8 100644 --- a/warp/aggregator/mock_signature_getter.go +++ b/warp/aggregator/mock_signature_getter.go @@ -23,6 +23,7 @@ import ( type MockSignatureGetter struct { ctrl *gomock.Controller recorder *MockSignatureGetterMockRecorder + isgomock struct{} } // MockSignatureGetterMockRecorder is the mock recorder for MockSignatureGetter. From 77661b1125c473ef27db221046b39cee97a0bdd9 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:15:55 -0500 Subject: [PATCH 35/57] Remove legacy gossip (#1435) * Began #1428 by removing legacy gossip handler from network.go and its test file. * Completed all recommended removals in #1428. Did not address any other changes in the original PR outside the scope of this issue * Skip registration in Codec for removed EthTxsGossip type to ensure that numbering is consistent * Fixed small RELEASES.md error incorrectly denoting version * Apply suggestions from code review Nit - typo, reduce git diffs Co-authored-by: Quentin McGaw Signed-off-by: Austin Larson <78000745+alarso16@users.noreply.github.com> * Changed test codec version name for clarity * Removed testCodecVersion due to later comparison with message.Version - use this for tests instead --------- Signed-off-by: Austin Larson <78000745+alarso16@users.noreply.github.com> Co-authored-by: Ceyhun Onur Co-authored-by: Quentin McGaw --- RELEASES.md | 1 + peer/network.go | 21 +-------- peer/network_test.go | 39 +++++---------- plugin/evm/gossip_stats.go | 46 ------------------ plugin/evm/handler.go | 76 ------------------------------ plugin/evm/message/codec.go | 6 +-- plugin/evm/message/handler.go | 15 ------ plugin/evm/message/handler_test.go | 41 ---------------- plugin/evm/message/message.go | 65 ------------------------- plugin/evm/message/message_test.go | 55 --------------------- plugin/evm/vm.go | 2 - 11 files changed, 18 insertions(+), 349 deletions(-) delete mode 100644 plugin/evm/gossip_stats.go delete mode 100644 plugin/evm/handler.go delete mode 100644 plugin/evm/message/handler_test.go delete mode 100644 plugin/evm/message/message.go delete mode 100644 plugin/evm/message/message_test.go diff --git a/RELEASES.md b/RELEASES.md index 29febf1347..b3e1654d4b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,7 @@ ## Pending Release * Refactored trie_prefetcher.go to be structurally similar to upstream. +* Remove legacy gossip handler and metrics ## [v0.7.0](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.0) diff --git a/peer/network.go b/peer/network.go index 7494db4656..21cfce1cc0 100644 --- a/peer/network.go +++ b/peer/network.go @@ -56,9 +56,6 @@ type Network interface { // by calling OnPeerConnected for each peer Shutdown() - // SetGossipHandler sets the provided gossip handler as the gossip handler - SetGossipHandler(handler message.GossipHandler) - // SetRequestHandler sets the provided request handler as the request handler SetRequestHandler(handler message.RequestHandler) @@ -87,7 +84,6 @@ type network struct { appSender common.AppSender // avalanchego AppSender for sending messages codec codec.Manager // Codec used for parsing messages appRequestHandler message.RequestHandler // maps request type => handler - gossipHandler message.GossipHandler // maps gossip type => handler peers *peerTracker // tracking of peers & bandwidth appStats stats.RequestHandlerStats // Provide request handler metrics @@ -110,7 +106,6 @@ func NewNetwork(p2pNetwork *p2p.Network, appSender common.AppSender, codec codec outstandingRequestHandlers: make(map[uint32]message.ResponseHandler), activeAppRequests: semaphore.NewWeighted(maxActiveAppRequests), p2pNetwork: p2pNetwork, - gossipHandler: message.NoopMempoolGossipHandler{}, appRequestHandler: message.NoopRequestHandler{}, peers: NewPeerTracker(), appStats: stats.NewRequestHandlerStats(), @@ -345,14 +340,7 @@ func (n *network) markRequestFulfilled(requestID uint32) (message.ResponseHandle // from a peer. An error returned by this function is treated as fatal by the // engine. func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) error { - var gossipMsg message.GossipMessage - if _, err := n.codec.Unmarshal(gossipBytes, &gossipMsg); err != nil { - log.Debug("forwarding AppGossip to SDK network", "nodeID", nodeID, "gossipLen", len(gossipBytes), "err", err) - return n.p2pNetwork.AppGossip(ctx, nodeID, gossipBytes) - } - - log.Debug("processing AppGossip from node", "nodeID", nodeID, "msg", gossipMsg) - return gossipMsg.Handle(n.gossipHandler, nodeID) + return n.p2pNetwork.AppGossip(ctx, nodeID, gossipBytes) } // Connected adds the given nodeID to the peer list so that it can receive messages @@ -407,13 +395,6 @@ func (n *network) Shutdown() { n.closed.Set(true) // mark network as closed } -func (n *network) SetGossipHandler(handler message.GossipHandler) { - n.lock.Lock() - defer n.lock.Unlock() - - n.gossipHandler = handler -} - func (n *network) SetRequestHandler(handler message.RequestHandler) { n.lock.Lock() defer n.lock.Unlock() diff --git a/peer/network_test.go b/peer/network_test.go index d93439b22b..d8df3267c7 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -46,9 +46,7 @@ var ( _ message.RequestHandler = &HelloGreetingRequestHandler{} _ message.RequestHandler = &testRequestHandler{} - _ common.AppSender = testAppSender{} - _ message.GossipMessage = HelloGossip{} - _ message.GossipHandler = &testGossipHandler{} + _ common.AppSender = testAppSender{} _ p2p.Handler = &testSDKHandler{} ) @@ -503,7 +501,6 @@ func TestHandleInvalidMessages(t *testing.T) { p2pNetwork, err := p2p.NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") require.NoError(t, err) clientNetwork := NewNetwork(p2pNetwork, sender, codecManager, ids.EmptyNodeID, 1) - clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{}) assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultPeerVersion)) @@ -511,7 +508,8 @@ func TestHandleInvalidMessages(t *testing.T) { defer clientNetwork.Shutdown() // Ensure a valid gossip message sent as any App specific message type does not trigger a fatal error - gossipMsg, err := buildGossip(codecManager, HelloGossip{Msg: "hello there!"}) + marshaller := helloGossipMarshaller{codec: codecManager} + gossipMsg, err := marshaller.MarshalGossip(&HelloGossip{Msg: "hello there!"}) assert.NoError(t, err) // Ensure a valid request message sent as any App specific message type does not trigger a fatal error @@ -552,7 +550,6 @@ func TestNetworkPropagatesRequestHandlerError(t *testing.T) { p2pNetwork, err := p2p.NewNetwork(logging.NoLog{}, nil, prometheus.NewRegistry(), "") require.NoError(t, err) clientNetwork := NewNetwork(p2pNetwork, sender, codecManager, ids.EmptyNodeID, 1) - clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{err: errors.New("fail")}) // Return an error from the request handler assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultPeerVersion)) @@ -625,10 +622,6 @@ func marshalStruct(codec codec.Manager, obj interface{}) ([]byte, error) { return codec.Marshal(message.Version, &obj) } -func buildGossip(codec codec.Manager, msg message.GossipMessage) ([]byte, error) { - return codec.Marshal(message.Version, &msg) -} - type testAppSender struct { sendAppRequestFn func(context.Context, set.Set[ids.NodeID], uint32, []byte) error sendAppResponseFn func(ids.NodeID, uint32, []byte) error @@ -719,28 +712,22 @@ type HelloGossip struct { Msg string `serialize:"true"` } -func (h HelloGossip) Handle(handler message.GossipHandler, nodeID ids.NodeID) error { - return handler.HandleEthTxs(nodeID, message.EthTxsGossip{}) +func (tx *HelloGossip) GossipID() ids.ID { + return ids.FromStringOrPanic(tx.Msg) } -func (h HelloGossip) String() string { - return fmt.Sprintf("HelloGossip(%s)", h.Msg) -} - -func (h HelloGossip) Bytes() []byte { - // no op - return nil +type helloGossipMarshaller struct { + codec codec.Manager } -type testGossipHandler struct { - received bool - nodeID ids.NodeID +func (g helloGossipMarshaller) MarshalGossip(tx *HelloGossip) ([]byte, error) { + return g.codec.Marshal(0, tx) } -func (t *testGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { - t.received = true - t.nodeID = nodeID - return nil +func (g helloGossipMarshaller) UnmarshalGossip(bytes []byte) (*HelloGossip, error) { + h := &HelloGossip{} + _, err := g.codec.Unmarshal(bytes, h) + return h, err } type testRequestHandler struct { diff --git a/plugin/evm/gossip_stats.go b/plugin/evm/gossip_stats.go deleted file mode 100644 index 3a6f552fcc..0000000000 --- a/plugin/evm/gossip_stats.go +++ /dev/null @@ -1,46 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import "github.com/ava-labs/subnet-evm/metrics" - -var _ GossipStats = &gossipStats{} - -// GossipStats contains methods for updating incoming and outgoing gossip stats. -type GossipStats interface { - IncEthTxsGossipReceived() - - // new vs. known txs received - IncEthTxsGossipReceivedError() - IncEthTxsGossipReceivedKnown() - IncEthTxsGossipReceivedNew() -} - -// gossipStats implements stats for incoming and outgoing gossip stats. -type gossipStats struct { - // messages - ethTxsGossipReceived metrics.Counter - - // new vs. known txs received - ethTxsGossipReceivedError metrics.Counter - ethTxsGossipReceivedKnown metrics.Counter - ethTxsGossipReceivedNew metrics.Counter -} - -func NewGossipStats() GossipStats { - return &gossipStats{ - ethTxsGossipReceived: metrics.GetOrRegisterCounter("gossip_eth_txs_received", nil), - ethTxsGossipReceivedError: metrics.GetOrRegisterCounter("gossip_eth_txs_received_error", nil), - ethTxsGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_eth_txs_received_known", nil), - ethTxsGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_eth_txs_received_new", nil), - } -} - -// incoming messages -func (g *gossipStats) IncEthTxsGossipReceived() { g.ethTxsGossipReceived.Inc(1) } - -// new vs. known txs received -func (g *gossipStats) IncEthTxsGossipReceivedError() { g.ethTxsGossipReceivedError.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedKnown() { g.ethTxsGossipReceivedKnown.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedNew() { g.ethTxsGossipReceivedNew.Inc(1) } diff --git a/plugin/evm/handler.go b/plugin/evm/handler.go deleted file mode 100644 index f01db79b04..0000000000 --- a/plugin/evm/handler.go +++ /dev/null @@ -1,76 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "github.com/ava-labs/avalanchego/ids" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - - "github.com/ava-labs/subnet-evm/core/txpool" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/plugin/evm/message" -) - -// GossipHandler handles incoming gossip messages -type GossipHandler struct { - vm *VM - txPool *txpool.TxPool - stats GossipStats -} - -func NewGossipHandler(vm *VM, stats GossipStats) *GossipHandler { - return &GossipHandler{ - vm: vm, - txPool: vm.txPool, - stats: stats, - } -} - -func (h *GossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { - log.Trace( - "AppGossip called with EthTxsGossip", - "peerID", nodeID, - "size(txs)", len(msg.Txs), - ) - - if len(msg.Txs) == 0 { - log.Trace( - "AppGossip received empty EthTxsGossip Message", - "peerID", nodeID, - ) - return nil - } - - // The maximum size of this encoded object is enforced by the codec. - txs := make([]*types.Transaction, 0) - if err := rlp.DecodeBytes(msg.Txs, &txs); err != nil { - log.Trace( - "AppGossip provided invalid txs", - "peerID", nodeID, - "err", err, - ) - return nil - } - h.stats.IncEthTxsGossipReceived() - errs := h.txPool.Add(txs, false, false) - for i, err := range errs { - if err != nil { - log.Trace( - "AppGossip failed to add to mempool", - "err", err, - "tx", txs[i].Hash(), - ) - if err == txpool.ErrAlreadyKnown { - h.stats.IncEthTxsGossipReceivedKnown() - } else { - h.stats.IncEthTxsGossipReceivedError() - } - continue - } - h.stats.IncEthTxsGossipReceivedNew() - } - return nil -} diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index 9ae2112b1a..64edbb81d6 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -23,11 +23,11 @@ func init() { Codec = codec.NewManager(maxMessageSize) c := linearcodec.NewDefault() + // Skip registration to keep registeredTypes unchanged after legacy gossip deprecation + c.SkipRegistrations(1) + errs := wrappers.Errs{} errs.Add( - // Gossip types - c.RegisterType(EthTxsGossip{}), - // Types for state sync frontier consensus c.RegisterType(SyncSummary{}), diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index d1e83d7883..da81a8b0f2 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -6,28 +6,13 @@ package message import ( "context" - "github.com/ethereum/go-ethereum/log" - "github.com/ava-labs/avalanchego/ids" ) var ( - _ GossipHandler = NoopMempoolGossipHandler{} _ RequestHandler = NoopRequestHandler{} ) -// GossipHandler handles incoming gossip messages -type GossipHandler interface { - HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error -} - -type NoopMempoolGossipHandler struct{} - -func (NoopMempoolGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error { - log.Debug("dropping unexpected EthTxsGossip message", "peerID", nodeID) - return nil -} - // RequestHandler interface handles incoming requests from peers // Must have methods in format of handleType(context.Context, ids.NodeID, uint32, request Type) error // so that the Request object of relevant Type can invoke its respective handle method diff --git a/plugin/evm/message/handler_test.go b/plugin/evm/message/handler_test.go deleted file mode 100644 index 8b87135ff5..0000000000 --- a/plugin/evm/message/handler_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "testing" - - "github.com/ava-labs/avalanchego/ids" - - "github.com/stretchr/testify/assert" -) - -type CounterHandler struct { - EthTxs int -} - -func (h *CounterHandler) HandleEthTxs(ids.NodeID, EthTxsGossip) error { - h.EthTxs++ - return nil -} - -func TestHandleEthTxs(t *testing.T) { - assert := assert.New(t) - - handler := CounterHandler{} - msg := EthTxsGossip{} - - err := msg.Handle(&handler, ids.EmptyNodeID) - assert.NoError(err) - assert.Equal(1, handler.EthTxs) -} - -func TestNoopHandler(t *testing.T) { - assert := assert.New(t) - - handler := NoopMempoolGossipHandler{} - - err := handler.HandleEthTxs(ids.EmptyNodeID, EthTxsGossip{}) - assert.NoError(err) -} diff --git a/plugin/evm/message/message.go b/plugin/evm/message/message.go deleted file mode 100644 index 35887911c9..0000000000 --- a/plugin/evm/message/message.go +++ /dev/null @@ -1,65 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "errors" - "fmt" - - "github.com/ava-labs/avalanchego/codec" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/units" -) - -const ( - // EthMsgSoftCapSize is the ideal size of encoded transaction bytes we send in - // any [EthTxsGossip] or [AtomicTxGossip] message. We do not limit inbound messages to - // this size, however. Max inbound message size is enforced by the codec - // (512KB). - EthMsgSoftCapSize = 64 * units.KiB -) - -var ( - _ GossipMessage = EthTxsGossip{} - - errUnexpectedCodecVersion = errors.New("unexpected codec version") -) - -type GossipMessage interface { - // types implementing GossipMessage should also implement fmt.Stringer for logging purposes. - fmt.Stringer - - // Handle this gossip message with the gossip handler. - Handle(handler GossipHandler, nodeID ids.NodeID) error -} - -type EthTxsGossip struct { - Txs []byte `serialize:"true"` -} - -func (msg EthTxsGossip) Handle(handler GossipHandler, nodeID ids.NodeID) error { - return handler.HandleEthTxs(nodeID, msg) -} - -func (msg EthTxsGossip) String() string { - return fmt.Sprintf("EthTxsGossip(Len=%d)", len(msg.Txs)) -} - -func ParseGossipMessage(codec codec.Manager, bytes []byte) (GossipMessage, error) { - var msg GossipMessage - version, err := codec.Unmarshal(bytes, &msg) - if err != nil { - return nil, err - } - if version != Version { - return nil, errUnexpectedCodecVersion - } - return msg, nil -} - -func BuildGossipMessage(codec codec.Manager, msg GossipMessage) ([]byte, error) { - bytes, err := codec.Marshal(Version, &msg) - return bytes, err -} diff --git a/plugin/evm/message/message_test.go b/plugin/evm/message/message_test.go deleted file mode 100644 index e1779d4b75..0000000000 --- a/plugin/evm/message/message_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// (c) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package message - -import ( - "encoding/base64" - "testing" - - "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/units" - - "github.com/stretchr/testify/assert" -) - -// TestMarshalEthTxs asserts that the structure or serialization logic hasn't changed, primarily to -// ensure compatibility with the network. -func TestMarshalEthTxs(t *testing.T) { - assert := assert.New(t) - - base64EthTxGossip := "AAAAAAAAAAAABGJsYWg=" - msg := []byte("blah") - builtMsg := EthTxsGossip{ - Txs: msg, - } - builtMsgBytes, err := BuildGossipMessage(Codec, builtMsg) - assert.NoError(err) - assert.Equal(base64EthTxGossip, base64.StdEncoding.EncodeToString(builtMsgBytes)) - - parsedMsgIntf, err := ParseGossipMessage(Codec, builtMsgBytes) - assert.NoError(err) - - parsedMsg, ok := parsedMsgIntf.(EthTxsGossip) - assert.True(ok) - - assert.Equal(msg, parsedMsg.Txs) -} - -func TestEthTxsTooLarge(t *testing.T) { - assert := assert.New(t) - - builtMsg := EthTxsGossip{ - Txs: utils.RandomBytes(maxMessageSize), - } - _, err := BuildGossipMessage(Codec, builtMsg) - assert.Error(err) -} - -func TestParseGibberish(t *testing.T) { - assert := assert.New(t) - - randomBytes := utils.RandomBytes(256 * units.KiB) - _, err := ParseGossipMessage(Codec, randomBytes) - assert.Error(err) -} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index e0e47b6923..9b73f4f02c 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -787,10 +787,8 @@ func (vm *VM) onNormalOperationsStarted() error { } // NOTE: gossip network must be initialized first otherwise ETH tx gossip will not work. - gossipStats := NewGossipStats() vm.builder = vm.NewBlockBuilder(vm.toEngine) vm.builder.awaitSubmittedTxs() - vm.Network.SetGossipHandler(NewGossipHandler(vm, gossipStats)) var p2pValidators p2p.ValidatorSet = &validatorSet{} if vm.config.PullGossipFrequency.Duration > 0 { From dc60ff71d3c925d52a50fe7d5d54a459159282b1 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 29 Jan 2025 16:18:28 -0500 Subject: [PATCH 36/57] Reduce magic numbers in dynamic fees (#1437) --- consensus/dummy/dynamic_fees.go | 11 +++++------ params/config_extra.go | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go index 566d72b301..289ac0907c 100644 --- a/consensus/dummy/dynamic_fees.go +++ b/consensus/dummy/dynamic_fees.go @@ -56,15 +56,14 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par // Add in the gas used by the parent block in the correct place // If the parent consumed gas within the rollup window, add the consumed // gas in. - expectedRollUp := params.RollupWindow - if roll < expectedRollUp { - slot := expectedRollUp - 1 - roll + if roll < params.RollupWindow { + slot := params.RollupWindow - 1 - roll start := slot * wrappers.LongLen updateLongWindow(newRollupWindow, start, parent.GasUsed) } // Calculate the amount of gas consumed within the rollup window. - totalGas := sumLongWindow(newRollupWindow, int(expectedRollUp)) + totalGas := sumLongWindow(newRollupWindow, params.RollupWindow) if totalGas == parentGasTarget { return newRollupWindow, baseFee, nil @@ -93,9 +92,9 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par // for the interval during which no blocks were produced. // We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds // that has elapsed between the parent and this block. - if roll > expectedRollUp { + if roll > params.RollupWindow { // Note: roll/params.RollupWindow must be greater than 1 since we've checked that roll > params.RollupWindow - baseFeeDelta = new(big.Int).Mul(baseFeeDelta, new(big.Int).SetUint64(roll/expectedRollUp)) + baseFeeDelta = new(big.Int).Mul(baseFeeDelta, new(big.Int).SetUint64(roll/params.RollupWindow)) } baseFee.Sub(baseFee, baseFeeDelta) } diff --git a/params/config_extra.go b/params/config_extra.go index b69f9553a5..c8f292d914 100644 --- a/params/config_extra.go +++ b/params/config_extra.go @@ -10,6 +10,7 @@ import ( "math/big" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" @@ -19,8 +20,8 @@ const ( maxJSONLen = 64 * 1024 * 1024 // 64MB // Consensus Params - RollupWindow uint64 = 10 - DynamicFeeExtraDataSize = 80 + RollupWindow = 10 // in seconds + DynamicFeeExtraDataSize = wrappers.LongLen * RollupWindow // For legacy tests MinGasPrice int64 = 225_000_000_000 From 2136604f81f6cf690726e6da1c47165a298b5047 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 30 Jan 2025 00:39:20 +0300 Subject: [PATCH 37/57] bump avalanchego to v1.12.2 (#1436) * bump avalanchego to v1.12.2 * remove unneeded var * fix antithesis images * add cross-compile setup to dockerfile * use build_env.sh in Dockerfile * export variables * remove build_env.sh from Dockerfile * remove comment * revert --- Dockerfile | 2 +- README.md | 15 +-------------- compatibility.json | 17 ++--------------- go.mod | 5 ++--- go.sum | 10 ++++------ scripts/build_antithesis_images.sh | 2 +- scripts/versions.sh | 2 +- tests/antithesis/gencomposeconfig/main.go | 5 +---- 8 files changed, 13 insertions(+), 45 deletions(-) diff --git a/Dockerfile b/Dockerfile index 047144d21a..c4e2f97757 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG AVALANCHEGO_NODE_IMAGE # ============= Compilation Stage ================ -FROM golang:1.22.8-bullseye AS builder +FROM --platform=$BUILDPLATFORM golang:1.22.8-bullseye AS builder WORKDIR /build diff --git a/README.md b/README.md index 9b22a32acc..5b1d910030 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,8 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and ### AvalancheGo Compatibility ```text -[v0.6.0] AvalancheGo@v1.11.0-v1.11.1 (Protocol Version: 33) -[v0.6.1] AvalancheGo@v1.11.0-v1.11.1 (Protocol Version: 33) -[v0.6.2] AvalancheGo@v1.11.2 (Protocol Version: 34) -[v0.6.3] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.4] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.5] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.6] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.7] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.8] AvalancheGo@v1.11.10 (Protocol Version: 36) -[v0.6.9] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) -[v0.6.10] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) -[v0.6.11] AvalancheGo@v1.11.11-v1.11.12 (Protocol Version: 37) -[v0.6.12] AvalancheGo@v1.11.13/v1.12.0 (Protocol Version: 38) [v0.7.0] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) -[v0.7.1] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) +[v0.7.1] AvalancheGo@v1.12.2 (Protocol Version: 39) ``` ## API diff --git a/compatibility.json b/compatibility.json index 435fda56ee..c3090f5746 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,19 +1,6 @@ { "rpcChainVMProtocolVersion": { - "v0.7.1": 38, - "v0.7.0": 38, - "v0.6.12": 38, - "v0.6.11": 37, - "v0.6.10": 37, - "v0.6.9": 37, - "v0.6.8": 36, - "v0.6.7": 35, - "v0.6.6": 35, - "v0.6.5": 35, - "v0.6.4": 35, - "v0.6.3": 35, - "v0.6.2": 34, - "v0.6.1": 33, - "v0.6.0": 33 + "v0.7.1": 39, + "v0.7.0": 38 } } \ No newline at end of file diff --git a/go.mod b/go.mod index 51f5f085c2..13197720b3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625 + github.com/ava-labs/avalanchego v1.12.2 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -57,7 +57,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35 // indirect + github.com/ava-labs/coreth v0.14.1-rc.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -121,7 +121,6 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect diff --git a/go.sum b/go.sum index 49e81af762..cb8666e5e8 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625 h1:sbmfwhpetCKI7Unzw9jJ+2HWLRFM7vq7th0pH2LclCQ= -github.com/ava-labs/avalanchego v1.12.2-0.20250116172728-54d8b06b8625/go.mod h1:oK/C7ZGo5cAEayBKBoawh2EpOo3E9gD1rpd9NAM0RkQ= -github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35 h1:qBNnMleaJ7yWjNiDdV7wIf/e/PxubB+Ww7Mfx4QN4p8= -github.com/ava-labs/coreth v0.14.1-0.20241230191223-351149733d35/go.mod h1:nvQqJem4MuE0pU93aqBPsaEZx9NnXT0lI8d6rrQS5uY= +github.com/ava-labs/avalanchego v1.12.2 h1:vZroUgB5xMMczDQnw9etDD1XhZsejFlKky+ZZv8wOKc= +github.com/ava-labs/avalanchego v1.12.2/go.mod h1:uEDLbAUPcGCfDWW680rVfysEofUe/jWte5qQk0j5hMs= +github.com/ava-labs/coreth v0.14.1-rc.1 h1:U72XlRm/fKyASmjThsWzfO/ZRvu1kaONFaX+KdJNxIc= +github.com/ava-labs/coreth v0.14.1-rc.1/go.mod h1:lxDSXLcrszMo0N/PVJzfZ//H+bRwXF/KQWtpEYgXZqM= 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= @@ -474,8 +474,6 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl 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= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/scripts/build_antithesis_images.sh b/scripts/build_antithesis_images.sh index 13bad83552..b195af6785 100755 --- a/scripts/build_antithesis_images.sh +++ b/scripts/build_antithesis_images.sh @@ -54,7 +54,7 @@ echo "Generating compose configuration" gen_antithesis_compose_config "${IMAGE_TAG}" "${SUBNET_EVM_PATH}/tests/antithesis/gencomposeconfig" \ "${SUBNET_EVM_PATH}/build/antithesis" \ "AVALANCHEGO_PATH=${AVALANCHEGO_CLONE_PATH}/build/avalanchego \ - AVALANCHEGO_PLUGIN_DIR=${DEFAULT_PLUGIN_DIR}" + AVAGO_PLUGIN_DIR=${DEFAULT_PLUGIN_DIR}" build_antithesis_images "${GO_VERSION}" "${IMAGE_PREFIX}" "antithesis-subnet-evm" "${IMAGE_TAG}" \ "${AVALANCHEGO_IMAGE_TAG}" "${SUBNET_EVM_PATH}/tests/antithesis/Dockerfile" \ diff --git a/scripts/versions.sh b/scripts/versions.sh index 0b985012de..2f0a46f660 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'54d8b06b'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.2'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} diff --git a/tests/antithesis/gencomposeconfig/main.go b/tests/antithesis/gencomposeconfig/main.go index 40cbf0037d..adc01df71f 100644 --- a/tests/antithesis/gencomposeconfig/main.go +++ b/tests/antithesis/gencomposeconfig/main.go @@ -32,10 +32,7 @@ func main() { utils.NewTmpnetSubnet("subnet-evm", genesisPath, utils.DefaultChainConfig, network.Nodes...), } - // Path to the plugin dir on subnet-evm node images that will be run by docker compose. - runtimePluginDir := "/avalanchego/build/plugins" - - if err := antithesis.GenerateComposeConfig(network, baseImageName, runtimePluginDir); err != nil { + if err := antithesis.GenerateComposeConfig(network, baseImageName); err != nil { log.Fatalf("failed to generate compose config: %v", err) } } From a1bfa0ed452426b2fef3f211ee02eb866d939cc7 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 30 Jan 2025 03:24:17 +0300 Subject: [PATCH 38/57] remove current state lock (#1438) * remove current state lock * revert formatting --- core/txpool/legacypool/legacypool.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 31e9fdd95e..11256937db 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -230,9 +230,6 @@ type LegacyPool struct { signer types.Signer mu sync.RWMutex - // [currentStateLock] is required to allow concurrent access to address nonces - // and balances during reorgs and gossip handling. - currentStateLock sync.Mutex // closed when the transaction pool is stopped. Any goroutine can listen // to this to be notified if it should shut down. generalShutdownChan chan struct{} @@ -688,9 +685,6 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { - pool.currentStateLock.Lock() - defer pool.currentStateLock.Unlock() - opts := &txpool.ValidationOptionsWithState{ State: pool.currentState, Rules: pool.chainconfig.Rules( @@ -1503,9 +1497,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { return } pool.currentHead.Store(newHead) - pool.currentStateLock.Lock() pool.currentState = statedb - pool.currentStateLock.Unlock() pool.pendingNonces = newNoncer(statedb) // when we reset txPool we should explicitly check if fee struct for min base fee has changed @@ -1529,9 +1521,6 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { - pool.currentStateLock.Lock() - defer pool.currentStateLock.Unlock() - // Track the promoted transactions to broadcast them at once var promoted []*types.Transaction @@ -1738,9 +1727,6 @@ func (pool *LegacyPool) truncateQueue() { // is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful // to trigger a re-heap is this function func (pool *LegacyPool) demoteUnexecutables() { - pool.currentStateLock.Lock() - defer pool.currentStateLock.Unlock() - // Iterate over all accounts and demote any non-executable transactions gasLimit := pool.currentHead.Load().GasLimit for addr, list := range pool.pending { From b049381bf3f50eb8687ca131c5237abdc0d2aa29 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 4 Feb 2025 09:56:28 -0800 Subject: [PATCH 39/57] pin goreleaser v2.5.1 (#1439) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5af8b527c2..19cfb30a91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: uses: goreleaser/goreleaser-action@v3 with: distribution: goreleaser - version: latest + version: v2.5.1 args: release --clean workdir: ./subnet-evm/ env: From a6a3d338809940db47ffc94d418b5abb61427f1e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 24 Feb 2025 06:41:24 -0800 Subject: [PATCH 40/57] chore: Bump avalanchego to master (#1443) Co-authored-by: Martin HS Co-authored-by: Marius van der Wijden Co-authored-by: Quentin McGaw Co-authored-by: Richard Pringle Co-authored-by: Ceyhun Onur Co-authored-by: Tsachi Herman <24438559+tsachiherman@users.noreply.github.com> --- .github/workflows/bench.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 4 +- .golangci.yml | 16 +++-- Dockerfile | 4 +- README.md | 2 +- RELEASES.md | 3 +- accounts/abi/abi_test.go | 1 - accounts/abi/bind/bind.go | 2 +- accounts/abi/bind/bind_test.go | 2 +- .../precompilebind/precompile_bind_test.go | 2 +- accounts/abi/event_test.go | 1 - accounts/abi/pack_test.go | 1 - accounts/abi/reflect_test.go | 1 - accounts/abi/topics_test.go | 3 - accounts/abi/unpack_test.go | 3 +- cmd/evm/t8n_test.go | 4 +- cmd/simulator/load/loader.go | 3 - core/blockchain_test.go | 1 - core/predicate_check_test.go | 1 - core/rawdb/accessors_chain_test.go | 6 +- core/types/transaction_test.go | 2 - eth/filters/api.go | 1 - .../internal/tracetest/calltrace_test.go | 2 - .../internal/tracetest/flat_calltrace_test.go | 1 - .../internal/tracetest/prestate_test.go | 1 - eth/tracers/logger/logger.go | 2 +- go.mod | 6 +- go.sum | 8 +-- internal/flags/flags_test.go | 3 +- metrics/json_test.go | 2 +- plugin/evm/vm_warp_test.go | 7 ++- plugin/runner/runner.go | 2 +- precompile/contracts/warp/config.go | 38 +++++++----- precompile/contracts/warp/contract_test.go | 5 +- precompile/contracts/warp/predicate_test.go | 3 +- .../warp/signature_verification_test.go | 59 +++++++++++-------- rpc/client_test.go | 1 - rpc/types_test.go | 1 - scripts/lint.sh | 7 +-- scripts/versions.sh | 2 +- utils/snow.go | 4 +- warp/aggregator/aggregator_test.go | 5 +- warp/backend_test.go | 12 ++-- warp/handlers/signature_request_test.go | 5 +- warp/service.go | 10 ++-- warp/verifier_backend_test.go | 8 +-- 47 files changed, 133 insertions(+), 128 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 71f2fc2135..c6710de7e2 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: "~1.22.8" + go-version: "~1.23.6" - run: go mod download shell: bash - run: ./scripts/build_bench_precompiles.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19cfb30a91..f0f5a7a888 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "~1.22.8" + go-version: "~1.23.6" - name: Set up arm64 cross compiler run: | sudo apt-get -y update diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dfd3f0358c..479877a8b9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: pull_request: env: - min_go_version: "~1.22.8" + min_go_version: "~1.23.6" jobs: lint_test: @@ -25,7 +25,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.56 + version: v1.63 working-directory: . args: --timeout 10m skip-pkg-cache: true diff --git a/.golangci.yml b/.golangci.yml index c6c8d5748a..ace8a22dad 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,11 +3,6 @@ run: timeout: 10m tests: true - # default is true. Enables skipping of directories: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs-use-default: true - # Include non-test files tagged as test-only. - # Context: https://github.com/ava-labs/avalanchego/pull/3173 linters: disable-all: true @@ -18,8 +13,19 @@ linters: - ineffassign - misspell - unconvert + - typecheck - unused + # - staticcheck + - bidichk + - durationcheck + - copyloopvar - whitespace + # - revive # only certain checks enabled + - durationcheck + - gocheckcompilerdirectives + - reassign + - mirror + - tenv linters-settings: gofmt: diff --git a/Dockerfile b/Dockerfile index c4e2f97757..7312954822 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG AVALANCHEGO_NODE_IMAGE # ============= Compilation Stage ================ -FROM --platform=$BUILDPLATFORM golang:1.22.8-bullseye AS builder +FROM --platform=$BUILDPLATFORM golang:1.23.6-bullseye AS builder WORKDIR /build @@ -14,7 +14,7 @@ WORKDIR /build COPY go.mod go.sum avalanchego* ./ # Download avalanche dependencies using go mod -RUN go mod download && go mod tidy -compat=1.22 +RUN go mod download && go mod tidy -compat=1.23 # Copy the code into the container COPY . . diff --git a/README.md b/README.md index 5b1d910030..ec8c8dd99d 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ To support these changes, there have been a number of changes to the SubnetEVM b ### Clone Subnet-evm -First install Go 1.22.8 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. +First install Go 1.23.6 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/code) for details. You can verify by running `echo $GOPATH`. diff --git a/RELEASES.md b/RELEASES.md index b3e1654d4b..b7eb6ea301 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,8 @@ # Release Notes ## Pending Release - +* Bump golang version to v1.23.6 +* Bump golangci-lint to v1.63 and add linters * Refactored trie_prefetcher.go to be structurally similar to upstream. * Remove legacy gossip handler and metrics diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 68052e639a..023ff5cc4e 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -1210,7 +1210,6 @@ func TestUnpackRevert(t *testing.T) { {"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil}, } for index, c := range cases { - index, c := index, c t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { t.Parallel() got, err := UnpackRevert(common.Hex2Bytes(c.input)) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index a4dd0963d7..eb82a9fb42 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -266,7 +266,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Parse library references. for pattern, name := range libs { - matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) + matched, err := regexp.MatchString("__\\$"+pattern+"\\$__", contracts[types[i]].InputBin) if err != nil { log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 9179e369c4..9adc39f62c 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2179,7 +2179,7 @@ func golangBindings(t *testing.T, overload bool) { if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } - tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.22") + tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.23") tidier.Dir = pkg if out, err := tidier.CombinedOutput(); err != nil { t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go index f12170dede..2158160d3a 100644 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -695,7 +695,7 @@ func TestPrecompileBind(t *testing.T) { if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } - tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.22") + tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.23") tidier.Dir = pkg if out, err := tidier.CombinedOutput(); err != nil { t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index a967decd52..4a254e9950 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -341,7 +341,6 @@ func TestEventTupleUnpack(t *testing.T) { for _, tc := range testCases { assert := assert.New(t) - tc := tc t.Run(tc.name, func(t *testing.T) { err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert) if tc.error == "" { diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 47d4931698..b13de922a5 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -44,7 +44,6 @@ import ( func TestPack(t *testing.T) { t.Parallel() for i, test := range packUnpackTests { - i, test := i, test t.Run(strconv.Itoa(i), func(t *testing.T) { t.Parallel() encb, err := hex.DecodeString(test.packed) diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index 5d90cdb763..624032968d 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -182,7 +182,6 @@ var reflectTests = []reflectTest{ func TestReflectNameToStruct(t *testing.T) { t.Parallel() for _, test := range reflectTests { - test := test t.Run(test.name, func(t *testing.T) { t.Parallel() m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc)) diff --git a/accounts/abi/topics_test.go b/accounts/abi/topics_test.go index 70522b6ce6..53e4327bf6 100644 --- a/accounts/abi/topics_test.go +++ b/accounts/abi/topics_test.go @@ -147,7 +147,6 @@ func TestMakeTopics(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := MakeTopics(tt.args.query...) @@ -383,7 +382,6 @@ func TestParseTopics(t *testing.T) { tests := setupTopicsTests() for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() createObj := tt.args.createObj() @@ -403,7 +401,6 @@ func TestParseTopicsIntoMap(t *testing.T) { tests := setupTopicsTests() for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() outMap := make(map[string]interface{}) diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 0b4224efa5..d7f383a731 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -399,7 +399,6 @@ func TestMethodMultiReturn(t *testing.T) { "Can not unpack into a slice with wrong types", }} for _, tc := range testCases { - tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) err := abi.UnpackIntoInterface(tc.dest, "multi", data) @@ -957,7 +956,7 @@ func TestOOMMaliciousInput(t *testing.T) { } encb, err := hex.DecodeString(test.enc) if err != nil { - t.Fatalf("invalid hex: %s" + test.enc) + t.Fatalf("invalid hex: %s", test.enc) } _, err = abi.Methods["method"].Outputs.UnpackValues(encb) if err == nil { diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index 2304a54af6..9f653cebea 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -429,7 +429,7 @@ func TestT9n(t *testing.T) { ok, err := cmpJson(have, want) switch { case err != nil: - t.Logf(string(have)) + t.Log(string(have)) t.Fatalf("test %d, json parsing failed: %v", i, err) case !ok: t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) @@ -561,7 +561,7 @@ func TestB11r(t *testing.T) { ok, err := cmpJson(have, want) switch { case err != nil: - t.Logf(string(have)) + t.Log(string(have)) t.Fatalf("test %d, json parsing failed: %v", i, err) case !ok: t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) diff --git a/cmd/simulator/load/loader.go b/cmd/simulator/load/loader.go index 0dffe80bb7..8a5c5d0c93 100644 --- a/cmd/simulator/load/loader.go +++ b/cmd/simulator/load/loader.go @@ -66,7 +66,6 @@ func (l *Loader[T]) Execute(ctx context.Context) error { log.Info("Starting tx agents...") eg := errgroup.Group{} for _, agent := range agents { - agent := agent eg.Go(func() error { return agent.Execute(ctx) }) @@ -100,8 +99,6 @@ func (l *Loader[T]) ConfirmReachedTip(ctx context.Context) error { eg := errgroup.Group{} for i, client := range l.clients { - i := i - client := client eg.Go(func() error { for { latestHeight, err := client.LatestHeight(ctx) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 83b4938080..4b9534996a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -293,7 +293,6 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { return createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) } for _, tt := range tests { - tt := tt t.Run(tt.Name, func(t *testing.T) { t.Parallel() tt.testFunc(t, create) diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go index 85e685154e..d31bbf0fd8 100644 --- a/core/predicate_check_test.go +++ b/core/predicate_check_test.go @@ -293,7 +293,6 @@ func TestCheckPredicate(t *testing.T) { expectedErr: ErrIntrinsicGas, }, } { - test := test t.Run(name, func(t *testing.T) { require := require.New(t) // Create the rules from TestChainConfig and update the predicates based on the test params diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index b9e1eddb4d..eaac8d546b 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -291,10 +291,10 @@ func TestBlockReceiptStorage(t *testing.T) { // Insert the receipt slice into the database and check presence WriteReceipts(db, hash, 0, receipts) if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 { - t.Fatalf("no receipts returned") + t.Fatal("no receipts returned") } else { if err := checkReceiptsRLP(rs, receipts); err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } } // Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed) @@ -308,7 +308,7 @@ func TestBlockReceiptStorage(t *testing.T) { } // Ensure that receipts without metadata can be returned without the block body too if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil { - t.Fatalf(err.Error()) + t.Fatal(err) } // Sanity check that body and header alone without the receipt is a full purge WriteHeader(db, header) diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 4471a6ba45..02ea3d16a4 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -520,9 +520,7 @@ func TestYParityJSONUnmarshalling(t *testing.T) { DynamicFeeTxType, BlobTxType, } { - txType := txType for _, test := range tests { - test := test t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) { // Copy the base json testJson := make(map[string]interface{}) diff --git a/eth/filters/api.go b/eth/filters/api.go index 0748822b97..ba551b1b39 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -350,7 +350,6 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc select { case logs := <-matchedLogs: for _, log := range logs { - log := log notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index f2cbeed762..61e41d2526 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -113,7 +113,6 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if !strings.HasSuffix(file.Name(), ".json") { continue } - file := file // capture range variable t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() @@ -205,7 +204,6 @@ func BenchmarkTracers(b *testing.B) { if !strings.HasSuffix(file.Name(), ".json") { continue } - file := file // capture range variable b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) if err != nil { diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 93b85735e6..3f6a626711 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -168,7 +168,6 @@ func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) { if !strings.HasSuffix(file.Name(), ".json") { continue } - file := file // capture range variable t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index ef7c5cfd20..fcc48d5189 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -83,7 +83,6 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if !strings.HasSuffix(file.Name(), ".json") { continue } - file := file // capture range variable t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { t.Parallel() diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 5aa4327434..863510d2c0 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -446,7 +446,7 @@ func formatLogs(logs []StructLog) []StructLogRes { } formatted[index].Stack = &stack } - if trace.ReturnData != nil && len(trace.ReturnData) > 0 { + if len(trace.ReturnData) > 0 { formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String() } if trace.Memory != nil { diff --git a/go.mod b/go.mod index 13197720b3..c77e869795 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/ava-labs/subnet-evm -go 1.22.8 +go 1.23.6 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.2 + github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -57,7 +57,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.14.1-rc.1 // indirect + github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index cb8666e5e8..f5f6740c90 100644 --- a/go.sum +++ b/go.sum @@ -60,10 +60,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.2 h1:vZroUgB5xMMczDQnw9etDD1XhZsejFlKky+ZZv8wOKc= -github.com/ava-labs/avalanchego v1.12.2/go.mod h1:uEDLbAUPcGCfDWW680rVfysEofUe/jWte5qQk0j5hMs= -github.com/ava-labs/coreth v0.14.1-rc.1 h1:U72XlRm/fKyASmjThsWzfO/ZRvu1kaONFaX+KdJNxIc= -github.com/ava-labs/coreth v0.14.1-rc.1/go.mod h1:lxDSXLcrszMo0N/PVJzfZ//H+bRwXF/KQWtpEYgXZqM= +github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a h1:cBuGdwpaB1TBnfHS0w7oJyBJlM2UGOS4CEioxqMCG2g= +github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a/go.mod h1:pgUuh27zaeaXeMiIqqu/OWpbAhJQkYMJMft6nxXG9FE= +github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3 h1:0OrQidq+b3kb7KlnDqc5mkB/73i1zxYEMiXioWSU4+w= +github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3/go.mod h1:/XrU+CAm/0wiY5Ui05TLj2/n+czbCV6/Qrw/UMWp5aI= 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= diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go index ce78870dcd..294c8149e9 100644 --- a/internal/flags/flags_test.go +++ b/internal/flags/flags_test.go @@ -27,7 +27,6 @@ package flags import ( - "os" "os/user" "runtime" "testing" @@ -61,7 +60,7 @@ func TestPathExpansion(t *testing.T) { } } - os.Setenv(`DDDXXX`, `/tmp`) + t.Setenv(`DDDXXX`, `/tmp`) for test, expected := range tests { got := expandPath(test) if got != expected { diff --git a/metrics/json_test.go b/metrics/json_test.go index f91fe8cfa5..811bc29f11 100644 --- a/metrics/json_test.go +++ b/metrics/json_test.go @@ -13,7 +13,7 @@ func TestRegistryMarshallJSON(t *testing.T) { r.Register("counter", NewCounter()) enc.Encode(r) if s := b.String(); s != "{\"counter\":{\"count\":0}}\n" { - t.Fatalf(s) + t.Fatal(s) } } diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 5a448c8ce4..03e30932a9 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -22,6 +22,7 @@ import ( avagoUtils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/chain" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -280,13 +281,13 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned defer logsSub.Unsubscribe() nodeID1 := ids.GenerateTestNodeID() - blsSecretKey1, err := bls.NewSigner() + blsSecretKey1, err := localsigner.New() require.NoError(err) blsPublicKey1 := blsSecretKey1.PublicKey() blsSignature1 := blsSecretKey1.Sign(unsignedMessage.Bytes()) nodeID2 := ids.GenerateTestNodeID() - blsSecretKey2, err := bls.NewSigner() + blsSecretKey2, err := localsigner.New() require.NoError(err) blsPublicKey2 := blsSecretKey2.PublicKey() blsSignature2 := blsSecretKey2.Sign(unsignedMessage.Bytes()) @@ -550,7 +551,7 @@ func testReceiveWarpMessage( weight uint64 } newSigner := func(networkID ids.ID, weight uint64) signer { - secret, err := bls.NewSigner() + secret, err := localsigner.New() require.NoError(err) return signer{ networkID: networkID, diff --git a/plugin/runner/runner.go b/plugin/runner/runner.go index 9b5b5efe29..fbccd72057 100644 --- a/plugin/runner/runner.go +++ b/plugin/runner/runner.go @@ -22,7 +22,7 @@ func Run(versionStr string) { os.Exit(1) } if printVersion && versionStr != "" { - fmt.Printf(versionStr) + fmt.Print(versionStr) os.Exit(0) } if err := ulimit.Set(ulimit.DefaultFDLimit, logging.NoLog{}); err != nil { diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index feede8eb44..3668ead9c8 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -31,16 +31,17 @@ var ( ) var ( - errOverflowSignersGasCost = errors.New("overflow calculating warp signers gas cost") - errInvalidPredicateBytes = errors.New("cannot unpack predicate bytes") - errInvalidWarpMsg = errors.New("cannot unpack warp message") - errCannotParseWarpMsg = errors.New("cannot parse warp message") - errInvalidWarpMsgPayload = errors.New("cannot unpack warp message payload") - errInvalidAddressedPayload = errors.New("cannot unpack addressed payload") - errInvalidBlockHashPayload = errors.New("cannot unpack block hash payload") - errCannotGetNumSigners = errors.New("cannot fetch num signers from warp message") - errWarpCannotBeActivated = errors.New("warp cannot be activated before Durango") - errFailedVerification = errors.New("cannot verify warp signature") + errOverflowSignersGasCost = errors.New("overflow calculating warp signers gas cost") + errInvalidPredicateBytes = errors.New("cannot unpack predicate bytes") + errInvalidWarpMsg = errors.New("cannot unpack warp message") + errCannotParseWarpMsg = errors.New("cannot parse warp message") + errInvalidWarpMsgPayload = errors.New("cannot unpack warp message payload") + errInvalidAddressedPayload = errors.New("cannot unpack addressed payload") + errInvalidBlockHashPayload = errors.New("cannot unpack block hash payload") + errCannotGetNumSigners = errors.New("cannot fetch num signers from warp message") + errWarpCannotBeActivated = errors.New("warp cannot be activated before Durango") + errFailedVerification = errors.New("cannot verify warp signature") + errCannotRetrieveValidatorSet = errors.New("cannot retrieve validator set") ) // Config implements the precompileconfig.Config interface and @@ -208,16 +209,25 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon warpMsg.SourceChainID, c.RequirePrimaryNetworkSigners, ) - err = warpMsg.Signature.Verify( + + validatorSet, err := warp.GetCanonicalValidatorSetFromChainID( context.Background(), - &warpMsg.UnsignedMessage, - predicateContext.SnowCtx.NetworkID, state, predicateContext.ProposerVMBlockCtx.PChainHeight, + warpMsg.UnsignedMessage.SourceChainID, + ) + if err != nil { + log.Debug("failed to retrieve canonical validator set", "msgID", warpMsg.ID(), "err", err) + return fmt.Errorf("%w: %w", errCannotRetrieveValidatorSet, err) + } + + err = warpMsg.Signature.Verify( + &warpMsg.UnsignedMessage, + predicateContext.SnowCtx.NetworkID, + validatorSet, quorumNumerator, WarpQuorumDenominator, ) - if err != nil { log.Debug("failed to verify warp signature", "msgID", warpMsg.ID(), "err", err) return fmt.Errorf("%w: %w", errFailedVerification, err) diff --git a/precompile/contracts/warp/contract_test.go b/precompile/contracts/warp/contract_test.go index 4565df0ef8..6c4c06001c 100644 --- a/precompile/contracts/warp/contract_test.go +++ b/precompile/contracts/warp/contract_test.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/avalanchego/ids" agoUtils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/vms/platformvm/warp" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core/state" @@ -96,7 +95,7 @@ func TestSendWarpMessage(t *testing.T) { sendWarpMessagePayload, ) require.NoError(t, err) - unsignedWarpMessage, err := warp.NewUnsignedMessage( + unsignedWarpMessage, err := avalancheWarp.NewUnsignedMessage( defaultSnowCtx.NetworkID, blockchainID, sendWarpMessageAddressedPayload.Bytes(), @@ -747,7 +746,7 @@ func TestPackEvents(t *testing.T) { ) require.NoError(t, err) - unsignedWarpMessage, err := warp.NewUnsignedMessage( + unsignedWarpMessage, err := avalancheWarp.NewUnsignedMessage( networkID, sourceChainID, addressedPayload.Bytes(), diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 65d202de09..35afb24ed9 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -17,6 +17,7 @@ import ( agoUtils "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -111,7 +112,7 @@ func (v *testValidator) Compare(o *testValidator) int { } func newTestValidator() *testValidator { - sk, err := bls.NewSigner() + sk, err := localsigner.New() if err != nil { panic(err) } diff --git a/precompile/contracts/warp/signature_verification_test.go b/precompile/contracts/warp/signature_verification_test.go index d52f0a0f89..90ee808f91 100644 --- a/precompile/contracts/warp/signature_verification_test.go +++ b/precompile/contracts/warp/signature_verification_test.go @@ -19,12 +19,13 @@ import ( ) type signatureTest struct { - name string - stateF func(*gomock.Controller) validators.State - quorumNum uint64 - quorumDen uint64 - msgF func(*require.Assertions) *avalancheWarp.Message - err error + name string + stateF func(*gomock.Controller) validators.State + quorumNum uint64 + quorumDen uint64 + msgF func(*require.Assertions) *avalancheWarp.Message + verifyErr error + canonicalErr error } // This test copies the test coverage from https://github.com/ava-labs/avalanchego/blob/0117ab96/vms/platformvm/warp/signature_test.go#L137. @@ -55,7 +56,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: errTest, + canonicalErr: errTest, }, { name: "can't get validator set", @@ -82,7 +83,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: errTest, + canonicalErr: errTest, }, { name: "weight overflow", @@ -122,7 +123,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrWeightOverflow, + canonicalErr: avalancheWarp.ErrWeightOverflow, }, { name: "invalid bit set index", @@ -152,7 +153,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrInvalidBitSet, + verifyErr: avalancheWarp.ErrInvalidBitSet, }, { name: "unknown index", @@ -185,7 +186,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrUnknownValidator, + verifyErr: avalancheWarp.ErrUnknownValidator, }, { name: "insufficient weight", @@ -229,7 +230,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrInsufficientWeight, + verifyErr: avalancheWarp.ErrInsufficientWeight, }, { name: "can't parse sig", @@ -263,7 +264,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrParseSignature, + verifyErr: avalancheWarp.ErrParseSignature, }, { name: "no validators", @@ -298,7 +299,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: bls.ErrNoPublicKeys, + verifyErr: bls.ErrNoPublicKeys, }, { name: "invalid signature (substitute)", @@ -342,7 +343,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrInvalidSignature, + verifyErr: avalancheWarp.ErrInvalidSignature, }, { name: "invalid signature (missing one)", @@ -382,7 +383,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrInvalidSignature, + verifyErr: avalancheWarp.ErrInvalidSignature, }, { name: "invalid signature (extra one)", @@ -427,7 +428,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: avalancheWarp.ErrInvalidSignature, + verifyErr: avalancheWarp.ErrInvalidSignature, }, { name: "valid signature", @@ -471,7 +472,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: nil, + verifyErr: nil, }, { name: "valid signature (boundary)", @@ -515,7 +516,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: nil, + verifyErr: nil, }, { name: "valid signature (missing key)", @@ -576,7 +577,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: nil, + verifyErr: nil, }, { name: "valid signature (duplicate key)", @@ -635,7 +636,7 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) return msg }, - err: nil, + verifyErr: nil, }, } @@ -648,16 +649,24 @@ func TestSignatureVerification(t *testing.T) { msg := tt.msgF(require) pChainState := tt.stateF(ctrl) - err := msg.Signature.Verify( + validatorSet, err := avalancheWarp.GetCanonicalValidatorSetFromChainID( context.Background(), - &msg.UnsignedMessage, - networkID, pChainState, pChainHeight, + msg.UnsignedMessage.SourceChainID, + ) + require.ErrorIs(err, tt.canonicalErr) + if err != nil { + return + } + err = msg.Signature.Verify( + &msg.UnsignedMessage, + networkID, + validatorSet, tt.quorumNum, tt.quorumDen, ) - require.ErrorIs(err, tt.err) + require.ErrorIs(err, tt.verifyErr) }) } } diff --git a/rpc/client_test.go b/rpc/client_test.go index eec18afb8f..97bc69e61b 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -723,7 +723,6 @@ func TestClientHTTP(t *testing.T) { ) defer client.Close() for i := range results { - i := i go func() { errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) }() diff --git a/rpc/types_test.go b/rpc/types_test.go index a255c1e9f7..779b23296f 100644 --- a/rpc/types_test.go +++ b/rpc/types_test.go @@ -145,7 +145,6 @@ func TestBlockNumberOrHash_WithNumber_MarshalAndUnmarshal(t *testing.T) { {"earliest", int64(EarliestBlockNumber)}, } for _, test := range tests { - test := test t.Run(test.name, func(t *testing.T) { bnh := BlockNumberOrHashWithNumber(BlockNumber(test.number)) marshalled, err := json.Marshal(bnh) diff --git a/scripts/lint.sh b/scripts/lint.sh index 643291d934..acd5b324fc 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -4,11 +4,10 @@ set -o errexit set -o nounset set -o pipefail -# Upstream is compatible with v1.54.x at time of this writing, and +# Upstream is compatible with v1.63.x at time of this writing, and # checking for this specific version is an attempt to avoid skew -# between local and CI execution. The latest version (v1.55.1) seems -# to cause spurious failures -KNOWN_GOOD_VERSION="v1.56" +# between local and CI execution. +KNOWN_GOOD_VERSION="v1.63" VERSION="$(golangci-lint --version | sed -e 's+golangci-lint has version \(v1.*\)\..* built.*+\1+')" if [[ "${VERSION}" != "${KNOWN_GOOD_VERSION}" ]]; then echo "expected golangci-lint ${KNOWN_GOOD_VERSION}, but ${VERSION} was used" diff --git a/scripts/versions.sh b/scripts/versions.sh index 2f0a46f660..2b27dadefc 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,5 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.12.2'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'f1ec9a13'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} diff --git a/utils/snow.go b/utils/snow.go index fe33a56512..f89b9cffdf 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -14,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/snow/validators/validatorstest" "github.com/ava-labs/avalanchego/upgrade/upgradetest" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) @@ -26,7 +26,7 @@ var ( ) func TestSnowContext() *snow.Context { - sk, err := bls.NewSigner() + sk, err := localsigner.New() if err != nil { panic(err) } diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 055d3edfa8..397e39553d 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -14,11 +14,12 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) func newValidator(t testing.TB, weight uint64) (bls.Signer, *avalancheWarp.Validator) { - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(t, err) pk := sk.PublicKey() return sk, &avalancheWarp.Validator{ @@ -51,7 +52,7 @@ func TestAggregateSignatures(t *testing.T) { vdr2: sig2, vdr3: sig3, } - nonVdrSk, err := bls.NewSigner() + nonVdrSk, err := localsigner.New() require.NoError(t, err) nonVdrSig := nonVdrSk.Sign(unsignedMsg.Bytes()) vdrs := []*avalancheWarp.Validator{ diff --git a/warp/backend_test.go b/warp/backend_test.go index 764957882c..cee1150353 100644 --- a/warp/backend_test.go +++ b/warp/backend_test.go @@ -12,7 +12,7 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/warp/warptest" @@ -41,7 +41,7 @@ func init() { func TestAddAndGetValidMessage(t *testing.T) { db := memdb.New() - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -64,7 +64,7 @@ func TestAddAndGetValidMessage(t *testing.T) { func TestAddAndGetUnknownMessage(t *testing.T) { db := memdb.New() - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -83,7 +83,7 @@ func TestGetBlockSignature(t *testing.T) { blockClient := warptest.MakeBlockClient(blkID) db := memdb.New() - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) messageSignatureCache := &cache.LRU[ids.ID, []byte]{Size: 500} @@ -108,7 +108,7 @@ func TestGetBlockSignature(t *testing.T) { func TestZeroSizedCache(t *testing.T) { db := memdb.New() - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) @@ -136,7 +136,7 @@ func TestOffChainMessages(t *testing.T) { check func(require *require.Assertions, b Backend) err error } - sk, err := bls.NewSigner() + sk, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(sk, networkID, sourceChainID) diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go index ac2a364a78..92eb72c724 100644 --- a/warp/handlers/signature_request_test.go +++ b/warp/handlers/signature_request_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/plugin/evm/message" @@ -23,7 +24,7 @@ import ( func TestMessageSignatureHandler(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSigner() + blsSecretKey, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -127,7 +128,7 @@ func TestMessageSignatureHandler(t *testing.T) { func TestBlockSignatureHandler(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSigner() + blsSecretKey, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) diff --git a/warp/service.go b/warp/service.go index bcb54ccbbc..993b65b221 100644 --- a/warp/service.go +++ b/warp/service.go @@ -112,22 +112,22 @@ func (a *API) aggregateSignatures(ctx context.Context, unsignedMessage *warp.Uns } state := warpValidators.NewState(a.state, a.sourceSubnetID, a.sourceChainID, a.requirePrimaryNetworkSigners()) - validators, totalWeight, err := warp.GetCanonicalValidatorSet(ctx, state, pChainHeight, subnetID) + validatorSet, err := warp.GetCanonicalValidatorSetFromSubnetID(ctx, state, pChainHeight, subnetID) if err != nil { return nil, fmt.Errorf("failed to get validator set: %w", err) } - if len(validators) == 0 { + if len(validatorSet.Validators) == 0 { return nil, fmt.Errorf("%w (SubnetID: %s, Height: %d)", errNoValidators, subnetID, pChainHeight) } log.Debug("Fetching signature", "sourceSubnetID", subnetID, "height", pChainHeight, - "numValidators", len(validators), - "totalWeight", totalWeight, + "numValidators", len(validatorSet.Validators), + "totalWeight", validatorSet.TotalWeight, ) - agg := aggregator.New(aggregator.NewSignatureGetter(a.client), validators, totalWeight) + agg := aggregator.New(aggregator.NewSignatureGetter(a.client), validatorSet.Validators, validatorSet.TotalWeight) signatureResult, err := agg.AggregateSignatures(ctx, unsignedMessage, quorumNum) if err != nil { return nil, err diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index d58e9e6c90..276b9592d9 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -14,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/timer/mockable" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" @@ -30,7 +30,7 @@ import ( func TestAddressedCallSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSigner() + blsSecretKey, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -144,7 +144,7 @@ func TestAddressedCallSignatures(t *testing.T) { func TestBlockSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSigner() + blsSecretKey, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) @@ -262,7 +262,7 @@ func TestBlockSignatures(t *testing.T) { func TestUptimeSignatures(t *testing.T) { database := memdb.New() snowCtx := utils.TestSnowContext() - blsSecretKey, err := bls.NewSigner() + blsSecretKey, err := localsigner.New() require.NoError(t, err) warpSigner := avalancheWarp.NewSigner(blsSecretKey, snowCtx.NetworkID, snowCtx.ChainID) From 95edaf0ca02ebbdb7f71c66e097000d35d7a190b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 3 Mar 2025 19:44:12 +0300 Subject: [PATCH 41/57] chore(ci): use go.mod version and remove unused lint.sh (#1456) --- .github/workflows/bench.yml | 4 ++-- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 15 ++++++--------- scripts/lint.sh | 19 ------------------- 4 files changed, 9 insertions(+), 31 deletions(-) delete mode 100755 scripts/lint.sh diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index c6710de7e2..3e81619fb5 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -9,9 +9,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: - go-version: "~1.23.6" + go-version-file: "go.mod" - run: go mod download shell: bash - run: ./scripts/build_bench_precompiles.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0f5a7a888..be3d09996f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "~1.23.6" + go-version-file: "go.mod" - name: Set up arm64 cross compiler run: | sudo apt-get -y update diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 479877a8b9..221179561d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,9 +8,6 @@ on: - "*" pull_request: -env: - min_go_version: "~1.23.6" - jobs: lint_test: name: Lint @@ -21,7 +18,7 @@ jobs: shell: bash - uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: @@ -51,7 +48,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: Set timeout on Windows # Windows UT run slower and need a longer timeout shell: bash if: matrix.os == 'windows-latest' @@ -85,7 +82,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: Use Node.js uses: actions/setup-node@v4 with: @@ -126,7 +123,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: Use Node.js uses: actions/setup-node@v4 with: @@ -170,7 +167,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: Install AvalancheGo Release shell: bash run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh @@ -205,7 +202,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: ${{ env.min_go_version }} + go-version-file: "go.mod" - name: Install AvalancheGo Release shell: bash run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index acd5b324fc..0000000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -# Upstream is compatible with v1.63.x at time of this writing, and -# checking for this specific version is an attempt to avoid skew -# between local and CI execution. -KNOWN_GOOD_VERSION="v1.63" -VERSION="$(golangci-lint --version | sed -e 's+golangci-lint has version \(v1.*\)\..* built.*+\1+')" -if [[ "${VERSION}" != "${KNOWN_GOOD_VERSION}" ]]; then - echo "expected golangci-lint ${KNOWN_GOOD_VERSION}, but ${VERSION} was used" - echo "${KNOWN_GOOD_VERSION} is used in CI and should be used locally to ensure compatible results" - echo "installation command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@${KNOWN_GOOD_VERSION}" - exit 255 -fi - -golangci-lint run --path-prefix=. --timeout 3m From 0f03de94114457cc80b526a2503d7bf09f35278d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 4 Mar 2025 22:38:45 +0300 Subject: [PATCH 42/57] update ubuntu 20.04 to 22.04 (#1464) --- .github/workflows/bench.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 3e81619fb5..a8d5d82fe6 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -6,7 +6,7 @@ on: jobs: bench: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index be3d09996f..60d0921901 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ on: jobs: release: # needs: [lint_test, unit_test, e2e_test, simulator_test] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Git checkout uses: actions/checkout@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 221179561d..4f72793c2e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ on: jobs: lint_test: name: Lint - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./scripts/lint_allowed_geth_imports.sh @@ -43,7 +43,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-latest, ubuntu-20.04, ubuntu-latest, windows-latest] + os: [macos-latest, ubuntu-22.04, ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 From 6839da242ebefc7b157de9252a73db548c38c35f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 4 Mar 2025 23:06:53 +0300 Subject: [PATCH 43/57] use ctx in validatorsManager (#1465) --- RELEASES.md | 1 + .../evm/validators/interfaces/interfaces.go | 17 ++++-- plugin/evm/validators/manager.go | 56 +++++++++++++++++-- plugin/evm/vm.go | 22 ++------ plugin/evm/vm_test.go | 4 -- 5 files changed, 71 insertions(+), 29 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index b7eb6ea301..963817abed 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,7 @@ * Bump golangci-lint to v1.63 and add linters * Refactored trie_prefetcher.go to be structurally similar to upstream. * Remove legacy gossip handler and metrics +* Fixed concurrency issue in validators/uptime manager ## [v0.7.0](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.0) diff --git a/plugin/evm/validators/interfaces/interfaces.go b/plugin/evm/validators/interfaces/interfaces.go index d8b88b3626..8a7d3a2621 100644 --- a/plugin/evm/validators/interfaces/interfaces.go +++ b/plugin/evm/validators/interfaces/interfaces.go @@ -13,7 +13,9 @@ import ( ) type ValidatorReader interface { - // GetValidatorAndUptime returns the uptime of the validator specified by validationID + // GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID + // and the last updated time. + // GetValidatorAndUptime holds the chain context lock while performing the operation and can be called concurrently. GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) } @@ -21,10 +23,15 @@ type Manager interface { stateinterfaces.State avalancheuptime.Manager ValidatorReader - - // Sync updates the validator set managed - // by the manager - Sync(ctx context.Context) error + // Initialize initializes the validator manager + // by syncing the validator state with the current validator set + // and starting the uptime tracking. + // Initialize holds the chain context lock while performing the operation. + Initialize(ctx context.Context) error + // Shutdown stops the uptime tracking and writes the validator state to the database. + // Shutdown holds the chain context lock while performing the operation. + Shutdown() error // DispatchSync starts the sync process + // DispatchSync holds the chain context lock while performing the sync. DispatchSync(ctx context.Context) } diff --git a/plugin/evm/validators/manager.go b/plugin/evm/validators/manager.go index be58ee4d2e..738b9d6257 100644 --- a/plugin/evm/validators/manager.go +++ b/plugin/evm/validators/manager.go @@ -56,6 +56,54 @@ func NewManager( }, nil } +// Initialize initializes the validator manager +// by syncing the validator state with the current validator set +// and starting the uptime tracking. +// Initialize holds the chain context lock while performing the operation. +func (m *manager) Initialize(ctx context.Context) error { + m.chainCtx.Lock.Lock() + defer m.chainCtx.Lock.Unlock() + // sync validators first + if err := m.sync(ctx); err != nil { + return fmt.Errorf("failed to update validators: %w", err) + } + vdrIDs := m.GetNodeIDs().List() + // Then start tracking with updated validators + // StartTracking initializes the uptime tracking with the known validators + // and update their uptime to account for the time we were being offline. + if err := m.StartTracking(vdrIDs); err != nil { + return fmt.Errorf("failed to start tracking uptime: %w", err) + } + return nil +} + +// Shutdown stops the uptime tracking and writes the validator state to the database. +// Shutdown holds the chain context lock while performing the operation. +func (m *manager) Shutdown() error { + m.chainCtx.Lock.Lock() + defer m.chainCtx.Lock.Unlock() + vdrIDs := m.GetNodeIDs().List() + if err := m.StopTracking(vdrIDs); err != nil { + return fmt.Errorf("failed to stop tracking uptime: %w", err) + } + if err := m.WriteState(); err != nil { + return fmt.Errorf("failed to write validator: %w", err) + } + return nil +} + +func (m *manager) Connect(nodeID ids.NodeID) error { + m.chainCtx.Lock.Lock() + defer m.chainCtx.Lock.Unlock() + return m.PausableManager.Connect(nodeID) +} + +func (m *manager) Disconnect(nodeID ids.NodeID) error { + m.chainCtx.Lock.Lock() + defer m.chainCtx.Lock.Unlock() + return m.PausableManager.Disconnect(nodeID) +} + // GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID // and the last updated time. // GetValidatorAndUptime holds the chain context lock while performing the operation and can be called concurrently. @@ -88,7 +136,7 @@ func (m *manager) DispatchSync(ctx context.Context) { select { case <-ticker.C: m.chainCtx.Lock.Lock() - if err := m.Sync(ctx); err != nil { + if err := m.sync(ctx); err != nil { log.Error("failed to sync validators", "error", err) } m.chainCtx.Lock.Unlock() @@ -98,10 +146,10 @@ func (m *manager) DispatchSync(ctx context.Context) { } } -// Sync synchronizes the validator state with the current validator set +// sync synchronizes the validator state with the current validator set // and writes the state to the database. -// Sync is not safe to call concurrently and should be called with the chain context locked. -func (m *manager) Sync(ctx context.Context) error { +// sync is not safe to call concurrently and should be called with the chain context locked. +func (m *manager) sync(ctx context.Context) error { now := time.Now() log.Debug("performing validator sync") // get current validator set diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 9b73f4f02c..32790c8768 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -720,17 +720,11 @@ func (vm *VM) onNormalOperationsStarted() error { ctx, cancel := context.WithCancel(context.TODO()) vm.cancel = cancel - // sync validators first - if err := vm.validatorsManager.Sync(ctx); err != nil { - return fmt.Errorf("failed to update validators: %w", err) - } - vdrIDs := vm.validatorsManager.GetNodeIDs().List() - // Then start tracking with updated validators - // StartTracking initializes the uptime tracking with the known validators - // and update their uptime to account for the time we were being offline. - if err := vm.validatorsManager.StartTracking(vdrIDs); err != nil { - return fmt.Errorf("failed to start tracking uptime: %w", err) + // Start the validators manager + if err := vm.validatorsManager.Initialize(ctx); err != nil { + return fmt.Errorf("failed to initialize validators manager: %w", err) } + // dispatch validator set update vm.shutdownWg.Add(1) go func() { @@ -875,12 +869,8 @@ func (vm *VM) Shutdown(context.Context) error { vm.cancel() } if vm.bootstrapped.Get() { - vdrIDs := vm.validatorsManager.GetNodeIDs().List() - if err := vm.validatorsManager.StopTracking(vdrIDs); err != nil { - return fmt.Errorf("failed to stop tracking uptime: %w", err) - } - if err := vm.validatorsManager.WriteState(); err != nil { - return fmt.Errorf("failed to write validator: %w", err) + if err := vm.validatorsManager.Shutdown(); err != nil { + return fmt.Errorf("failed to shutdown validators manager: %w", err) } } vm.Network.Shutdown() diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 963b2b75ef..21ab6ecb40 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -153,10 +153,6 @@ func setupGenesis( atomicMemory := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) ctx.SharedMemory = atomicMemory.NewSharedMemory(ctx.ChainID) - // NB: this lock is intentionally left locked when this function returns. - // The caller of this function is responsible for unlocking. - ctx.Lock.Lock() - issuer := make(chan commonEng.Message, 1) prefixedDB := prefixdb.New([]byte{1}, baseDB) return ctx, prefixedDB, genesisBytes, issuer, atomicMemory From 70428c84ea6d05b0fcd352d5c6d467ac23395c9d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 4 Mar 2025 23:26:34 +0300 Subject: [PATCH 44/57] fix multiarch docker binary build (#1461) --- .github/workflows/publish_docker.yml | 2 +- .github/workflows/tests.yml | 4 ++ Dockerfile | 38 +++++++++--- plugin/runner/runner.go | 2 +- scripts/build_docker_image.sh | 45 +++++++++++--- scripts/constants.sh | 8 ++- scripts/tests.build_docker_image.sh | 92 ++++++++++++++++++++-------- 7 files changed, 144 insertions(+), 47 deletions(-) diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index d1764edc66..6f1a89177a 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -26,7 +26,7 @@ jobs: env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} - DOCKER_REPO: "avaplatform/subnet-evm" + IMAGE_NAME: "avaplatform/subnet-evm" VM_ID: ${{ inputs.vm_id }} PUBLISH: 1 PLATFORMS: "linux/amd64,linux/arm64" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f72793c2e..a8866b323d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -192,6 +192,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Install qemu (required for cross-platform builds) + run: | + sudo apt update + sudo apt -y install qemu-system qemu-user-static - name: Check image build shell: bash run: bash -x scripts/tests.build_docker_image.sh diff --git a/Dockerfile b/Dockerfile index 7312954822..5afa01d5f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,7 @@ -# syntax=docker/dockerfile:experimental - # ============= Setting up base Stage ================ # AVALANCHEGO_NODE_IMAGE needs to identify an existing node image and should include the tag -ARG AVALANCHEGO_NODE_IMAGE +# This value is not intended to be used but silences a warning +ARG AVALANCHEGO_NODE_IMAGE="invalid-image" # ============= Compilation Stage ================ FROM --platform=$BUILDPLATFORM golang:1.23.6-bullseye AS builder @@ -12,9 +11,8 @@ WORKDIR /build # Copy avalanche dependencies first (intermediate docker image caching) # Copy avalanchego directory if present (for manual CI case, which uses local dependency) COPY go.mod go.sum avalanchego* ./ - # Download avalanche dependencies using go mod -RUN go mod download && go mod tidy -compat=1.23 +RUN go mod download && go mod tidy # Copy the code into the container COPY . . @@ -22,15 +20,39 @@ COPY . . # Ensure pre-existing builds are not available for inclusion in the final image RUN [ -d ./build ] && rm -rf ./build/* || true + +ARG TARGETPLATFORM +ARG BUILDPLATFORM + +# Configure a cross-compiler if the target platform differs from the build platform. +# +# build_env.sh is used to capture the environmental changes required by the build step since RUN +# environment state is not otherwise persistent. +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] && [ "$BUILDPLATFORM" != "linux/arm64" ]; then \ + apt-get update && apt-get install -y gcc-aarch64-linux-gnu && \ + echo "export CC=aarch64-linux-gnu-gcc" > ./build_env.sh \ + ; elif [ "$TARGETPLATFORM" = "linux/amd64" ] && [ "$BUILDPLATFORM" != "linux/amd64" ]; then \ + apt-get update && apt-get install -y gcc-x86-64-linux-gnu && \ + echo "export CC=x86_64-linux-gnu-gcc" > ./build_env.sh \ + ; else \ + echo "export CC=gcc" > ./build_env.sh \ + ; fi + # Pass in SUBNET_EVM_COMMIT as an arg to allow the build script to set this externally ARG SUBNET_EVM_COMMIT ARG CURRENT_BRANCH -RUN export SUBNET_EVM_COMMIT=$SUBNET_EVM_COMMIT && export CURRENT_BRANCH=$CURRENT_BRANCH && ./scripts/build.sh build/subnet-evm +RUN . ./build_env.sh && \ + echo "{CC=$CC, TARGETPLATFORM=$TARGETPLATFORM, BUILDPLATFORM=$BUILDPLATFORM}" && \ + export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \ + export CURRENT_BRANCH=$CURRENT_BRANCH && \ + export SUBNET_EVM_COMMIT=$SUBNET_EVM_COMMIT && \ + ./scripts/build.sh build/subnet-evm # ============= Cleanup Stage ================ -FROM $AVALANCHEGO_NODE_IMAGE AS builtImage +FROM $AVALANCHEGO_NODE_IMAGE AS execution # Copy the evm binary into the correct location in the container ARG VM_ID=srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy -COPY --from=builder /build/build/subnet-evm /avalanchego/build/plugins/$VM_ID +ENV AVAGO_PLUGIN_DIR="/avalanchego/build/plugins" +COPY --from=builder /build/build/subnet-evm $AVAGO_PLUGIN_DIR/$VM_ID diff --git a/plugin/runner/runner.go b/plugin/runner/runner.go index fbccd72057..e7d9ea6c3d 100644 --- a/plugin/runner/runner.go +++ b/plugin/runner/runner.go @@ -22,7 +22,7 @@ func Run(versionStr string) { os.Exit(1) } if printVersion && versionStr != "" { - fmt.Print(versionStr) + fmt.Println(versionStr) os.Exit(0) } if err := ulimit.Set(ulimit.DefaultFDLimit, logging.NoLog{}); err != nil { diff --git a/scripts/build_docker_image.sh b/scripts/build_docker_image.sh index 93474e4a36..1fdea150bb 100755 --- a/scripts/build_docker_image.sh +++ b/scripts/build_docker_image.sh @@ -36,21 +36,33 @@ BUILD_IMAGE_ID=${BUILD_IMAGE_ID:-"${CURRENT_BRANCH}"} # # Reference: https://docs.docker.com/build/buildkit/ DOCKER_CMD="docker buildx build" - +ispush=0 if [[ -n "${PUBLISH}" ]]; then - DOCKER_CMD="${DOCKER_CMD} --push" - - echo "Pushing $DOCKERHUB_REPO:$BUILD_IMAGE_ID" - + echo "Pushing $IMAGE_NAME:$BUILD_IMAGE_ID" + ispush=1 # A populated DOCKER_USERNAME env var triggers login if [[ -n "${DOCKER_USERNAME:-}" ]]; then echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin fi fi -# Build a multi-arch image if requested +# Build a specified platform image if requested if [[ -n "${PLATFORMS}" ]]; then DOCKER_CMD="${DOCKER_CMD} --platform=${PLATFORMS}" + if [[ "$PLATFORMS" == *,* ]]; then ## Multi-arch + ispush=1 + fi +fi + +if [[ $ispush -eq 1 ]]; then + DOCKER_CMD="${DOCKER_CMD} --push" +else + ## Single arch + # + # Building a single-arch image with buildx and having the resulting image show up + # in the local store of docker images (ala 'docker build') requires explicitly + # loading it from the buildx store with '--load'. + DOCKER_CMD="${DOCKER_CMD} --load" fi VM_ID=${VM_ID:-"${DEFAULT_VM_ID}"} @@ -61,8 +73,21 @@ fi # Default to the release image. Will need to be overridden when testing against unreleased versions. AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_NODE_IMAGE:-${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION}}" -echo "Building Docker Image: $DOCKERHUB_REPO:$BUILD_IMAGE_ID based of AvalancheGo@$AVALANCHE_VERSION" -${DOCKER_CMD} -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" -t "$DOCKERHUB_REPO:${DOCKERHUB_TAG}" \ +# Build the avalanchego image if it cannot be pulled. This will usually be due to +# AVALANCHE_VERSION being not yet merged since the image is published post-merge. +if ! docker pull "${AVALANCHEGO_NODE_IMAGE}"; then + # Use a image name without a repository (i.e. without 'avaplatform/' prefix ) to build a + # local image that will not be pushed. + export AVALANCHEGO_IMAGE_NAME="avalanchego" + echo "Building ${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION} locally" + + source "${SUBNET_EVM_PATH}"/scripts/lib_avalanchego_clone.sh + clone_avalanchego "${AVALANCHE_VERSION}" + SKIP_BUILD_RACE=1 DOCKER_IMAGE="${AVALANCHEGO_IMAGE_NAME}" "${AVALANCHEGO_CLONE_PATH}"/scripts/build_image.sh +fi + +echo "Building Docker Image: $IMAGE_NAME:$BUILD_IMAGE_ID based of AvalancheGo@$AVALANCHE_VERSION" +${DOCKER_CMD} -t "$IMAGE_NAME:$BUILD_IMAGE_ID" -t "$IMAGE_NAME:${DOCKERHUB_TAG}" \ "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ --build-arg AVALANCHEGO_NODE_IMAGE="$AVALANCHEGO_NODE_IMAGE" \ --build-arg SUBNET_EVM_COMMIT="$SUBNET_EVM_COMMIT" \ @@ -70,6 +95,6 @@ ${DOCKER_CMD} -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" -t "$DOCKERHUB_REPO:${DOCKERH --build-arg VM_ID="$VM_ID" if [[ -n "${PUBLISH}" && $CURRENT_BRANCH == "master" ]]; then - echo "Tagging current image as $DOCKERHUB_REPO:latest" - docker buildx imagetools create -t "$DOCKERHUB_REPO:latest" "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" + echo "Tagging current image as $IMAGE_NAME:latest" + docker buildx imagetools create -t "$IMAGE_NAME:latest" "$IMAGE_NAME:$BUILD_IMAGE_ID" fi diff --git a/scripts/constants.sh b/scripts/constants.sh index 4fad925775..4cb00af244 100644 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -8,12 +8,13 @@ set -euo pipefail # Set the PATHS GOPATH="$(go env GOPATH)" DEFAULT_PLUGIN_DIR="${HOME}/.avalanchego/plugins" +DEFAULT_VM_NAME="subnet-evm" DEFAULT_VM_ID="srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" # Avalabs docker hub # avaplatform/avalanchego - defaults to local as to avoid unintentional pushes -# You should probably set it - export DOCKER_REPO='avaplatform/subnet-evm' -DOCKERHUB_REPO=${DOCKER_REPO:-"subnet-evm"} +# You should probably set it - export IMAGE_NAME='avaplatform/subnet-evm' +IMAGE_NAME=${IMAGE_NAME:-"subnet-evm"} # Shared between ./scripts/build_docker_image.sh and ./scripts/tests.build_docker_image.sh AVALANCHEGO_IMAGE_NAME="${AVALANCHEGO_IMAGE_NAME:-avaplatform/avalanchego}" @@ -51,3 +52,6 @@ fi # We use "export" here instead of just setting a bash variable because we need # to pass this flag to all child processes spawned by the shell. export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" + +# CGO_ENABLED is required for multi-arch builds. +export CGO_ENABLED=1 diff --git a/scripts/tests.build_docker_image.sh b/scripts/tests.build_docker_image.sh index 8c928e25a5..e556ceb500 100755 --- a/scripts/tests.build_docker_image.sh +++ b/scripts/tests.build_docker_image.sh @@ -5,34 +5,76 @@ set -euo pipefail # Sanity check the image build by attempting to build and run the image without error. # Directory above this script -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) # Load the constants source "$SUBNET_EVM_PATH"/scripts/constants.sh # Load the versions source "$SUBNET_EVM_PATH"/scripts/versions.sh -# Use the default node image -AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION}" - -# Build the avalanchego image if it cannot be pulled. This will usually be due to -# AVALANCHE_VERSION being not yet merged since the image is published post-merge. -if ! docker pull "${AVALANCHEGO_NODE_IMAGE}"; then - # Use a image name without a repository (i.e. without 'avaplatform/' prefix ) to build a - # local image that will not be pushed. - export AVALANCHEGO_IMAGE_NAME="avalanchego" - echo "Building ${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION} locally" - - source "${SUBNET_EVM_PATH}"/scripts/lib_avalanchego_clone.sh - clone_avalanchego "${AVALANCHE_VERSION}" - SKIP_BUILD_RACE=1 DOCKER_IMAGE="${AVALANCHEGO_IMAGE_NAME}" "${AVALANCHEGO_CLONE_PATH}"/scripts/build_image.sh -fi - -# Build a local image -bash -x "${SUBNET_EVM_PATH}"/scripts/build_docker_image.sh - -# Check that the image can be run and contains the plugin -echo "Checking version of the plugin provided by the image" -docker run -t --rm "${DOCKERHUB_REPO}:${DOCKERHUB_TAG}" /avalanchego/build/plugins/"${DEFAULT_VM_ID}" --version -echo "" # --version output doesn't include a newline -echo "Successfully checked image build" +build_and_test() { + local imagename="${1}" + local vm_id="${2}" + local multiarch_image="${3}" + + if [[ "${multiarch_image}" == true ]]; then + local arches="linux/amd64,linux/arm64" + else + # Test only the host platform for single arch builds + local host_arch + host_arch="$(go env GOARCH)" + local arches="linux/$host_arch" + fi + + local imgtag="testtag" + + PLATFORMS="${arches}" \ + BUILD_IMAGE_ID="${imgtag}" \ + VM_ID=$"${vm_id}" \ + IMAGE_NAME="${imagename}" \ + ./scripts/build_docker_image.sh + + echo "listing images" + docker images + + # Check all of the images expected to have been built + local target_images=( + "$imagename:$imgtag" + "$imagename:$DOCKERHUB_TAG" + ) + IFS=',' read -r -a archarray <<<"$arches" + for arch in "${archarray[@]}"; do + for target_image in "${target_images[@]}"; do + echo "checking sanity of image $target_image for $arch by running '${VM_ID} version'" + docker run -t --rm --platform "$arch" "$target_image" /avalanchego/build/plugins/"${VM_ID}" --version + done + done +} + +VM_ID="${VM_ID:-${DEFAULT_VM_ID}}" + +echo "checking build of single-arch image" +build_and_test "subnet-evm" "${VM_ID}" false + +echo "starting local docker registry to allow verification of multi-arch image builds" +REGISTRY_CONTAINER_ID="$(docker run --rm -d -P registry:2)" +REGISTRY_PORT="$(docker port "$REGISTRY_CONTAINER_ID" 5000/tcp | grep -v "::" | awk -F: '{print $NF}')" + +echo "starting docker builder that supports multiplatform builds" +# - '--driver-opt network=host' enables the builder to use the local registry +docker buildx create --use --name ci-builder --driver-opt network=host + +# Ensure registry and builder cleanup on teardown +function cleanup { + echo "stopping local docker registry" + docker stop "${REGISTRY_CONTAINER_ID}" + echo "removing multiplatform builder" + docker buildx rm ci-builder +} +trap cleanup EXIT + +echo "checking build of multi-arch images" +build_and_test "localhost:${REGISTRY_PORT}/subnet-evm" "${VM_ID}" true From fdb279d4ce7701af1750bc4a944e3be79f1820f3 Mon Sep 17 00:00:00 2001 From: chuangjinglu Date: Wed, 5 Mar 2025 04:27:35 +0800 Subject: [PATCH 45/57] chore: fix some comments (#1431) Signed-off-by: chuangjinglu Co-authored-by: Ceyhun Onur --- cmd/simulator/config/flags.go | 2 +- core/evm.go | 2 +- core/predicate_check_test.go | 2 +- precompile/contracts/rewardmanager/contract.go | 2 +- precompile/contracts/rewardmanager/contract_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/simulator/config/flags.go b/cmd/simulator/config/flags.go index 8fb3d3c5fd..95ffe96603 100644 --- a/cmd/simulator/config/flags.go +++ b/cmd/simulator/config/flags.go @@ -125,5 +125,5 @@ func addSimulatorFlags(fs *pflag.FlagSet) { fs.String(LogLevelKey, "info", "Specify the log level to use in the simulator") fs.Uint64(BatchSizeKey, 100, "Specify the batchsize for the worker to issue and confirm txs") fs.Uint64(MetricsPortKey, 8082, "Specify the port to use for the metrics server") - fs.String(MetricsOutputKey, "", "Specify the file to write metrics in json format, or empy to write to stdout (defaults to stdout)") + fs.String(MetricsOutputKey, "", "Specify the file to write metrics in json format, or empty to write to stdout (defaults to stdout)") } diff --git a/core/evm.go b/core/evm.go index 0b732bb8ae..eccd17b140 100644 --- a/core/evm.go +++ b/core/evm.go @@ -72,7 +72,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common // NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present // in header.Extra. // This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults -// directly rather than re-encode the latest results when executing each individaul transaction. +// directly rather than re-encode the latest results when executing each individual transaction. func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext { return newEVMBlockContext(header, chain, author, predicateResults) } diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go index d31bbf0fd8..c1d451d03c 100644 --- a/core/predicate_check_test.go +++ b/core/predicate_check_test.go @@ -244,7 +244,7 @@ func TestCheckPredicate(t *testing.T) { }, expectedErr: nil, }, - "two predicates niether named by access list": { + "two predicates neither named by access list": { gas: 61600, predicateContext: predicateContext, createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater { diff --git a/precompile/contracts/rewardmanager/contract.go b/precompile/contracts/rewardmanager/contract.go index 4d9b6224a8..602ecc3d67 100644 --- a/precompile/contracts/rewardmanager/contract.go +++ b/precompile/contracts/rewardmanager/contract.go @@ -167,7 +167,7 @@ func GetStoredRewardAddress(stateDB contract.StateDB) (common.Address, bool) { return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue } -// StoredRewardAddress stores the given [val] under rewardAddressStorageKey. +// StoreRewardAddress stores the given [val] under rewardAddressStorageKey. func StoreRewardAddress(stateDB contract.StateDB, val common.Address) { stateDB.SetState(ContractAddress, rewardAddressStorageKey, common.BytesToHash(val.Bytes())) } diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go index eeecdbe795..0d56cd217e 100644 --- a/precompile/contracts/rewardmanager/contract_test.go +++ b/precompile/contracts/rewardmanager/contract_test.go @@ -366,7 +366,7 @@ var ( ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, - "readOnly set reward addresss with allowed role fails": { + "readOnly set reward address with allowed role fails": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), InputFn: func(t testing.TB) []byte { From a4c71b10c5e0bfbe4829829bf60c5d970805d6bc Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 5 Mar 2025 00:19:55 +0300 Subject: [PATCH 46/57] bump version to v0.7.2 (#1467) --- .github/ISSUE_TEMPLATE/release_checklist.md | 2 +- README.md | 1 + RELEASES.md | 19 +++++++++++++++++-- compatibility.json | 1 + plugin/evm/version.go | 2 +- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md index f71bab419f..8bcb711591 100644 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -26,5 +26,5 @@ Link the relevant documentation PRs for this release. - [ ] Update AvalancheGo dependency in scripts/versions.sh for e2e tests. - [ ] Add new entry in compatibility.json for RPCChainVM Compatibility - [ ] Update AvalancheGo compatibility in README -- [ ] Deploy to WAGMI +- [ ] Deploy to Echo/Dispatch - [ ] Confirm goreleaser job has successfully generated binaries by checking the releases page diff --git a/README.md b/README.md index ec8c8dd99d..1c25e19e7d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and ```text [v0.7.0] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) [v0.7.1] AvalancheGo@v1.12.2 (Protocol Version: 39) +[v0.7.2] AvalancheGo@v1.12.2 (Protocol Version: 39) ``` ## API diff --git a/RELEASES.md b/RELEASES.md index 963817abed..1aa691d9f3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,10 +3,25 @@ ## Pending Release * Bump golang version to v1.23.6 * Bump golangci-lint to v1.63 and add linters -* Refactored trie_prefetcher.go to be structurally similar to upstream. -* Remove legacy gossip handler and metrics * Fixed concurrency issue in validators/uptime manager +## [v0.7.1](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.1) + +This release focuses on code quality improvements and post-Etna cleanups. + +### Compatibility + +The plugin version is **updated** to 39 and is compatible with AvalancheGo version v1.12.2. + +### Updates + +* Moved client type and structs to new `plugin/evm/client` package +* Fixed statedb improper copy issue +* Limited the maximum number of query-able rewardPercentile by 100 in `eth_feeHistory` API +* Refactored `trie_prefetcher.go` to be structurally similar to upstream +* Removed deprecated legacy gossip handler and metrics +* Removed unnecessary locks in mempool + ## [v0.7.0](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.0) ### Updates diff --git a/compatibility.json b/compatibility.json index c3090f5746..e2c9603394 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,6 @@ { "rpcChainVMProtocolVersion": { + "v0.7.2": 39, "v0.7.1": 39, "v0.7.0": 38 } diff --git a/plugin/evm/version.go b/plugin/evm/version.go index 158c6e1510..d6ccf8d553 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.7.1" + Version string = "v0.7.2" ) func init() { From f6f17a717dd64f17f9512be5a46d77fdb4227e39 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 5 Mar 2025 16:56:34 +0300 Subject: [PATCH 47/57] fix go mod path (#1468) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60d0921901..86fc3de66f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "go.mod" + go-version-file: "./subnet-evm/go.mod" - name: Set up arm64 cross compiler run: | sudo apt-get -y update From 8a40954d80701f9f7ab82635328a70c568241f27 Mon Sep 17 00:00:00 2001 From: Tsachi Herman <24438559+tsachiherman@users.noreply.github.com> Date: Thu, 6 Mar 2025 05:37:25 -0500 Subject: [PATCH 48/57] eliminate an impossible error case from state.WriteState (#1473) Signed-off-by: Tsachi Herman <24438559+tsachiherman@users.noreply.github.com> Co-authored-by: Darioush Jalali --- plugin/evm/validators/state/state.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/evm/validators/state/state.go b/plugin/evm/validators/state/state.go index c3150c10ac..2ffcf493a8 100644 --- a/plugin/evm/validators/state/state.go +++ b/plugin/evm/validators/state/state.go @@ -197,13 +197,14 @@ func (s *state) WriteState() error { if err := batch.Delete(vID[:]); err != nil { return err } - default: - return fmt.Errorf("unknown update status for %s", vID) } - // we're done, remove the updated marker - delete(s.updatedData, vID) } - return batch.Write() + if err := batch.Write(); err != nil { + return err + } + // we've successfully flushed the updates, clear the updated marker. + clear(s.updatedData) + return nil } // SetStatus sets the active status of the validator with the given vID From fe30ad73efc4450532f9755249ee872a1ee5ac17 Mon Sep 17 00:00:00 2001 From: huochexizhan Date: Mon, 10 Mar 2025 08:10:56 +0100 Subject: [PATCH 49/57] refactor: use slices.Equal to simplify code (#1470) Signed-off-by: huochexizhan --- precompile/allowlist/config.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/precompile/allowlist/config.go b/precompile/allowlist/config.go index 520021f511..f434d589b2 100644 --- a/precompile/allowlist/config.go +++ b/precompile/allowlist/config.go @@ -5,6 +5,7 @@ package allowlist import ( "fmt" + "slices" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" @@ -50,15 +51,7 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { // areEqualAddressLists returns true iff [a] and [b] have the same addresses in the same order. func areEqualAddressLists(current []common.Address, other []common.Address) bool { - if len(current) != len(other) { - return false - } - for i, address := range current { - if address != other[i] { - return false - } - } - return true + return slices.Equal(current, other) } // Verify returns an error if there is an overlapping address between admin and enabled roles From c5a095e7fbb5835cb049b41a9e8463635156ad5d Mon Sep 17 00:00:00 2001 From: Martin Eckardt Date: Mon, 10 Mar 2025 08:29:56 +0100 Subject: [PATCH 50/57] [simulator] Fix wrong flag name in readme (#1476) Signed-off-by: Martin Eckardt --- cmd/simulator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/simulator/README.md b/cmd/simulator/README.md index f05304c079..b199106934 100644 --- a/cmd/simulator/README.md +++ b/cmd/simulator/README.md @@ -40,7 +40,7 @@ Once you've built AvalancheGo, open the AvalancheGo directory in a separate term WARNING: -The staking-enabled flag is only for local testing. Disabling staking serves two functions explicitly for testing purposes: +The `--sybil-protection-enabled=false` flag is only suitable for local testing. Disabling staking serves two functions explicitly for testing purposes: 1. Ignore stake weight on the P-Chain and count each connected peer as having a stake weight of 1 2. Automatically opts in to validate every Subnet From fb35e423635e6ceac96de0adb54d84326c892b40 Mon Sep 17 00:00:00 2001 From: maru Date: Mon, 10 Mar 2025 10:31:21 +0100 Subject: [PATCH 51/57] Simplify avalanchego version maintenance (#1469) --- scripts/versions.sh | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/versions.sh b/scripts/versions.sh index 2b27dadefc..4da054716c 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -4,5 +4,25 @@ # shellcheck disable=SC2034 # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'f1ec9a13'} + +if [[ -z ${AVALANCHE_VERSION:-} ]]; then + # Get module details from go.mod + MODULE_DETAILS="$(go list -m "github.com/ava-labs/avalanchego" 2>/dev/null)" + + # Extract the version part + AVALANCHE_VERSION="$(echo "${MODULE_DETAILS}" | awk '{print $2}')" + + # Check if the version matches the pattern where the last part is the module hash + # v*YYYYMMDDHHMMSS-abcdef123456 + # + # If not, the value is assumed to represent a tag + if [[ "${AVALANCHE_VERSION}" =~ ^v.*[0-9]{14}-[0-9a-f]{12}$ ]]; then + # Extract module hash from version + MODULE_HASH="$(echo "${AVALANCHE_VERSION}" | cut -d'-' -f3)" + + # The first 8 chars of the hash is used as the tag of avalanchego images + AVALANCHE_VERSION="${MODULE_HASH::8}" + fi +fi + GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} From ecce706dc9fbae74e8e8f572c676fa432b443512 Mon Sep 17 00:00:00 2001 From: maru Date: Mon, 10 Mar 2025 15:35:34 +0100 Subject: [PATCH 52/57] [ci] Support providing avalanche version when triggering image build job (#1478) --- .github/workflows/publish_docker.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 6f1a89177a..407b373739 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -8,6 +8,11 @@ on: default: '' required: false type: string + avalanche_version: + description: 'The SHA or tag of avalanchego to use for the base image (must be compatible with the version in go.mod)' + default: '' + required: false + type: string push: tags: @@ -30,4 +35,5 @@ jobs: VM_ID: ${{ inputs.vm_id }} PUBLISH: 1 PLATFORMS: "linux/amd64,linux/arm64" + AVALANCHE_VERSION: ${{ inputs.avalanche_version }} run: scripts/build_docker_image.sh From 90865da609ac40926aa1e3a8c5fcb66c12017579 Mon Sep 17 00:00:00 2001 From: maru Date: Mon, 10 Mar 2025 19:09:17 +0100 Subject: [PATCH 53/57] [docker] Fix multi-arch builds for unmerged avalanchego versions (#1474) --- scripts/build_docker_image.sh | 28 +++++++++++++++++++++++----- scripts/tests.build_docker_image.sh | 8 ++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/scripts/build_docker_image.sh b/scripts/build_docker_image.sh index 1fdea150bb..48a9a016f8 100755 --- a/scripts/build_docker_image.sh +++ b/scripts/build_docker_image.sh @@ -50,6 +50,10 @@ fi if [[ -n "${PLATFORMS}" ]]; then DOCKER_CMD="${DOCKER_CMD} --platform=${PLATFORMS}" if [[ "$PLATFORMS" == *,* ]]; then ## Multi-arch + if [[ "${IMAGE_NAME}" != *"/"* ]]; then + echo "ERROR: Multi-arch images must be pushed to a registry." + exit 1 + fi ispush=1 fi fi @@ -76,14 +80,28 @@ AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_NODE_IMAGE:-${AVALANCHEGO_IMAGE_NAME}:${AV # Build the avalanchego image if it cannot be pulled. This will usually be due to # AVALANCHE_VERSION being not yet merged since the image is published post-merge. if ! docker pull "${AVALANCHEGO_NODE_IMAGE}"; then - # Use a image name without a repository (i.e. without 'avaplatform/' prefix ) to build a - # local image that will not be pushed. - export AVALANCHEGO_IMAGE_NAME="avalanchego" - echo "Building ${AVALANCHEGO_IMAGE_NAME}:${AVALANCHE_VERSION} locally" + # Build a multi-arch avalanchego image if the subnet-evm image build is multi-arch + BUILD_MULTI_ARCH="$([[ "$PLATFORMS" =~ , ]] && echo 1 || echo "")" + + # - Use a image name without a repository (i.e. without 'avaplatform/' prefix ) to build a + # local single-arch image that will not be pushed. + # - Use a image name with a repository to build a multi-arch image that will be pushed. + AVALANCHEGO_LOCAL_IMAGE_NAME="${AVALANCHEGO_LOCAL_IMAGE_NAME:-avalanchego}" + + if [[ -n "${BUILD_MULTI_ARCH}" && "${AVALANCHEGO_LOCAL_IMAGE_NAME}" != *"/"* ]]; then + echo "ERROR: Multi-arch images must be pushed to a registry." + exit 1 + fi + + AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_LOCAL_IMAGE_NAME}:${AVALANCHE_VERSION}" + echo "Building ${AVALANCHEGO_NODE_IMAGE} locally" source "${SUBNET_EVM_PATH}"/scripts/lib_avalanchego_clone.sh clone_avalanchego "${AVALANCHE_VERSION}" - SKIP_BUILD_RACE=1 DOCKER_IMAGE="${AVALANCHEGO_IMAGE_NAME}" "${AVALANCHEGO_CLONE_PATH}"/scripts/build_image.sh + SKIP_BUILD_RACE=1 \ + DOCKER_IMAGE="${AVALANCHEGO_LOCAL_IMAGE_NAME}" \ + BUILD_MULTI_ARCH="${BUILD_MULTI_ARCH}" \ + "${AVALANCHEGO_CLONE_PATH}"/scripts/build_image.sh fi echo "Building Docker Image: $IMAGE_NAME:$BUILD_IMAGE_ID based of AvalancheGo@$AVALANCHE_VERSION" diff --git a/scripts/tests.build_docker_image.sh b/scripts/tests.build_docker_image.sh index e556ceb500..a60f56006f 100755 --- a/scripts/tests.build_docker_image.sh +++ b/scripts/tests.build_docker_image.sh @@ -19,6 +19,9 @@ build_and_test() { local imagename="${1}" local vm_id="${2}" local multiarch_image="${3}" + # The local image name will be used to build a local image if the + # current avalanchego version lacks a published image. + local avalanchego_local_image_name="${4}" if [[ "${multiarch_image}" == true ]]; then local arches="linux/amd64,linux/arm64" @@ -35,6 +38,7 @@ build_and_test() { BUILD_IMAGE_ID="${imgtag}" \ VM_ID=$"${vm_id}" \ IMAGE_NAME="${imagename}" \ + AVALANCHEGO_LOCAL_IMAGE_NAME="${avalanchego_local_image_name}" \ ./scripts/build_docker_image.sh echo "listing images" @@ -57,7 +61,7 @@ build_and_test() { VM_ID="${VM_ID:-${DEFAULT_VM_ID}}" echo "checking build of single-arch image" -build_and_test "subnet-evm" "${VM_ID}" false +build_and_test "subnet-evm" "${VM_ID}" false "avalanchego" echo "starting local docker registry to allow verification of multi-arch image builds" REGISTRY_CONTAINER_ID="$(docker run --rm -d -P registry:2)" @@ -77,4 +81,4 @@ function cleanup { trap cleanup EXIT echo "checking build of multi-arch images" -build_and_test "localhost:${REGISTRY_PORT}/subnet-evm" "${VM_ID}" true +build_and_test "localhost:${REGISTRY_PORT}/subnet-evm" "${VM_ID}" true "localhost:${REGISTRY_PORT}/avalanchego" From 4780d74a847e6bccb40f47b074a29f3ae9cc88a0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 10 Mar 2025 22:28:42 +0300 Subject: [PATCH 54/57] remove ctx locks and use chain locks (#1483) Signed-off-by: Ceyhun Onur Co-authored-by: Darioush Jalali --- plugin/evm/admin.go | 20 +++---- plugin/evm/service.go | 4 +- .../evm/validators/interfaces/interfaces.go | 11 ++-- plugin/evm/validators/locked_reader.go | 53 ++++++++++++++++++ plugin/evm/validators/manager.go | 55 +++---------------- plugin/evm/vm.go | 17 +++++- plugin/evm/vm_validators_test.go | 4 +- warp/verifier_backend_test.go | 5 +- 8 files changed, 98 insertions(+), 71 deletions(-) create mode 100644 plugin/evm/validators/locked_reader.go diff --git a/plugin/evm/admin.go b/plugin/evm/admin.go index 37b4e78e12..e35413d6cf 100644 --- a/plugin/evm/admin.go +++ b/plugin/evm/admin.go @@ -30,8 +30,8 @@ func NewAdminService(vm *VM, performanceDir string) *Admin { func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StartCPUProfiler called") - p.vm.ctx.Lock.Lock() - defer p.vm.ctx.Lock.Unlock() + p.vm.vmLock.Lock() + defer p.vm.vmLock.Unlock() return p.profiler.StartCPUProfiler() } @@ -40,8 +40,8 @@ func (p *Admin) StartCPUProfiler(_ *http.Request, _ *struct{}, _ *api.EmptyReply func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: StopCPUProfiler called") - p.vm.ctx.Lock.Lock() - defer p.vm.ctx.Lock.Unlock() + p.vm.vmLock.Lock() + defer p.vm.vmLock.Unlock() return p.profiler.StopCPUProfiler() } @@ -50,8 +50,8 @@ func (p *Admin) StopCPUProfiler(r *http.Request, _ *struct{}, _ *api.EmptyReply) func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: MemoryProfile called") - p.vm.ctx.Lock.Lock() - defer p.vm.ctx.Lock.Unlock() + p.vm.vmLock.Lock() + defer p.vm.vmLock.Unlock() return p.profiler.MemoryProfile() } @@ -60,8 +60,8 @@ func (p *Admin) MemoryProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) e func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) error { log.Info("Admin: LockProfile called") - p.vm.ctx.Lock.Lock() - defer p.vm.ctx.Lock.Unlock() + p.vm.vmLock.Lock() + defer p.vm.vmLock.Unlock() return p.profiler.LockProfile() } @@ -69,8 +69,8 @@ func (p *Admin) LockProfile(_ *http.Request, _ *struct{}, _ *api.EmptyReply) err func (p *Admin) SetLogLevel(_ *http.Request, args *client.SetLogLevelArgs, reply *api.EmptyReply) error { log.Info("EVM: SetLogLevel called", "logLevel", args.Level) - p.vm.ctx.Lock.Lock() - defer p.vm.ctx.Lock.Unlock() + p.vm.vmLock.Lock() + defer p.vm.vmLock.Unlock() if err := p.vm.logger.SetLogLevel(args.Level); err != nil { return fmt.Errorf("failed to parse log level: %w ", err) diff --git a/plugin/evm/service.go b/plugin/evm/service.go index ea57b15e26..70b92bd538 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -18,8 +18,8 @@ type ValidatorsAPI struct { } func (api *ValidatorsAPI) GetCurrentValidators(_ *http.Request, req *client.GetCurrentValidatorsRequest, reply *client.GetCurrentValidatorsResponse) error { - api.vm.ctx.Lock.RLock() - defer api.vm.ctx.Lock.RUnlock() + api.vm.vmLock.RLock() + defer api.vm.vmLock.RUnlock() var vIDs set.Set[ids.ID] if len(req.NodeIDs) > 0 { diff --git a/plugin/evm/validators/interfaces/interfaces.go b/plugin/evm/validators/interfaces/interfaces.go index 8a7d3a2621..aeac266af9 100644 --- a/plugin/evm/validators/interfaces/interfaces.go +++ b/plugin/evm/validators/interfaces/interfaces.go @@ -5,6 +5,7 @@ package interfaces import ( "context" + "sync" "time" "github.com/ava-labs/avalanchego/ids" @@ -15,23 +16,19 @@ import ( type ValidatorReader interface { // GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID // and the last updated time. - // GetValidatorAndUptime holds the chain context lock while performing the operation and can be called concurrently. + // GetValidatorAndUptime holds the VM lock while performing the operation and can be called concurrently. GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) } type Manager interface { - stateinterfaces.State + stateinterfaces.StateReader avalancheuptime.Manager - ValidatorReader // Initialize initializes the validator manager // by syncing the validator state with the current validator set // and starting the uptime tracking. - // Initialize holds the chain context lock while performing the operation. Initialize(ctx context.Context) error // Shutdown stops the uptime tracking and writes the validator state to the database. - // Shutdown holds the chain context lock while performing the operation. Shutdown() error // DispatchSync starts the sync process - // DispatchSync holds the chain context lock while performing the sync. - DispatchSync(ctx context.Context) + DispatchSync(ctx context.Context, lock sync.Locker) } diff --git a/plugin/evm/validators/locked_reader.go b/plugin/evm/validators/locked_reader.go new file mode 100644 index 0000000000..f4b12f8eb9 --- /dev/null +++ b/plugin/evm/validators/locked_reader.go @@ -0,0 +1,53 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package validators + +import ( + "fmt" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" + stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" +) + +type RLocker interface { + RLock() + RUnlock() +} + +type lockedReader struct { + manager interfaces.Manager + lock RLocker +} + +func NewLockedValidatorReader( + manager interfaces.Manager, + lock RLocker, +) interfaces.ValidatorReader { + return &lockedReader{ + lock: lock, + manager: manager, + } +} + +// GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID +// and the last updated time. +// GetValidatorAndUptime holds the lock while performing the operation and can be called concurrently. +func (l *lockedReader) GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) { + l.lock.RLock() + defer l.lock.RUnlock() + + vdr, err := l.manager.GetValidator(validationID) + if err != nil { + return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get validator: %w", err) + } + + uptime, lastUpdated, err := l.manager.CalculateUptime(vdr.NodeID) + if err != nil { + return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get uptime: %w", err) + } + + return vdr, uptime, lastUpdated, nil +} diff --git a/plugin/evm/validators/manager.go b/plugin/evm/validators/manager.go index 738b9d6257..106a365400 100644 --- a/plugin/evm/validators/manager.go +++ b/plugin/evm/validators/manager.go @@ -6,6 +6,7 @@ package validators import ( "context" "fmt" + "sync" "time" "github.com/ava-labs/avalanchego/database" @@ -14,7 +15,6 @@ import ( avalancheuptime "github.com/ava-labs/avalanchego/snow/uptime" avalanchevalidators "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/subnet-evm/plugin/evm/validators/interfaces" validators "github.com/ava-labs/subnet-evm/plugin/evm/validators/state" stateinterfaces "github.com/ava-labs/subnet-evm/plugin/evm/validators/state/interfaces" "github.com/ava-labs/subnet-evm/plugin/evm/validators/uptime" @@ -35,11 +35,12 @@ type manager struct { // NewManager returns a new validator manager // that manages the validator state and the uptime manager. +// Manager is not thread safe and should be used with the VM locked. func NewManager( ctx *snow.Context, db database.Database, clock *mockable.Clock, -) (interfaces.Manager, error) { +) (*manager, error) { validatorState, err := validators.NewState(db) if err != nil { return nil, fmt.Errorf("failed to initialize validator state: %w", err) @@ -59,10 +60,7 @@ func NewManager( // Initialize initializes the validator manager // by syncing the validator state with the current validator set // and starting the uptime tracking. -// Initialize holds the chain context lock while performing the operation. func (m *manager) Initialize(ctx context.Context) error { - m.chainCtx.Lock.Lock() - defer m.chainCtx.Lock.Unlock() // sync validators first if err := m.sync(ctx); err != nil { return fmt.Errorf("failed to update validators: %w", err) @@ -78,10 +76,7 @@ func (m *manager) Initialize(ctx context.Context) error { } // Shutdown stops the uptime tracking and writes the validator state to the database. -// Shutdown holds the chain context lock while performing the operation. func (m *manager) Shutdown() error { - m.chainCtx.Lock.Lock() - defer m.chainCtx.Lock.Unlock() vdrIDs := m.GetNodeIDs().List() if err := m.StopTracking(vdrIDs); err != nil { return fmt.Errorf("failed to stop tracking uptime: %w", err) @@ -92,54 +87,20 @@ func (m *manager) Shutdown() error { return nil } -func (m *manager) Connect(nodeID ids.NodeID) error { - m.chainCtx.Lock.Lock() - defer m.chainCtx.Lock.Unlock() - return m.PausableManager.Connect(nodeID) -} - -func (m *manager) Disconnect(nodeID ids.NodeID) error { - m.chainCtx.Lock.Lock() - defer m.chainCtx.Lock.Unlock() - return m.PausableManager.Disconnect(nodeID) -} - -// GetValidatorAndUptime returns the calculated uptime of the validator specified by validationID -// and the last updated time. -// GetValidatorAndUptime holds the chain context lock while performing the operation and can be called concurrently. -func (m *manager) GetValidatorAndUptime(validationID ids.ID) (stateinterfaces.Validator, time.Duration, time.Time, error) { - // lock the state - m.chainCtx.Lock.RLock() - defer m.chainCtx.Lock.RUnlock() - - // Get validator first - vdr, err := m.GetValidator(validationID) - if err != nil { - return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get validator: %w", err) - } - - uptime, lastUpdated, err := m.CalculateUptime(vdr.NodeID) - if err != nil { - return stateinterfaces.Validator{}, 0, time.Time{}, fmt.Errorf("failed to get uptime: %w", err) - } - - return vdr, uptime, lastUpdated, nil -} - // DispatchSync starts the sync process -// DispatchSync holds the chain context lock while performing the sync. -func (m *manager) DispatchSync(ctx context.Context) { +// DispatchSync holds the given lock while performing the sync. +func (m *manager) DispatchSync(ctx context.Context, lock sync.Locker) { ticker := time.NewTicker(SyncFrequency) defer ticker.Stop() for { select { case <-ticker.C: - m.chainCtx.Lock.Lock() + lock.Lock() if err := m.sync(ctx); err != nil { log.Error("failed to sync validators", "error", err) } - m.chainCtx.Lock.Unlock() + lock.Unlock() case <-ctx.Done(): return } @@ -148,7 +109,7 @@ func (m *manager) DispatchSync(ctx context.Context) { // sync synchronizes the validator state with the current validator set // and writes the state to the database. -// sync is not safe to call concurrently and should be called with the chain context locked. +// sync is not safe to call concurrently and should be called with the VM locked. func (m *manager) sync(ctx context.Context) error { now := time.Now() log.Debug("performing validator sync") diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 32790c8768..c4c6bb0fc6 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -177,6 +177,9 @@ var legacyApiNames = map[string]string{ // VM implements the snowman.ChainVM interface type VM struct { ctx *snow.Context + // contextLock is used to coordinate global VM operations. + // This can be used safely instead of snow.Context.Lock which is deprecated and should not be used in rpcchainvm. + vmLock sync.RWMutex // [cancel] may be nil until [snow.NormalOp] starts cancel context.CancelFunc // *chain.State helps to implement the VM interface by wrapping blocks @@ -521,7 +524,7 @@ func (vm *VM) Initialize( vm.ctx.ChainID, vm.ctx.WarpSigner, vm, - vm.validatorsManager, + validators.NewLockedValidatorReader(vm.validatorsManager, &vm.vmLock), vm.warpDB, meteredCache, offchainWarpMessages, @@ -680,6 +683,8 @@ func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { } func (vm *VM) SetState(_ context.Context, state snow.State) error { + vm.vmLock.Lock() + defer vm.vmLock.Unlock() switch state { case snow.StateSyncing: vm.bootstrapped.Set(false) @@ -728,7 +733,7 @@ func (vm *VM) onNormalOperationsStarted() error { // dispatch validator set update vm.shutdownWg.Add(1) go func() { - vm.validatorsManager.DispatchSync(ctx) + vm.validatorsManager.DispatchSync(ctx, &vm.vmLock) vm.shutdownWg.Done() }() @@ -862,6 +867,8 @@ func (vm *VM) setAppRequestHandlers() { // Shutdown implements the snowman.ChainVM interface func (vm *VM) Shutdown(context.Context) error { + vm.vmLock.Lock() + defer vm.vmLock.Unlock() if vm.ctx == nil { return nil } @@ -1254,6 +1261,9 @@ func attachEthService(handler *rpc.Server, apis []rpc.API, names []string) error } func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { + vm.vmLock.Lock() + defer vm.vmLock.Unlock() + if err := vm.validatorsManager.Connect(nodeID); err != nil { return fmt.Errorf("uptime manager failed to connect node %s: %w", nodeID, err) } @@ -1261,6 +1271,9 @@ func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version } func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { + vm.vmLock.Lock() + defer vm.vmLock.Unlock() + if err := vm.validatorsManager.Disconnect(nodeID); err != nil { return fmt.Errorf("uptime manager failed to disconnect node %s: %w", nodeID, err) } diff --git a/plugin/evm/vm_validators_test.go b/plugin/evm/vm_validators_test.go index d8d0719fde..5e7fa523ca 100644 --- a/plugin/evm/vm_validators_test.go +++ b/plugin/evm/vm_validators_test.go @@ -151,8 +151,8 @@ func TestValidatorState(t *testing.T) { // new validator should be added to the state eventually after SyncFrequency require.EventuallyWithT(func(c *assert.CollectT) { - vm.ctx.Lock.Lock() - defer vm.ctx.Lock.Unlock() + vm.vmLock.Lock() + defer vm.vmLock.Unlock() assert.Len(c, vm.validatorsManager.GetNodeIDs(), 4) newValidator, err := vm.validatorsManager.GetValidator(newValidationID) assert.NoError(c, err) diff --git a/warp/verifier_backend_test.go b/warp/verifier_backend_test.go index 276b9592d9..bfcdd0986b 100644 --- a/warp/verifier_backend_test.go +++ b/warp/verifier_backend_test.go @@ -5,6 +5,7 @@ package warp import ( "context" + "sync" "testing" "time" @@ -291,8 +292,10 @@ func TestUptimeSignatures(t *testing.T) { clk := &mockable.Clock{} validatorsManager, err := validators.NewManager(chainCtx, memdb.New(), clk) require.NoError(t, err) + lock := &sync.RWMutex{} + newLockedValidatorManager := validators.NewLockedValidatorReader(validatorsManager, lock) validatorsManager.StartTracking([]ids.NodeID{}) - warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, validatorsManager, database, sigCache, nil) + warpBackend, err := NewBackend(snowCtx.NetworkID, snowCtx.ChainID, warpSigner, warptest.EmptyBlockClient, newLockedValidatorManager, database, sigCache, nil) require.NoError(t, err) handler := acp118.NewCachedHandler(sigCache, warpBackend, warpSigner) From 5516d298c9f2e3403f770b6a92e6397fe396bf0d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Mar 2025 17:52:47 +0300 Subject: [PATCH 55/57] chore(ci): add merge groups (#1485) --- .github/workflows/bench.yml | 2 ++ .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/tests.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a8d5d82fe6..2f850a9591 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -3,6 +3,8 @@ name: Bench on: workflow_dispatch: pull_request: + merge_group: + types: [checks_requested] jobs: bench: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 639963adec..9112587cdc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -17,6 +17,8 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ master ] + merge_group: + types: [checks_requested] schedule: - cron: '44 11 * * 4' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a8866b323d..80076072c3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,8 @@ on: tags: - "*" pull_request: + merge_group: + types: [checks_requested] jobs: lint_test: From 950bafef2576fc260b004e4e1fa78bcd5306f297 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Mar 2025 21:17:11 +0300 Subject: [PATCH 56/57] update after 0.7.2 release (#1486) --- README.md | 2 +- RELEASES.md | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c25e19e7d..060590866e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and ```text [v0.7.0] AvalancheGo@v1.12.0-v1.12.1 (Protocol Version: 38) [v0.7.1] AvalancheGo@v1.12.2 (Protocol Version: 39) -[v0.7.2] AvalancheGo@v1.12.2 (Protocol Version: 39) +[v0.7.2] AvalancheGo@v1.12.2/1.13.0-fuji (Protocol Version: 39) ``` ## API diff --git a/RELEASES.md b/RELEASES.md index 1aa691d9f3..0ad598edd8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,20 @@ # Release Notes ## Pending Release + +## [v0.7.2](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.2) + +This version is backwards compatible to [v0.7.0](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.0). It is optional, **but strongly encouraged as it's fixing an important bug in uptime tracking.** + +### AvalancheGo Compatibility + +The plugin version is unchanged at 39 and is compatible with AvalancheGo version v1.12.2. + +### Updates + +* Fixed concurrency issue in validators/uptime manager * Bump golang version to v1.23.6 * Bump golangci-lint to v1.63 and add linters -* Fixed concurrency issue in validators/uptime manager ## [v0.7.1](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.1) From 88182c1b5a2f70b6e662d4943a2a9c950a5b36f7 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Mar 2025 19:52:19 +0300 Subject: [PATCH 57/57] Update avago 1.12.3 rc1 (#1492) Co-authored-by: Richard Pringle Co-authored-by: Darioush Jalali --- go.mod | 17 +++---- go.sum | 42 +++++++++------- plugin/evm/vm_warp_test.go | 11 +++-- precompile/contracts/warp/predicate_test.go | 10 +++- .../warp/signature_verification_test.go | 48 ++++++++++++------- tests/antithesis/Dockerfile.node | 7 ++- warp/aggregator/aggregator_test.go | 12 +++-- 7 files changed, 94 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index c77e869795..fcb1942881 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.6 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a + github.com/ava-labs/avalanchego v1.12.3-rc.1 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -36,28 +36,29 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/status-im/keycard-go v0.2.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.25.7 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.5.0 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.31.0 - golang.org/x/exp v0.0.0-20231127185646-65229373498e - golang.org/x/mod v0.18.0 + golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e + golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 golang.org/x/time v0.3.0 - golang.org/x/tools v0.22.0 - google.golang.org/protobuf v1.34.2 + golang.org/x/tools v0.28.0 + google.golang.org/protobuf v1.35.2 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3 // indirect + github.com/StephenButtolph/canoto v0.10.0 // indirect + github.com/ava-labs/coreth v0.14.1-rc.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -72,7 +73,7 @@ require ( github.com/compose-spec/compose-go v1.20.2 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/distribution/reference v0.5.0 // indirect diff --git a/go.sum b/go.sum index f5f6740c90..ddd7bea8cf 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/StephenButtolph/canoto v0.10.0 h1:KdW85TYQXH+gwR8vOxfOUf28TRpkLU+X06Kycg1IR7s= +github.com/StephenButtolph/canoto v0.10.0/go.mod h1:MxppdgKRApRBvIg4ZgO2e14m/NSBjFMuydy97OB/gYY= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -60,10 +62,10 @@ github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a h1:cBuGdwpaB1TBnfHS0w7oJyBJlM2UGOS4CEioxqMCG2g= -github.com/ava-labs/avalanchego v1.12.3-0.20250218154446-f1ec9a13b90a/go.mod h1:pgUuh27zaeaXeMiIqqu/OWpbAhJQkYMJMft6nxXG9FE= -github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3 h1:0OrQidq+b3kb7KlnDqc5mkB/73i1zxYEMiXioWSU4+w= -github.com/ava-labs/coreth v0.14.1-rc.1.0.20250217192834-6060a4a6a6d3/go.mod h1:/XrU+CAm/0wiY5Ui05TLj2/n+czbCV6/Qrw/UMWp5aI= +github.com/ava-labs/avalanchego v1.12.3-rc.1 h1:p56A8bzbSCzz1OvXkIuGSyHsCj5MAzRMH6U4VNME/cQ= +github.com/ava-labs/avalanchego v1.12.3-rc.1/go.mod h1:odvg1kVIdtPBFE5LRzwdPhGC16TnkOGVO4k8sZlDs6E= +github.com/ava-labs/coreth v0.14.1-rc.3 h1:4MXKCgW7kUuKsssRiN9Pl8hWFRKuanD13/v1OtpQJPw= +github.com/ava-labs/coreth v0.14.1-rc.3/go.mod h1:gIGr+5WDNX1DrFvUMy53AtTpkxlM/8cNOD/PDIChKfM= 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= @@ -136,8 +138,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc 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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= @@ -378,6 +380,8 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 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= @@ -468,6 +472,8 @@ 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= @@ -581,8 +587,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= @@ -590,8 +596,8 @@ github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 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.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw= -github.com/thepudds/fzgen v0.4.2/go.mod h1:kHCWdsv5tdnt32NIHYDdgq083m6bMtaY0M+ipiO9xWE= +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= @@ -683,8 +689,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= -golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -711,8 +717,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -930,8 +936,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 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= @@ -1039,8 +1045,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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= diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 03e30932a9..17e7f7c29c 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -284,13 +284,15 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned blsSecretKey1, err := localsigner.New() require.NoError(err) blsPublicKey1 := blsSecretKey1.PublicKey() - blsSignature1 := blsSecretKey1.Sign(unsignedMessage.Bytes()) + blsSignature1, err := blsSecretKey1.Sign(unsignedMessage.Bytes()) + require.NoError(err) nodeID2 := ids.GenerateTestNodeID() blsSecretKey2, err := localsigner.New() require.NoError(err) blsPublicKey2 := blsSecretKey2.PublicKey() - blsSignature2 := blsSecretKey2.Sign(unsignedMessage.Bytes()) + blsSignature2, err := blsSecretKey2.Sign(unsignedMessage.Bytes()) + require.NoError(err) blsAggregatedSignature, err := bls.AggregateSignatures([]*bls.Signature{blsSignature1, blsSignature2}) require.NoError(err) @@ -553,11 +555,14 @@ func testReceiveWarpMessage( newSigner := func(networkID ids.ID, weight uint64) signer { secret, err := localsigner.New() require.NoError(err) + sig, err := secret.Sign(unsignedMessage.Bytes()) + require.NoError(err) + return signer{ networkID: networkID, nodeID: ids.GenerateTestNodeID(), secret: secret, - signature: secret.Sign(unsignedMessage.Bytes()), + signature: sig, weight: weight, } } diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index 35afb24ed9..460dd4be2c 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -94,7 +94,10 @@ func init() { } for _, testVdr := range testVdrs { - blsSignature := testVdr.sk.Sign(unsignedMsg.Bytes()) + blsSignature, err := testVdr.sk.Sign(unsignedMsg.Bytes()) + if err != nil { + panic(err) + } blsSignatures = append(blsSignatures, blsSignature) } @@ -235,13 +238,16 @@ func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigner getValidatorsOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) blsSignatures := make([]*bls.Signature, 0, numKeys) for i := 0; i < numKeys; i++ { + sig, err := testVdrs[i].sk.Sign(unsignedMsg.Bytes()) + require.NoError(err) + validatorOutput := &validators.GetValidatorOutput{ NodeID: testVdrs[i].nodeID, Weight: 20, PublicKey: testVdrs[i].vdr.PublicKey, } getValidatorsOutput[testVdrs[i].nodeID] = validatorOutput - blsSignatures = append(blsSignatures, testVdrs[i].sk.Sign(unsignedMsg.Bytes())) + blsSignatures = append(blsSignatures, sig) } aggregateSignature, err := bls.AggregateSignatures(blsSignatures) require.NoError(err) diff --git a/precompile/contracts/warp/signature_verification_test.go b/precompile/contracts/warp/signature_verification_test.go index 90ee808f91..e3b33e2d4d 100644 --- a/precompile/contracts/warp/signature_verification_test.go +++ b/precompile/contracts/warp/signature_verification_test.go @@ -213,8 +213,10 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) - vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) + vdr0Sig, err := testVdrs[0].sk.Sign(unsignedBytes) + require.NoError(err) + vdr1Sig, err := testVdrs[1].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -285,7 +287,8 @@ func TestSignatureVerification(t *testing.T) { require.NoError(err) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) + vdr0Sig, err := testVdrs[0].sk.Sign(unsignedBytes) + require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig)) @@ -324,10 +327,12 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) + vdr0Sig, err := testVdrs[0].sk.Sign(unsignedBytes) + require.NoError(err) // Give sig from vdr[2] even though the bit vector says it // should be from vdr[1] - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -368,7 +373,8 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) + vdr0Sig, err := testVdrs[0].sk.Sign(unsignedBytes) + require.NoError(err) // Don't give the sig from vdr[1] aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr0Sig)) @@ -408,11 +414,14 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) unsignedBytes := unsignedMsg.Bytes() - vdr0Sig := testVdrs[0].sk.Sign(unsignedBytes) - vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) + vdr0Sig, err := testVdrs[0].sk.Sign(unsignedBytes) + require.NoError(err) + vdr1Sig, err := testVdrs[1].sk.Sign(unsignedBytes) + require.NoError(err) // Give sig from vdr[2] even though the bit vector doesn't have // it - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr0Sig, vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -455,8 +464,10 @@ func TestSignatureVerification(t *testing.T) { signers.Add(2) unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr1Sig, err := testVdrs[1].sk.Sign(unsignedBytes) + require.NoError(err) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -499,8 +510,10 @@ func TestSignatureVerification(t *testing.T) { signers.Add(2) unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr1Sig, err := testVdrs[1].sk.Sign(unsignedBytes) + require.NoError(err) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -560,8 +573,10 @@ func TestSignatureVerification(t *testing.T) { signers.Add(1) // vdr[2] unsignedBytes := unsignedMsg.Bytes() - vdr1Sig := testVdrs[1].sk.Sign(unsignedBytes) - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr1Sig, err := testVdrs[1].sk.Sign(unsignedBytes) + require.NoError(err) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSig, err := bls.AggregateSignatures([]*bls.Signature{vdr1Sig, vdr2Sig}) require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} @@ -622,7 +637,8 @@ func TestSignatureVerification(t *testing.T) { unsignedBytes := unsignedMsg.Bytes() // Because vdr[1] and vdr[2] share a key, only one of them sign. - vdr2Sig := testVdrs[2].sk.Sign(unsignedBytes) + vdr2Sig, err := testVdrs[2].sk.Sign(unsignedBytes) + require.NoError(err) aggSigBytes := [bls.SignatureLen]byte{} copy(aggSigBytes[:], bls.SignatureToBytes(vdr2Sig)) diff --git a/tests/antithesis/Dockerfile.node b/tests/antithesis/Dockerfile.node index 67538fefad..ebd9711247 100644 --- a/tests/antithesis/Dockerfile.node +++ b/tests/antithesis/Dockerfile.node @@ -25,8 +25,11 @@ COPY --from=builder /build/commit_hash.txt /avalanchego/build/commit_hash.txt # Copy the antithesis dependencies into the container COPY --from=builder /instrumented/symbols /symbols +# Configure the node with the location of the plugin +ENV AVAGO_PLUGIN_DIR=/avalanchego/build/plugins + # Copy the executable into the container -COPY --from=builder /build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy\ - /avalanchego/build/plugins/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy +COPY --from=builder $BUILDER_WORKDIR/build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy\ + $AVAGO_PLUGIN_DIR/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy # The node image's entrypoint will be reused. diff --git a/warp/aggregator/aggregator_test.go b/warp/aggregator/aggregator_test.go index 397e39553d..2a976bb662 100644 --- a/warp/aggregator/aggregator_test.go +++ b/warp/aggregator/aggregator_test.go @@ -44,9 +44,12 @@ func TestAggregateSignatures(t *testing.T) { vdr1sk, vdr1 := newValidator(t, vdrWeight) vdr2sk, vdr2 := newValidator(t, vdrWeight+1) vdr3sk, vdr3 := newValidator(t, vdrWeight-1) - sig1 := vdr1sk.Sign(unsignedMsg.Bytes()) - sig2 := vdr2sk.Sign(unsignedMsg.Bytes()) - sig3 := vdr3sk.Sign(unsignedMsg.Bytes()) + sig1, err := vdr1sk.Sign(unsignedMsg.Bytes()) + require.NoError(t, err) + sig2, err := vdr2sk.Sign(unsignedMsg.Bytes()) + require.NoError(t, err) + sig3, err := vdr3sk.Sign(unsignedMsg.Bytes()) + require.NoError(t, err) vdrToSig := map[*avalancheWarp.Validator]*bls.Signature{ vdr1: sig1, vdr2: sig2, @@ -54,7 +57,8 @@ func TestAggregateSignatures(t *testing.T) { } nonVdrSk, err := localsigner.New() require.NoError(t, err) - nonVdrSig := nonVdrSk.Sign(unsignedMsg.Bytes()) + nonVdrSig, err := nonVdrSk.Sign(unsignedMsg.Bytes()) + require.NoError(t, err) vdrs := []*avalancheWarp.Validator{ { PublicKey: vdr1.PublicKey,