From d1615a918d9148498b53b4513d47bbc4f28a3efb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 4 Sep 2024 10:27:05 -0700 Subject: [PATCH] Sync to changes in subnet-evm up to 252592ae (#645) * format: as subnet-evm * Sync to subnet-evm at 8e9dbc0f * format: as coreth * nits * change avalanchego to latest on fix-coreth-chain-configs * go mod tidy * nit * a bit closer to subnet-evm * move handling of flakey test * blockchain: Fix blockCache for rejected blocks (#1326) * bump avalanchego to master commit --- .github/CODEOWNERS | 3 +- .golangci.yml | 2 - accounts/keystore/keystore_test.go | 3 - core/blockchain.go | 29 +-- core/blockchain_test.go | 4 +- core/genesis.go | 2 +- core/genesis_test.go | 2 +- core/state_processor_test.go | 4 +- core/test_blockchain.go | 1 + eth/backend.go | 1 + eth/filters/filter_system.go | 15 +- eth/tracers/api.go | 4 + go.mod | 2 +- go.sum | 4 +- internal/ethapi/api_test.go | 1 - internal/ethapi/transaction_args.go | 17 +- metrics/sample_test.go | 4 - params/config.go | 4 +- plugin/evm/gossiper_atomic_gossiping_test.go | 4 - plugin/evm/gossiper_eth_gossiping_test.go | 4 - plugin/evm/tx_gossip_test.go | 6 +- plugin/evm/vm.go | 26 +- plugin/evm/vm_test.go | 20 +- plugin/evm/vm_warp_test.go | 230 +++++++++++++++--- precompile/contracts/warp/config.go | 22 +- precompile/contracts/warp/config_test.go | 18 +- precompile/contracts/warp/predicate_test.go | 27 +- .../warp/signature_verification_test.go | 44 ++-- scripts/build_test.sh | 42 +++- scripts/constants.sh | 2 + scripts/known_flakes.txt | 11 + scripts/shellcheck.sh | 18 +- scripts/versions.sh | 2 +- utils/snow.go | 2 +- warp/service.go | 24 +- warp/validators/state.go | 24 +- warp/validators/state_test.go | 5 +- 37 files changed, 435 insertions(+), 198 deletions(-) create mode 100644 scripts/known_flakes.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4de07544cd..0362e905b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,4 +6,5 @@ # review whenever someone opens a pull request. -* @darioush @ceyonur +* @ceyonur @darioush @ava-labs/platform-evm + diff --git a/.golangci.yml b/.golangci.yml index 31c41897e1..c6c8d5748a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,8 +8,6 @@ run: skip-dirs-use-default: true # Include non-test files tagged as test-only. # Context: https://github.com/ava-labs/avalanchego/pull/3173 - build-tags: - - test linters: disable-all: true diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index ed51fb8e76..25dc517c6b 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -293,9 +293,6 @@ type walletEvent struct { // Tests that wallet notifications and correctly fired when accounts are added // or deleted from the keystore. func TestWalletNotifications(t *testing.T) { - if os.Getenv("RUN_FLAKY_TESTS") != "true" { - t.Skip("FLAKY") - } t.Parallel() _, ks := tmpKeyStore(t, false) diff --git a/core/blockchain.go b/core/blockchain.go index cac2bb82b9..f02bb563e2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1165,6 +1165,9 @@ func (bc *BlockChain) Reject(block *types.Block) error { return fmt.Errorf("failed to write delete block batch: %w", err) } + // Remove the block from the block cache (ignore return value of whether it was in the cache) + _ = bc.blockCache.Remove(block.Hash()) + return nil } @@ -1721,32 +1724,6 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e log.Debug(reason.String()) } -func (bc *BlockChain) RemoveRejectedBlocks(start, end uint64) error { - batch := bc.db.NewBatch() - - for i := start; i < end; i++ { - hashes := rawdb.ReadAllHashes(bc.db, i) - canonicalBlock := bc.GetBlockByNumber((i)) - if canonicalBlock == nil { - return fmt.Errorf("failed to retrieve block by number at height %d", i) - } - canonicalHash := canonicalBlock.Hash() - for _, hash := range hashes { - if hash == canonicalHash { - continue - } - rawdb.DeleteBlock(batch, hash, i) - } - - if err := batch.Write(); err != nil { - return fmt.Errorf("failed to write delete rejected block batch at height %d", i) - } - batch.Reset() - } - - return nil -} - // reprocessBlock reprocesses a previously accepted block. This is often used // to regenerate previously pruned state tries. func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) (common.Hash, error) { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 069d8fd8fc..26d0b3e313 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -9,6 +9,7 @@ import ( "os" "testing" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state" @@ -1212,7 +1213,8 @@ func TestEIP3651(t *testing.T) { addr2 = crypto.PubkeyToAddress(key2.PublicKey) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) gspec = &Genesis{ - Config: params.TestChainConfig, + Config: params.TestChainConfig, + Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), Alloc: GenesisAlloc{ addr1: {Balance: funds}, addr2: {Balance: funds}, diff --git a/core/genesis.go b/core/genesis.go index bb8de14c80..2bcafd9a7a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -111,8 +111,8 @@ type genesisSpecMarshaling struct { GasUsed math.HexOrDecimal64 Number math.HexOrDecimal64 Difficulty *math.HexOrDecimal256 - BaseFee *math.HexOrDecimal256 Alloc map[common.UnprefixedAddress]GenesisAccount + BaseFee *math.HexOrDecimal256 ExcessBlobGas *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64 } diff --git a/core/genesis_test.go b/core/genesis_test.go index 5deb4a8bf9..9f300942a6 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -258,7 +258,7 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { genesis.Config.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{ { - Config: warp.NewConfig(utils.NewUint64(51), 0), + Config: warp.NewConfig(utils.NewUint64(51), 0, false), }, } _, _, err = SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 5646604d11..d64ed0b65b 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -31,6 +31,7 @@ import ( "math/big" "testing" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/consensus/misc/eip4844" @@ -108,7 +109,8 @@ func TestStateProcessorErrors(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() gspec = &Genesis{ - Config: config, + Config: config, + Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ Balance: big.NewInt(4000000000000000000), // 4 ether diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 7c0807b829..dedd013762 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -494,6 +494,7 @@ func TestAcceptNonCanonicalBlock(t *testing.T, create func(db ethdb.Database, gs if err := blockchain.Reject(chain1[i]); err != nil { t.Fatal(err) } + require.False(t, blockchain.HasBlock(chain1[i].Hash(), chain1[i].NumberU64())) } lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() diff --git a/eth/backend.go b/eth/backend.go index 1dbb48fd19..3a1de0ec39 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -274,6 +274,7 @@ func New( log.Info("Unprotected transactions allowed") } gpoParams := config.GPO + gpoParams.MinPrice = new(big.Int).SetUint64(config.TxPool.PriceLimit) eth.APIBackend.gpo, err = gasprice.NewOracle(eth.APIBackend, gpoParams) if err != nil { return nil, err diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 3987826a88..6a312df2ee 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -520,14 +520,15 @@ func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { } } -func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent, accepted bool) { +func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { for _, f := range filters[PendingTransactionsSubscription] { f.txs <- ev.Txs } - if accepted { - for _, f := range filters[AcceptedTransactionsSubscription] { - f.txs <- ev.Txs - } +} + +func (es *EventSystem) handleTxsAcceptedEvent(filters filterIndex, ev core.NewTxsEvent) { + for _, f := range filters[AcceptedTransactionsSubscription] { + f.txs <- ev.Txs } } @@ -565,7 +566,7 @@ func (es *EventSystem) eventLoop() { for { select { case ev := <-es.txsCh: - es.handleTxsEvent(index, ev, false) + es.handleTxsEvent(index, ev) case ev := <-es.logsCh: es.handleLogs(index, ev) case ev := <-es.logsAcceptedCh: @@ -579,7 +580,7 @@ func (es *EventSystem) eventLoop() { case ev := <-es.chainAcceptedCh: es.handleChainAcceptedEvent(index, ev) case ev := <-es.txsAcceptedCh: - es.handleTxsEvent(index, ev, true) + es.handleTxsAcceptedEvent(index, ev) case f := <-es.install: if f.typ == MinedAndPendingLogsSubscription { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index f596bf2638..e733633c17 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1105,6 +1105,10 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) copy.CancunTime = timestamp canon = false } + if timestamp := override.EtnaTimestamp; timestamp != nil { + copy.EtnaTimestamp = timestamp + canon = false + } return copy, canon } diff --git a/go.mod b/go.mod index d552afc582..a697d97a99 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21.12 require ( github.com/VictoriaMetrics/fastcache v1.12.1 - github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df + github.com/ava-labs/avalanchego v1.11.11 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 a6c70bd048..56dbde1c84 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df h1:Yp9rCHpgEsPFzpx2MXxpb/T+/NbP2NpS1EDwFquffLQ= -github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df/go.mod h1:Kw2GKwTaCkLwq2z3zSVH4V2eiAmq2FohHmN3AIDWjvY= +github.com/ava-labs/avalanchego v1.11.11 h1:MIQq8xRavRj4ZXHA4G+aMiymig7SOScGOG1SApmMvBc= +github.com/ava-labs/avalanchego v1.11.11/go.mod h1:yFx3V31Jy9NFa8GZlgGnwiVf8KGjeF2+Uc99l9Scd/8= 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/ethapi/api_test.go b/internal/ethapi/api_test.go index b74507c1be..012d9ff86a 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1490,7 +1490,6 @@ func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Ha b.AddTx(tx) txHashes[i] = tx.Hash() } - // b.SetPoS() }) return backend, txHashes } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 77d4716575..3c5ba9f1d0 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -191,13 +191,12 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b feeBackend) e if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil { return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active") } - if args.GasPrice == nil { - price, err := b.SuggestGasTipCap(ctx) - if err != nil { - return err - } - args.GasPrice = (*hexutil.Big)(price) + // London not active, set gas price. + price, err := b.SuggestGasTipCap(ctx) + if err != nil { + return err } + args.GasPrice = (*hexutil.Big)(price) } return nil } @@ -217,11 +216,11 @@ func (args *TransactionArgs) setApricotPhase3FeeDefault(ctx context.Context, hea // Set the max fee to be 2 times larger than the previous block's base fee. // The additional slack allows the tx to not become invalidated if the base // fee is rising. - gasFeeCap := new(big.Int).Add( - (*big.Int)(args.MaxPriorityFeePerGas), + val := new(big.Int).Add( + args.MaxPriorityFeePerGas.ToInt(), new(big.Int).Mul(head.BaseFee, big.NewInt(2)), ) - args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap) + args.MaxFeePerGas = (*hexutil.Big)(val) } // Both EIP-1559 fee parameters are now set; sanity check them. if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 5122a132c1..7967357055 100644 --- a/metrics/sample_test.go +++ b/metrics/sample_test.go @@ -3,7 +3,6 @@ package metrics import ( "math" "math/rand" - "os" "runtime" "testing" "time" @@ -133,9 +132,6 @@ func TestExpDecaySample(t *testing.T) { // The priority becomes +Inf quickly after starting if this is done, // effectively freezing the set of samples until a rescale step happens. func TestExpDecaySampleNanosecondRegression(t *testing.T) { - if os.Getenv("RUN_FLAKY_TESTS") != "true" { - t.Skip("FLAKY") - } sw := NewExpDecaySample(100, 0.99) for i := 0; i < 100; i++ { sw.Update(10) diff --git a/params/config.go b/params/config.go index da47cf8282..657b0a0f1f 100644 --- a/params/config.go +++ b/params/config.go @@ -502,8 +502,8 @@ type ChainConfig struct { CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already activated) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) - // Avalanche Network Upgrades - NetworkUpgrades + + NetworkUpgrades // Config for timestamps that enable network upgrades. Skip encoding/decoding directly into ChainConfig. AvalancheContext `json:"-"` // Avalanche specific context set during VM initialization. Not serialized. diff --git a/plugin/evm/gossiper_atomic_gossiping_test.go b/plugin/evm/gossiper_atomic_gossiping_test.go index 6ab4b429b9..0c4779d675 100644 --- a/plugin/evm/gossiper_atomic_gossiping_test.go +++ b/plugin/evm/gossiper_atomic_gossiping_test.go @@ -5,7 +5,6 @@ package evm import ( "context" - "os" "sync" "testing" "time" @@ -107,9 +106,6 @@ func TestMempoolAtmTxsAppGossipHandling(t *testing.T) { // show that txs already marked as invalid are not re-requested on gossiping func TestMempoolAtmTxsAppGossipHandlingDiscardedTx(t *testing.T) { - if os.Getenv("RUN_FLAKY_TESTS") != "true" { - t.Skip("FLAKY") - } assert := assert.New(t) _, vm, _, sharedMemory, sender := GenesisVM(t, true, "", "", "") diff --git a/plugin/evm/gossiper_eth_gossiping_test.go b/plugin/evm/gossiper_eth_gossiping_test.go index dfcf80666f..4ddc1432b8 100644 --- a/plugin/evm/gossiper_eth_gossiping_test.go +++ b/plugin/evm/gossiper_eth_gossiping_test.go @@ -8,7 +8,6 @@ import ( "crypto/ecdsa" "encoding/json" "math/big" - "os" "strings" "sync" "testing" @@ -74,9 +73,6 @@ func getValidEthTxs(key *ecdsa.PrivateKey, count int, gasPrice *big.Int) []*type // show that a geth tx discovered from gossip is requested to the same node that // gossiped it func TestMempoolEthTxsAppGossipHandling(t *testing.T) { - if os.Getenv("RUN_FLAKY_TESTS") != "true" { - t.Skip("FLAKY") - } assert := assert.New(t) key, err := crypto.GenerateKey() diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index 4da9713d68..5588601567 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -86,7 +86,7 @@ func TestEthTxGossip(t *testing.T) { network, err := p2p.NewNetwork(logging.NoLog{}, peerSender, prometheus.NewRegistry(), "") require.NoError(err) - client := network.NewClient(ethTxGossipProtocol) + client := network.NewClient(p2p.TxGossipHandlerID) // we only accept gossip requests from validators requestingNodeID := ids.GenerateTestNodeID() @@ -363,7 +363,7 @@ func TestEthTxPushGossipOutbound(t *testing.T) { // we should get a message that has the protocol prefix and the gossip // message - require.Equal(byte(ethTxGossipProtocol), sent[0]) + require.Equal(byte(p2p.TxGossipHandlerID), sent[0]) require.NoError(proto.Unmarshal(sent[1:], got)) marshaller := GossipEthTxMarshaller{} @@ -428,7 +428,7 @@ func TestEthTxPushGossipInbound(t *testing.T) { inboundGossipBytes, err := proto.Marshal(inboundGossip) require.NoError(err) - inboundGossipMsg := append(binary.AppendUvarint(nil, ethTxGossipProtocol), inboundGossipBytes...) + inboundGossipMsg := append(binary.AppendUvarint(nil, p2p.TxGossipHandlerID), inboundGossipBytes...) require.NoError(vm.AppGossip(ctx, ids.EmptyNodeID, inboundGossipMsg)) require.True(vm.txPool.Has(signedTx.Hash())) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index cb7cd9f1d6..beb267d2d0 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -41,7 +41,7 @@ import ( "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/trie/triedb/hashdb" - warpPrecompile "github.com/ava-labs/coreth/precompile/contracts/warp" + warpcontract "github.com/ava-labs/coreth/precompile/contracts/warp" "github.com/ava-labs/coreth/rpc" statesyncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/client/stats" @@ -49,7 +49,6 @@ import ( "github.com/ava-labs/coreth/utils" "github.com/ava-labs/coreth/warp" "github.com/ava-labs/coreth/warp/handlers" - warpValidators "github.com/ava-labs/coreth/warp/validators" // Force-load tracer engine to trigger registration // @@ -473,7 +472,7 @@ func (vm *VM) Initialize( // If the Durango is activated, activate the Warp Precompile at the same time if g.Config.DurangoBlockTimestamp != nil { g.Config.PrecompileUpgrades = append(g.Config.PrecompileUpgrades, params.PrecompileUpgrade{ - Config: warpPrecompile.NewDefaultConfig(g.Config.DurangoBlockTimestamp), + Config: warpcontract.NewDefaultConfig(g.Config.DurangoBlockTimestamp), }) } @@ -1112,7 +1111,7 @@ func (vm *VM) initBlockBuilding() error { vm.cancel = cancel ethTxGossipMarshaller := GossipEthTxMarshaller{} - ethTxGossipClient := vm.Network.NewClient(ethTxGossipProtocol, p2p.WithValidatorSampling(vm.validators)) + ethTxGossipClient := vm.Network.NewClient(p2p.TxGossipHandlerID, p2p.WithValidatorSampling(vm.validators)) ethTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, ethTxGossipNamespace) if err != nil { return fmt.Errorf("failed to initialize eth tx gossip metrics: %w", err) @@ -1201,7 +1200,7 @@ func (vm *VM) initBlockBuilding() error { ) } - if err := vm.Network.AddHandler(ethTxGossipProtocol, vm.ethTxGossipHandler); err != nil { + if err := vm.Network.AddHandler(p2p.TxGossipHandlerID, vm.ethTxGossipHandler); err != nil { return err } @@ -1459,7 +1458,7 @@ func (vm *VM) VerifyHeightIndex(context.Context) error { return nil } -// GetBlockAtHeight returns the canonical block at [height]. +// GetBlockIDAtHeight returns the canonical block at [height]. // Note: the engine assumes that if a block is not found at [height], then // [database.ErrNotFound] will be returned. This indicates that the VM has state // synced and does not have all historical blocks available. @@ -1528,8 +1527,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { } if vm.config.WarpAPIEnabled { - validatorsState := warpValidators.NewState(vm.ctx) - if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, validatorsState, vm.warpBackend, vm.client)); err != nil { + if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, vm.ctx.ValidatorState, vm.warpBackend, vm.client, vm.requirePrimaryNetworkSigners)); err != nil { return nil, err } enabledAPIs = append(enabledAPIs, "warp") @@ -1945,6 +1943,18 @@ func (vm *VM) currentRules() params.Rules { return vm.chainConfig.Rules(header.Number, header.Time) } +// requirePrimaryNetworkSigners returns true if warp messages from the primary +// network must be signed by the primary network validators. +// This is necessary when the subnet is not validating the primary network. +func (vm *VM) requirePrimaryNetworkSigners() bool { + switch c := vm.currentRules().ActivePrecompiles[warpcontract.ContractAddress].(type) { + case *warpcontract.Config: + return c.RequirePrimaryNetworkSigners + default: // includes nil due to non-presence + return false + } +} + func (vm *VM) startContinuousProfiler() { // If the profiler directory is empty, return immediately // without creating or starting a continuous profiler. diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index c0de677fe4..c7721837ef 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -38,6 +38,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/cb58" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" @@ -82,6 +83,7 @@ var ( g := new(core.Genesis) g.Difficulty = big.NewInt(0) g.GasLimit = 0x5f5e100 + g.Timestamp = uint64(upgrade.InitiallyActiveTime.Unix()) // Use chainId: 43111, so that it does not overlap with any Avalanche ChainIDs, which may have their // config overridden in vm.Initialize. @@ -283,7 +285,8 @@ func GenesisVM(t *testing.T, upgradeJSON string, ) ( chan commonEng.Message, - *VM, database.Database, + *VM, + database.Database, *atomic.Memory, *enginetest.Sender, ) { @@ -301,7 +304,8 @@ func GenesisVMWithClock( clock mockable.Clock, ) ( chan commonEng.Message, - *VM, database.Database, + *VM, + database.Database, *atomic.Memory, *enginetest.Sender, ) { @@ -3850,21 +3854,21 @@ func TestParentBeaconRootBlock(t *testing.T) { expectedError: false, }, { - name: "non-empty parent beacon root in Cancun", - genesisJSON: genesisJSONCancun, + name: "non-empty parent beacon root in E-Upgrade (Cancun)", + genesisJSON: genesisJSONEtna, beaconRoot: &common.Hash{0x01}, expectedError: true, errString: "expected empty hash", }, { - name: "empty parent beacon root in Cancun", - genesisJSON: genesisJSONCancun, + name: "empty parent beacon root in E-Upgrade (Cancun)", + genesisJSON: genesisJSONEtna, beaconRoot: &common.Hash{}, expectedError: false, }, { - name: "nil parent beacon root in Cancun", - genesisJSON: genesisJSONCancun, + name: "nil parent beacon root in E-Upgrade (Cancun)", + genesisJSON: genesisJSONEtna, beaconRoot: nil, expectedError: true, errString: "header is missing parentBeaconRoot", diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index a447950d07..ec333e4132 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -13,10 +13,13 @@ import ( _ "embed" "github.com/ava-labs/avalanchego/ids" + commonEng "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/upgrade" + 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/set" "github.com/ava-labs/avalanchego/vms/components/chain" @@ -30,6 +33,7 @@ import ( "github.com/ava-labs/coreth/precompile/contract" "github.com/ava-labs/coreth/precompile/contracts/warp" "github.com/ava-labs/coreth/predicate" + "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -42,6 +46,20 @@ var ( exampleWarpABI string ) +type warpMsgFrom int + +const ( + fromSubnet warpMsgFrom = iota + fromPrimary +) + +type useWarpMsgSigners int + +const ( + signersSubnet useWarpMsgSigners = iota + signersPrimary +) + func TestSendWarpMessage(t *testing.T) { require := require.New(t) issuer, vm, _, _, _ := GenesisVM(t, true, genesisJSONDurango, "", "") @@ -54,7 +72,7 @@ func TestSendWarpMessage(t *testing.T) { logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) defer logsSub.Unsubscribe() - payloadData := utils.RandomBytes(100) + payloadData := avagoUtils.RandomBytes(100) warpSendMessageInput, err := warp.PackSendWarpMessage(payloadData) require.NoError(err) @@ -390,37 +408,154 @@ func TestReceiveWarpMessage(t *testing.T) { require.NoError(vm.Shutdown(context.Background())) }() - acceptedLogsChan := make(chan []*types.Log, 10) - logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) - defer logsSub.Unsubscribe() + // enable warp at the default genesis time + enableTime := upgrade.InitiallyActiveTime + enableConfig := warp.NewDefaultConfig(utils.TimeToNewUint64(enableTime)) + + // disable warp so we can re-enable it with RequirePrimaryNetworkSigners + disableTime := upgrade.InitiallyActiveTime.Add(10 * time.Second) + disableConfig := warp.NewDisableConfig(utils.TimeToNewUint64(disableTime)) - payloadData := utils.RandomBytes(100) + // re-enable warp with RequirePrimaryNetworkSigners + reEnableTime := disableTime.Add(10 * time.Second) + reEnableConfig := warp.NewConfig( + utils.TimeToNewUint64(reEnableTime), + 0, // QuorumNumerator + true, // RequirePrimaryNetworkSigners + ) + + vm.chainConfig.UpgradeConfig = params.UpgradeConfig{ + PrecompileUpgrades: []params.PrecompileUpgrade{ + {Config: enableConfig}, + {Config: disableConfig}, + {Config: reEnableConfig}, + }, + } + + type test struct { + name string + sourceChainID ids.ID + msgFrom warpMsgFrom + useSigners useWarpMsgSigners + blockTime time.Time + } + + blockGap := 2 * time.Second // Build blocks with a gap. Blocks built too quickly will have high fees. + tests := []test{ + { + name: "subnet message should be signed by subnet without RequirePrimaryNetworkSigners", + sourceChainID: vm.ctx.ChainID, + msgFrom: fromSubnet, + useSigners: signersSubnet, + blockTime: upgrade.InitiallyActiveTime, + }, + { + name: "P-Chain message should be signed by subnet without RequirePrimaryNetworkSigners", + sourceChainID: constants.PlatformChainID, + msgFrom: fromPrimary, + useSigners: signersSubnet, + blockTime: upgrade.InitiallyActiveTime.Add(blockGap), + }, + { + name: "C-Chain message should be signed by subnet without RequirePrimaryNetworkSigners", + sourceChainID: testCChainID, + msgFrom: fromPrimary, + useSigners: signersSubnet, + blockTime: upgrade.InitiallyActiveTime.Add(2 * blockGap), + }, + // Note here we disable warp and re-enable it with RequirePrimaryNetworkSigners + // by using reEnableTime. + { + name: "subnet message should be signed by subnet with RequirePrimaryNetworkSigners (unimpacted)", + sourceChainID: vm.ctx.ChainID, + msgFrom: fromSubnet, + useSigners: signersSubnet, + blockTime: reEnableTime, + }, + { + name: "P-Chain message should be signed by subnet with RequirePrimaryNetworkSigners (unimpacted)", + sourceChainID: constants.PlatformChainID, + msgFrom: fromPrimary, + useSigners: signersSubnet, + blockTime: reEnableTime.Add(blockGap), + }, + { + name: "C-Chain message should be signed by primary with RequirePrimaryNetworkSigners (impacted)", + sourceChainID: testCChainID, + msgFrom: fromPrimary, + useSigners: signersPrimary, + blockTime: reEnableTime.Add(2 * blockGap), + }, + } + // Note each test corresponds to a block, the tests must be ordered by block + // time and cannot, eg be run in parallel or a separate golang test. + for _, test := range tests { + testReceiveWarpMessage( + t, issuer, vm, test.sourceChainID, test.msgFrom, test.useSigners, test.blockTime, + ) + } +} +func testReceiveWarpMessage( + t *testing.T, issuer chan commonEng.Message, vm *VM, + sourceChainID ids.ID, + msgFrom warpMsgFrom, useSigners useWarpMsgSigners, + blockTime time.Time, +) { + require := require.New(t) + payloadData := avagoUtils.RandomBytes(100) addressedPayload, err := payload.NewAddressedCall( testEthAddrs[0].Bytes(), payloadData, ) require.NoError(err) + + vm.ctx.SubnetID = ids.GenerateTestID() + vm.ctx.NetworkID = testNetworkID unsignedMessage, err := avalancheWarp.NewUnsignedMessage( vm.ctx.NetworkID, - vm.ctx.ChainID, + sourceChainID, addressedPayload.Bytes(), ) require.NoError(err) - nodeID1 := ids.GenerateTestNodeID() - blsSecretKey1, err := bls.NewSecretKey() - require.NoError(err) - blsPublicKey1 := bls.PublicFromSecretKey(blsSecretKey1) - blsSignature1 := bls.Sign(blsSecretKey1, unsignedMessage.Bytes()) + type signer struct { + networkID ids.ID + nodeID ids.NodeID + secret *bls.SecretKey + signature *bls.Signature + weight uint64 + } + newSigner := func(networkID ids.ID, weight uint64) signer { + secret, err := bls.NewSecretKey() + require.NoError(err) + return signer{ + networkID: networkID, + nodeID: ids.GenerateTestNodeID(), + secret: secret, + signature: bls.Sign(secret, unsignedMessage.Bytes()), + weight: weight, + } + } - nodeID2 := ids.GenerateTestNodeID() - blsSecretKey2, err := bls.NewSecretKey() - require.NoError(err) - blsPublicKey2 := bls.PublicFromSecretKey(blsSecretKey2) - blsSignature2 := bls.Sign(blsSecretKey2, unsignedMessage.Bytes()) + primarySigners := []signer{ + newSigner(constants.PrimaryNetworkID, 50), + newSigner(constants.PrimaryNetworkID, 50), + } + subnetSigners := []signer{ + newSigner(vm.ctx.SubnetID, 50), + newSigner(vm.ctx.SubnetID, 50), + } + signers := subnetSigners + if useSigners == signersPrimary { + signers = primarySigners + } - blsAggregatedSignature, err := bls.AggregateSignatures([]*bls.Signature{blsSignature1, blsSignature2}) + blsSignatures := make([]*bls.Signature, len(signers)) + for i := range signers { + blsSignatures[i] = signers[i].signature + } + blsAggregatedSignature, err := bls.AggregateSignatures(blsSignatures) require.NoError(err) minimumValidPChainHeight := uint64(10) @@ -428,30 +563,36 @@ func TestReceiveWarpMessage(t *testing.T) { vm.ctx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(ctx context.Context, chainID ids.ID) (ids.ID, error) { - return ids.Empty, nil + if msgFrom == fromPrimary { + return constants.PrimaryNetworkID, nil + } + return vm.ctx.SubnetID, nil }, GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { if height < minimumValidPChainHeight { return nil, getValidatorSetTestErr } - return map[ids.NodeID]*validators.GetValidatorOutput{ - nodeID1: { - NodeID: nodeID1, - PublicKey: blsPublicKey1, - Weight: 50, - }, - nodeID2: { - NodeID: nodeID2, - PublicKey: blsPublicKey2, - Weight: 50, - }, - }, nil + signers := subnetSigners + if subnetID == constants.PrimaryNetworkID { + signers = primarySigners + } + + vdrOutput := make(map[ids.NodeID]*validators.GetValidatorOutput) + for _, s := range signers { + vdrOutput[s.nodeID] = &validators.GetValidatorOutput{ + NodeID: s.nodeID, + PublicKey: bls.PublicFromSecretKey(s.secret), + Weight: s.weight, + } + } + return vdrOutput, nil }, } signersBitSet := set.NewBits() - signersBitSet.Add(0) - signersBitSet.Add(1) + for i := range signers { + signersBitSet.Add(i) + } warpSignature := &avalancheWarp.BitSetSignature{ Signers: signersBitSet.Bytes(), @@ -471,7 +612,7 @@ func TestReceiveWarpMessage(t *testing.T) { getVerifiedWarpMessageTx, err := types.SignTx( predicate.NewPredicateTx( vm.chainConfig.ChainID, - 0, + vm.txPool.Nonce(testEthAddrs[0]), &warp.Module.Address, 1_000_000, big.NewInt(225*params.GWei), @@ -495,12 +636,28 @@ func TestReceiveWarpMessage(t *testing.T) { validProposerCtx := &block.Context{ PChainHeight: minimumValidPChainHeight, } - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) + vm.clock.Set(blockTime) <-issuer block2, err := vm.BuildBlockWithContext(context.Background(), validProposerCtx) require.NoError(err) + // Require the block was built with a successful predicate result + ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock + headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(ethBlock.Extra()) + require.True(ok) + results, err := predicate.ParseResults(headerPredicateResultsBytes) + require.NoError(err) + + // Predicate results encode the index of invalid warp messages in a bitset. + // An empty bitset indicates success. + txResultsBytes := results.GetResults( + getVerifiedWarpMessageTx.Hash(), + warp.ContractAddress, + ) + bitset := set.BitsFromBytes(txResultsBytes) + require.Zero(bitset.Len()) // Empty bitset indicates success + block2VerifyWithCtx, ok := block2.(block.WithVerifyContext) require.True(ok) shouldVerifyWithCtx, err := block2VerifyWithCtx.ShouldVerifyWithContext(context.Background()) @@ -524,7 +681,6 @@ func TestReceiveWarpMessage(t *testing.T) { require.NoError(block2.Accept(context.Background())) vm.blockChain.DrainAcceptorQueue() - ethBlock := block2.(*chain.BlockWrapper).Block.(*Block).ethBlock verifiedMessageReceipts := vm.blockChain.GetReceiptsByHash(ethBlock.Hash()) require.Len(verifiedMessageReceipts, 1) verifiedMessageTxReceipt := verifiedMessageReceipts[0] @@ -532,7 +688,7 @@ func TestReceiveWarpMessage(t *testing.T) { expectedOutput, err := warp.PackGetVerifiedWarpMessageOutput(warp.GetVerifiedWarpMessageOutput{ Message: warp.WarpMessage{ - SourceChainID: common.Hash(vm.ctx.ChainID), + SourceChainID: common.Hash(sourceChainID), OriginSenderAddress: testEthAddrs[0], Payload: payloadData, }, diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index d564546e5a..ce90cf282c 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -47,22 +47,24 @@ var ( // adds specific configuration for Warp. type Config struct { precompileconfig.Upgrade - QuorumNumerator uint64 `json:"quorumNumerator"` + QuorumNumerator uint64 `json:"quorumNumerator"` + RequirePrimaryNetworkSigners bool `json:"requirePrimaryNetworkSigners"` } // NewConfig returns a config for a network upgrade at [blockTimestamp] that enables // Warp with the given quorum numerator. -func NewConfig(blockTimestamp *uint64, quorumNumerator uint64) *Config { +func NewConfig(blockTimestamp *uint64, quorumNumerator uint64, requirePrimaryNetworkSigners bool) *Config { return &Config{ - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - QuorumNumerator: quorumNumerator, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, + QuorumNumerator: quorumNumerator, + RequirePrimaryNetworkSigners: requirePrimaryNetworkSigners, } } // NewDefaultConfig returns a config for a network upgrade at [blockTimestamp] that enables // Warp with the default quorum numerator (0 denotes using the default). func NewDefaultConfig(blockTimestamp *uint64) *Config { - return NewConfig(blockTimestamp, 0) + return NewConfig(blockTimestamp, 0, false) } // NewDisableConfig returns config for a network upgrade at [blockTimestamp] @@ -198,11 +200,19 @@ func (c *Config) VerifyPredicate(predicateContext *precompileconfig.PredicateCon } log.Debug("verifying warp message", "warpMsg", warpMsg, "quorumNum", quorumNumerator, "quorumDenom", WarpQuorumDenominator) + + // Wrap validators.State on the chain snow context to special case the Primary Network + state := warpValidators.NewState( + predicateContext.SnowCtx.ValidatorState, + predicateContext.SnowCtx.SubnetID, + warpMsg.SourceChainID, + c.RequirePrimaryNetworkSigners, + ) err = warpMsg.Signature.Verify( context.Background(), &warpMsg.UnsignedMessage, predicateContext.SnowCtx.NetworkID, - warpValidators.NewState(predicateContext.SnowCtx), // Wrap validators.State on the chain snow context to special case the Primary Network + state, predicateContext.ProposerVMBlockCtx.PChainHeight, quorumNumerator, WarpQuorumDenominator, diff --git a/precompile/contracts/warp/config_test.go b/precompile/contracts/warp/config_test.go index 419b9bbeee..526a5ce51a 100644 --- a/precompile/contracts/warp/config_test.go +++ b/precompile/contracts/warp/config_test.go @@ -16,24 +16,24 @@ import ( func TestVerify(t *testing.T) { tests := map[string]testutils.ConfigVerifyTest{ "quorum numerator less than minimum": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum-1), + Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum-1, false), ExpectedError: fmt.Sprintf("cannot specify quorum numerator (%d) < min quorum numerator (%d)", WarpQuorumNumeratorMinimum-1, WarpQuorumNumeratorMinimum), }, "quorum numerator greater than quorum denominator": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumDenominator+1), + Config: NewConfig(utils.NewUint64(3), WarpQuorumDenominator+1, false), ExpectedError: fmt.Sprintf("cannot specify quorum numerator (%d) > quorum denominator (%d)", WarpQuorumDenominator+1, WarpQuorumDenominator), }, "default quorum numerator": { Config: NewDefaultConfig(utils.NewUint64(3)), }, "valid quorum numerator 1 less than denominator": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumDenominator-1), + Config: NewConfig(utils.NewUint64(3), WarpQuorumDenominator-1, false), }, "valid quorum numerator 1 more than minimum": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+1), + Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+1, false), }, "invalid cannot activated before Durango activation": { - Config: NewConfig(utils.NewUint64(3), 0), + Config: NewConfig(utils.NewUint64(3), 0, false), ChainConfig: func() precompileconfig.ChainConfig { config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) config.EXPECT().IsDurango(gomock.Any()).Return(false) @@ -66,8 +66,8 @@ func TestEqualWarpConfig(t *testing.T) { }, "different quorum numerator": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+1), - Other: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+2), + Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+1, false), + Other: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+2, false), Expected: false, }, @@ -78,8 +78,8 @@ func TestEqualWarpConfig(t *testing.T) { }, "same non-default config": { - Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+5), - Other: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+5), + Config: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+5, false), + Other: NewConfig(utils.NewUint64(3), WarpQuorumNumeratorMinimum+5, false), Expected: true, }, } diff --git a/precompile/contracts/warp/predicate_test.go b/precompile/contracts/warp/predicate_test.go index d0351b844b..04ddd46705 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -25,7 +25,6 @@ import ( "github.com/ava-labs/coreth/predicate" "github.com/ava-labs/coreth/utils" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) const pChainHeight uint64 = 1337 @@ -49,7 +48,6 @@ var ( numTestVdrs = 10_000 testVdrs []*testValidator vdrs map[ids.NodeID]*validators.GetValidatorOutput - tests []signatureTest predicateTests = make(map[string]testutils.PredicateTest) ) @@ -132,15 +130,6 @@ func newTestValidator() *testValidator { } } -type signatureTest struct { - name string - stateF func(*gomock.Controller) validators.State - quorumNum uint64 - quorumDen uint64 - msgF func(*require.Assertions) *avalancheWarp.Message - err error -} - // createWarpMessage constructs a signed warp message using the global variable [unsignedMsg] // and the first [numKeys] signatures from [blsSignatures] func createWarpMessage(numKeys int) *avalancheWarp.Message { @@ -228,6 +217,12 @@ func createValidPredicateTest(snowCtx *snow.Context, numKeys uint64, predicateBy } func TestWarpMessageFromPrimaryNetwork(t *testing.T) { + for _, requirePrimaryNetworkSigners := range []bool{true, false} { + testWarpMessageFromPrimaryNetwork(t, requirePrimaryNetworkSigners) + } +} + +func testWarpMessageFromPrimaryNetwork(t *testing.T, requirePrimaryNetworkSigners bool) { require := require.New(t) numKeys := 10 cChainID := ids.GenerateTestID() @@ -273,13 +268,17 @@ func TestWarpMessageFromPrimaryNetwork(t *testing.T) { return constants.PrimaryNetworkID, nil // Return Primary Network SubnetID }, GetValidatorSetF: func(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - require.Equal(snowCtx.SubnetID, subnetID) + expectedSubnetID := snowCtx.SubnetID + if requirePrimaryNetworkSigners { + expectedSubnetID = constants.PrimaryNetworkID + } + require.Equal(expectedSubnetID, subnetID) return getValidatorsOutput, nil }, } test := testutils.PredicateTest{ - Config: NewDefaultConfig(utils.NewUint64(0)), + Config: NewConfig(utils.NewUint64(0), 0, requirePrimaryNetworkSigners), PredicateContext: &precompileconfig.PredicateContext{ SnowCtx: snowCtx, ProposerVMBlockCtx: &block.Context{ @@ -576,7 +575,7 @@ func TestWarpSignatureWeightsNonDefaultQuorumNumerator(t *testing.T) { name := fmt.Sprintf("non-default quorum %d signature(s)", numSigners) tests[name] = testutils.PredicateTest{ - Config: NewConfig(utils.NewUint64(0), uint64(nonDefaultQuorumNumerator)), + Config: NewConfig(utils.NewUint64(0), uint64(nonDefaultQuorumNumerator), false), PredicateContext: &precompileconfig.PredicateContext{ SnowCtx: snowCtx, ProposerVMBlockCtx: &block.Context{ diff --git a/precompile/contracts/warp/signature_verification_test.go b/precompile/contracts/warp/signature_verification_test.go index dadefeb4d6..5b54cd29b8 100644 --- a/precompile/contracts/warp/signature_verification_test.go +++ b/precompile/contracts/warp/signature_verification_test.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorsmock" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -17,14 +18,23 @@ import ( "go.uber.org/mock/gomock" ) -// This test copies the test coverage from https://github.com/ava-labs/avalanchego/blob/v1.10.0/vms/platformvm/warp/signature_test.go#L137. +type signatureTest struct { + name string + stateF func(*gomock.Controller) validators.State + quorumNum uint64 + quorumDen uint64 + msgF func(*require.Assertions) *avalancheWarp.Message + err error +} + +// This test copies the test coverage from https://github.com/ava-labs/avalanchego/blob/0117ab96/vms/platformvm/warp/signature_test.go#L137. // These tests are only expected to fail if there is a breaking change in AvalancheGo that unexpectedly changes behavior. func TestSignatureVerification(t *testing.T) { - tests = []signatureTest{ + tests := []signatureTest{ { name: "can't get subnetID", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, errTest) return state }, @@ -50,7 +60,7 @@ func TestSignatureVerification(t *testing.T) { { name: "can't get validator set", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(nil, errTest) return state @@ -77,7 +87,7 @@ func TestSignatureVerification(t *testing.T) { { name: "weight overflow", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{ testVdrs[0].nodeID: { @@ -117,7 +127,7 @@ func TestSignatureVerification(t *testing.T) { { name: "invalid bit set index", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -147,7 +157,7 @@ func TestSignatureVerification(t *testing.T) { { name: "unknown index", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -180,7 +190,7 @@ func TestSignatureVerification(t *testing.T) { { name: "insufficient weight", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -224,7 +234,7 @@ func TestSignatureVerification(t *testing.T) { { name: "can't parse sig", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -258,7 +268,7 @@ func TestSignatureVerification(t *testing.T) { { name: "no validators", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(nil, nil) return state @@ -293,7 +303,7 @@ func TestSignatureVerification(t *testing.T) { { name: "invalid signature (substitute)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -337,7 +347,7 @@ func TestSignatureVerification(t *testing.T) { { name: "invalid signature (missing one)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -377,7 +387,7 @@ func TestSignatureVerification(t *testing.T) { { name: "invalid signature (extra one)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -422,7 +432,7 @@ func TestSignatureVerification(t *testing.T) { { name: "valid signature", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -466,7 +476,7 @@ func TestSignatureVerification(t *testing.T) { { name: "valid signature (boundary)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(vdrs, nil) return state @@ -510,7 +520,7 @@ func TestSignatureVerification(t *testing.T) { { name: "valid signature (missing key)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{ testVdrs[0].nodeID: { @@ -571,7 +581,7 @@ func TestSignatureVerification(t *testing.T) { { name: "valid signature (duplicate key)", stateF: func(ctrl *gomock.Controller) validators.State { - state := validators.NewMockState(ctrl) + state := validatorsmock.NewState(ctrl) state.EXPECT().GetSubnetID(gomock.Any(), sourceChainID).Return(sourceSubnetID, nil) state.EXPECT().GetValidatorSet(gomock.Any(), pChainHeight, sourceSubnetID).Return(map[ids.NodeID]*validators.GetValidatorOutput{ testVdrs[0].nodeID: { diff --git a/scripts/build_test.sh b/scripts/build_test.sh index c62dc95789..460ebcd564 100755 --- a/scripts/build_test.sh +++ b/scripts/build_test.sh @@ -15,4 +15,44 @@ source "$CORETH_PATH"/scripts/constants.sh # We pass in the arguments to this script directly to enable easily passing parameters such as enabling race detection, # parallelism, and test coverage. -go test -shuffle=on -race -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic ./... "$@" +# DO NOT RUN tests from the top level "tests" directory since they are run by ginkgo +race="-race" +if [[ -n "${NO_RACE:-}" ]]; then + race="" +fi + +# MAX_RUNS bounds the attempts to retry the tests before giving up +# This is useful for flaky tests +MAX_RUNS=4 +for ((i = 1; i <= MAX_RUNS; i++)); +do + # shellcheck disable=SC2046 + go test -shuffle=on ${race:-} -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" ./... | tee test.out || command_status=$? + + # If the test passed, exit + if [[ ${command_status:-0} == 0 ]]; then + rm test.out + exit 0 + else + unset command_status # Clear the error code for the next run + fi + + # If the test failed, print the output + unexpected_failures=$( + # First grep pattern corresponds to test failures, second pattern corresponds to test panics due to timeouts + (grep "^--- FAIL" test.out | awk '{print $3}' || grep -E '^\s+Test.+ \(' test.out | awk '{print $1}') | + sort -u | comm -23 - ./scripts/known_flakes.txt + ) + if [ -n "${unexpected_failures}" ]; then + echo "Unexpected test failures: ${unexpected_failures}" + exit 1 + fi + + # Note the absence of unexpected failures cannot be indicative that we only need to run the tests that failed, + # for example a test may panic and cause subsequent tests in that package to not run. + # So we loop here. + echo "Test run $i failed with known flakes, retrying..." +done + +# If we reach here, we have failed all retries +exit 1 diff --git a/scripts/constants.sh b/scripts/constants.sh index e4d0e81744..42b4f16829 100644 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -7,6 +7,8 @@ set -euo pipefail # Set the PATHS GOPATH="$(go env GOPATH)" +DEFAULT_PLUGIN_DIR="${HOME}/.avalanchego/plugins" +DEFAULT_VM_ID="srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" # Set binary location binary_path=${CORETH_BINARY_PATH:-"$GOPATH/src/github.com/ava-labs/avalanchego/build/plugins/evm"} diff --git a/scripts/known_flakes.txt b/scripts/known_flakes.txt new file mode 100644 index 0000000000..05c41794ba --- /dev/null +++ b/scripts/known_flakes.txt @@ -0,0 +1,11 @@ +TestClientCancelWebsocket +TestExpDecaySampleNanosecondRegression +TestGolangBindings +TestMempoolAtmTxsAppGossipHandlingDiscardedTx +TestMempoolEthTxsAppGossipHandling +TestResumeSyncAccountsTrieInterrupted +TestResyncNewRootAfterDeletes +TestTransactionSkipIndexing +TestVMShutdownWhileSyncing +TestWalletNotifications +TestWebsocketLargeRead \ No newline at end of file diff --git a/scripts/shellcheck.sh b/scripts/shellcheck.sh index 61fc09f90b..f57b853362 100755 --- a/scripts/shellcheck.sh +++ b/scripts/shellcheck.sh @@ -4,6 +4,14 @@ set -euo pipefail VERSION="v0.9.0" +# Scripts that are sourced from upstream and not maintained in this repo will not be shellchecked. +# Also ignore the local avalanchego clone. +IGNORED_FILES=" + cmd/evm/transition-test.sh + metrics/validate.sh + avalanchego/* +" + function get_version { local target_path=$1 if command -v "${target_path}" > /dev/null; then @@ -36,4 +44,12 @@ else fi fi -find "${REPO_ROOT}" -name "*.sh" -type f -print0 | xargs -0 "${SHELLCHECK}" "${@}" +IGNORED_CONDITIONS=() +for file in ${IGNORED_FILES}; do + if [[ -n "${IGNORED_CONDITIONS-}" ]]; then + IGNORED_CONDITIONS+=(-o) + fi + IGNORED_CONDITIONS+=(-path "${REPO_ROOT}/${file}" -prune) +done + +find "${REPO_ROOT}" \( "${IGNORED_CONDITIONS[@]}" \) -o -type f -name "*.sh" -print0 | xargs -0 "${SHELLCHECK}" "${@}" diff --git a/scripts/versions.sh b/scripts/versions.sh index 5bed52626d..4154fe89c0 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -6,4 +6,4 @@ set -euo pipefail # Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'1ac532af76df'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'v1.11.11'} diff --git a/utils/snow.go b/utils/snow.go index 92a1d236c1..77368f0750 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -26,7 +26,7 @@ func TestSnowContext() *snow.Context { PublicKey: pk, Log: logging.NoLog{}, BCLookup: ids.NewAliaser(), - Metrics: metrics.NewMultiGatherer(), + Metrics: metrics.NewPrefixGatherer(), ChainDataDir: "", ValidatorState: &validatorstest.State{}, } diff --git a/warp/service.go b/warp/service.go index 6eec775e37..8b92da80db 100644 --- a/warp/service.go +++ b/warp/service.go @@ -9,11 +9,12 @@ import ( "fmt" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/coreth/peer" "github.com/ava-labs/coreth/warp/aggregator" - "github.com/ava-labs/coreth/warp/validators" + warpValidators "github.com/ava-labs/coreth/warp/validators" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" ) @@ -25,18 +26,20 @@ type API struct { networkID uint32 sourceSubnetID, sourceChainID ids.ID backend Backend - state *validators.State + state validators.State client peer.NetworkClient + requirePrimaryNetworkSigners func() bool } -func NewAPI(networkID uint32, sourceSubnetID ids.ID, sourceChainID ids.ID, state *validators.State, backend Backend, client peer.NetworkClient) *API { +func NewAPI(networkID uint32, sourceSubnetID ids.ID, sourceChainID ids.ID, state validators.State, backend Backend, client peer.NetworkClient, requirePrimaryNetworkSigners func() bool) *API { return &API{ - networkID: networkID, - sourceSubnetID: sourceSubnetID, - sourceChainID: sourceChainID, - backend: backend, - state: state, - client: client, + networkID: networkID, + sourceSubnetID: sourceSubnetID, + sourceChainID: sourceChainID, + backend: backend, + state: state, + client: client, + requirePrimaryNetworkSigners: requirePrimaryNetworkSigners, } } @@ -104,7 +107,8 @@ func (a *API) aggregateSignatures(ctx context.Context, unsignedMessage *warp.Uns return nil, err } - validators, totalWeight, err := warp.GetCanonicalValidatorSet(ctx, a.state, pChainHeight, subnetID) + state := warpValidators.NewState(a.state, a.sourceSubnetID, a.sourceChainID, a.requirePrimaryNetworkSigners()) + validators, totalWeight, err := warp.GetCanonicalValidatorSet(ctx, state, pChainHeight, subnetID) if err != nil { return nil, fmt.Errorf("failed to get validator set: %w", err) } diff --git a/warp/validators/state.go b/warp/validators/state.go index c2929a5807..61e6e14cfe 100644 --- a/warp/validators/state.go +++ b/warp/validators/state.go @@ -7,7 +7,6 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" ) @@ -19,18 +18,22 @@ var _ validators.State = (*State)(nil) // signatures from a threshold of the RECEIVING subnet validator set rather than the full Primary Network // since the receiving subnet already relies on a majority of its validators being correct. type State struct { - chainContext *snow.Context validators.State + mySubnetID ids.ID + sourceChainID ids.ID + requirePrimaryNetworkSigners bool } // NewState returns a wrapper of [validators.State] which special cases the handling of the Primary Network. // -// The wrapped state will return the chainContext's Subnet validator set instead of the Primary Network when +// The wrapped state will return the [mySubnetID's] validator set instead of the Primary Network when // the Primary Network SubnetID is passed in. -func NewState(chainContext *snow.Context) *State { +func NewState(state validators.State, mySubnetID ids.ID, sourceChainID ids.ID, requirePrimaryNetworkSigners bool) *State { return &State{ - chainContext: chainContext, - State: chainContext.ValidatorState, + State: state, + mySubnetID: mySubnetID, + sourceChainID: sourceChainID, + requirePrimaryNetworkSigners: requirePrimaryNetworkSigners, } } @@ -39,13 +42,14 @@ func (s *State) GetValidatorSet( height uint64, subnetID ids.ID, ) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - // If the subnetID is anything other than the Primary Network, this is a direct - // passthrough - if subnetID != constants.PrimaryNetworkID { + // If the subnetID is anything other than the Primary Network, or Primary + // Network signers are required (except P-Chain), this is a direct passthrough. + usePrimary := s.requirePrimaryNetworkSigners && s.sourceChainID != constants.PlatformChainID + if usePrimary || subnetID != constants.PrimaryNetworkID { return s.State.GetValidatorSet(ctx, height, subnetID) } // If the requested subnet is the primary network, then we return the validator // set for the Subnet that is receiving the message instead. - return s.State.GetValidatorSet(ctx, height, s.chainContext.SubnetID) + return s.State.GetValidatorSet(ctx, height, s.mySubnetID) } diff --git a/warp/validators/state_test.go b/warp/validators/state_test.go index e06d1374ad..610927bb95 100644 --- a/warp/validators/state_test.go +++ b/warp/validators/state_test.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/snow/validators/validatorsmock" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/coreth/utils" "github.com/stretchr/testify/require" @@ -22,11 +23,11 @@ func TestGetValidatorSetPrimaryNetwork(t *testing.T) { mySubnetID := ids.GenerateTestID() otherSubnetID := ids.GenerateTestID() - mockState := validators.NewMockState(ctrl) + mockState := validatorsmock.NewState(ctrl) snowCtx := utils.TestSnowContext() snowCtx.SubnetID = mySubnetID snowCtx.ValidatorState = mockState - state := NewState(snowCtx) + state := NewState(snowCtx.ValidatorState, snowCtx.SubnetID, snowCtx.ChainID, false) // Expect that requesting my validator set returns my validator set mockState.EXPECT().GetValidatorSet(gomock.Any(), gomock.Any(), mySubnetID).Return(make(map[ids.NodeID]*validators.GetValidatorOutput), nil) output, err := state.GetValidatorSet(context.Background(), 10, mySubnetID)