diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 87f0a8d60b..d1a45a1480 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-actions + uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.11-monitoring-url 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-actions + uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1.11.11-monitoring-url with: run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/run_ginkgo_load.sh prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} diff --git a/.github/workflows/trigger-antithesis.yml b/.github/workflows/trigger-antithesis.yml index 1d3128f974..f957d26ed6 100644 --- a/.github/workflows/trigger-antithesis.yml +++ b/.github/workflows/trigger-antithesis.yml @@ -1,7 +1,13 @@ name: Trigger Antithesis on: - # TODO(marun) Add a schedule + schedule: + # When adjusting the schedule and duration of execution, make sure + # to check the schedule of antithesis jobs scheduled by other + # repos (avalanchego and hypersdk). Ideally the schedules for + # different test setups should not overlap to avoid unintended + # resource contention. + - cron: '0 14 * * *' # Every day at 2PM UTC workflow_dispatch: inputs: duration: @@ -36,5 +42,5 @@ jobs: email_recipients: ${{ github.event.inputs.recipients || secrets.ANTITHESIS_RECIPIENTS }} # Duration is in hours additional_parameters: |- - custom.duration=${{ github.event.inputs.duration || '11.25' }} + custom.duration=${{ github.event.inputs.duration || '7.5' }} custom.workload=subnet-evm diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index 144a9eff2d..ede27dceab 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/go.mod b/go.mod index 8bc5596d73..f033cf4918 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21.12 require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.11-0.20240819192939-df91c2f4ab99 + github.com/ava-labs/avalanchego v1.11.11-0.20240821175119-35c66e33f0dc 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 31b6d72788..17e8f63025 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ 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.11-0.20240819192939-df91c2f4ab99 h1:fPWpINk7O1W4w5thqa8CGMRJ/kvycHvehBEiWwMjezI= -github.com/ava-labs/avalanchego v1.11.11-0.20240819192939-df91c2f4ab99/go.mod h1:UkyrRDXK2E15Lq2abyae2Pt+JsWvgsg1pe0/AtoMyAM= +github.com/ava-labs/avalanchego v1.11.11-0.20240821175119-35c66e33f0dc h1:cUz1N+LJIeQAR0Z6zTBiuZ7s8GqIE5QQbRWs423VFRA= +github.com/ava-labs/avalanchego v1.11.11-0.20240821175119-35c66e33f0dc/go.mod h1:UkyrRDXK2E15Lq2abyae2Pt+JsWvgsg1pe0/AtoMyAM= github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180 h1:6aIHp7wbyGVYdhHVQUbG7BEcbCMEQ5SYopPPJyipyvk= github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180/go.mod h1:/wNBVq7J7wlC2Kbov7kk6LV5xZvau7VF9zwTVOeyAjY= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= 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/plugin/evm/gossiper_eth_gossiping_test.go b/plugin/evm/gossiper_eth_gossiping_test.go index d7052a6d7d..bf59b39f51 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/syncervm_test.go b/plugin/evm/syncervm_test.go index 533740666b..64306ef66c 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -241,7 +241,6 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { } func TestVMShutdownWhileSyncing(t *testing.T) { - t.Skip("FLAKY") var ( lock sync.Mutex vmSetup *syncVMSetup diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 2c6dfd55b9..3e49bcc94d 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -37,13 +37,13 @@ import ( "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/trie/triedb/hashdb" + warpcontract "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/subnet-evm/rpc" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/client/stats" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/warp" "github.com/ava-labs/subnet-evm/warp/handlers" - warpValidators "github.com/ava-labs/subnet-evm/warp/validators" // Force-load tracer engine to trigger registration // @@ -1019,8 +1019,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") @@ -1067,6 +1066,24 @@ func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) { return state.GetNonce(address), nil } +// currentRules returns the chain rules for the current block. +func (vm *VM) currentRules() params.Rules { + header := vm.eth.APIBackend.CurrentHeader() + 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_warp_test.go b/plugin/evm/vm_warp_test.go index 2b4109bae2..eb235379ee 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -13,11 +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/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" @@ -45,6 +47,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) genesis := &core.Genesis{} @@ -404,47 +420,164 @@ func TestReceiveWarpMessage(t *testing.T) { genesis := &core.Genesis{} require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONDurango))) genesis.Config.GenesisPrecompiles = params.Precompiles{ + // Note that warp is enabled without RequirePrimaryNetworkSigners + // by default in the genesis configuration. warp.ConfigKey: warp.NewDefaultConfig(utils.TimeToNewUint64(upgrade.InitiallyActiveTime)), } genesisJSON, err := genesis.MarshalJSON() require.NoError(err) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") + + // disable warp so we can re-enable it with RequirePrimaryNetworkSigners + disableTime := upgrade.InitiallyActiveTime.Add(10 * time.Second) + disableConfig := warp.NewDisableConfig(utils.TimeToNewUint64(disableTime)) + + // re-enable warp with RequirePrimaryNetworkSigners + reEnableTime := disableTime.Add(10 * time.Second) + reEnableConfig := warp.NewConfig( + utils.TimeToNewUint64(reEnableTime), + 0, // QuorumNumerator + true, // RequirePrimaryNetworkSigners + ) + + upgradeConfig := params.UpgradeConfig{ + PrecompileUpgrades: []params.PrecompileUpgrade{ + {Config: disableConfig}, + {Config: reEnableConfig}, + }, + } + upgradeBytes, err := json.Marshal(upgradeConfig) + require.NoError(err) + + issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", string(upgradeBytes)) defer func() { require.NoError(vm.Shutdown(context.Background())) }() - acceptedLogsChan := make(chan []*types.Log, 10) - logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) - defer logsSub.Unsubscribe() + type test struct { + name string + sourceChainID ids.ID + msgFrom warpMsgFrom + useSigners useWarpMsgSigners + blockTime time.Time + } - payloadData := avagoUtils.RandomBytes(100) + 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) @@ -452,30 +585,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(), @@ -495,7 +634,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), @@ -519,12 +658,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()) @@ -548,7 +703,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] @@ -556,7 +710,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 dde04a8695..feede8eb44 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 dc52f3a5df..a29a9f48ed 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 113db6fc43..23b82644a5 100644 --- a/precompile/contracts/warp/predicate_test.go +++ b/precompile/contracts/warp/predicate_test.go @@ -228,6 +228,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 +279,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 +586,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/scripts/build_test.sh b/scripts/build_test.sh index 6f588887e0..2b59930c57 100755 --- a/scripts/build_test.sh +++ b/scripts/build_test.sh @@ -20,5 +20,43 @@ source "$SUBNET_EVM_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. # DO NOT RUN tests from the top level "tests" directory since they are run by ginkgo -# shellcheck disable=SC2046 -go test -shuffle=on -race -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" $(go list ./... | grep -v github.com/ava-labs/subnet-evm/tests) +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 "$@" $(go list ./... | grep -v github.com/ava-labs/subnet-evm/tests) | 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 \ No newline at end of file diff --git a/scripts/known_flakes.txt b/scripts/known_flakes.txt new file mode 100644 index 0000000000..c035e08c25 --- /dev/null +++ b/scripts/known_flakes.txt @@ -0,0 +1,10 @@ +TestClientCancelWebsocket +TestExpDecaySampleNanosecondRegression +TestGolangBindings +TestMempoolEthTxsAppGossipHandling +TestResumeSyncAccountsTrieInterrupted +TestResyncNewRootAfterDeletes +TestTransactionSkipIndexing +TestVMShutdownWhileSyncing +TestWalletNotifications +TestWebsocketLargeRead \ No newline at end of file diff --git a/scripts/run_prometheus.sh b/scripts/run_prometheus.sh index 1952227231..dce9ef1e30 100755 --- a/scripts/run_prometheus.sh +++ b/scripts/run_prometheus.sh @@ -31,7 +31,7 @@ if pgrep --pidfile="${PIDFILE}" -f 'prometheus.*enable-feature=agent' &> /dev/nu exit 0 fi -PROMETHEUS_URL="${PROMETHEUS_URL:-https://prometheus-experimental.avax-dev.network}" +PROMETHEUS_URL="${PROMETHEUS_URL:-https://prometheus-poc.avax-dev.network}" if [[ -z "${PROMETHEUS_URL}" ]]; then echo "Please provide a value for PROMETHEUS_URL" exit 1 diff --git a/scripts/run_promtail.sh b/scripts/run_promtail.sh index 9b386d3d55..5811dcf06f 100755 --- a/scripts/run_promtail.sh +++ b/scripts/run_promtail.sh @@ -30,7 +30,7 @@ if pgrep --pidfile="${PIDFILE}" &> /dev/null; then exit 0 fi -LOKI_URL="${LOKI_URL:-https://loki-experimental.avax-dev.network}" +LOKI_URL="${LOKI_URL:-https://loki-poc.avax-dev.network}" if [[ -z "${LOKI_URL}" ]]; then echo "Please provide a value for LOKI_URL" exit 1 diff --git a/scripts/versions.sh b/scripts/versions.sh index d6f28fb862..7b7959c2d3 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:-'df91c2f4a'} +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'35c66e33f'} GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} # This won't be used, but it's here to make code syncs easier diff --git a/warp/service.go b/warp/service.go index 2bd310f38d..4afe93a168 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/subnet-evm/peer" "github.com/ava-labs/subnet-evm/warp/aggregator" - "github.com/ava-labs/subnet-evm/warp/validators" + warpValidators "github.com/ava-labs/subnet-evm/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 4d642c3e2e..b3dc67ac51 100644 --- a/warp/validators/state_test.go +++ b/warp/validators/state_test.go @@ -26,7 +26,7 @@ func TestGetValidatorSetPrimaryNetwork(t *testing.T) { 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)