Skip to content

Commit

Permalink
e2e: Add test of validatorsets across nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
marun committed Jan 24, 2024
1 parent c0bc9f5 commit 756fc35
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 4 deletions.
44 changes: 43 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,51 @@ jobs:
uses: actions/upload-artifact@v3
if: always()
with:
name: e2e-tmpnet-data
name: e2e-local-tmpnet-data
path: ${{ env.tmpnet_data_path }}
if-no-files-found: error
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ env.go_version }}
check-latest: true
- name: Build AvalancheGo Binary
shell: bash
run: ./scripts/build.sh -r
- name: Run e2e tests
shell: bash
run: E2E_SERIAL=1 ./scripts/tests.e2e.sh --network-id=808
- name: Upload tmpnet network dir
uses: actions/upload-artifact@v3
if: always()
with:
name: e2e-mainnet-tmpnet-data
path: ~/.tmpnet/networks/808*
if-no-files-found: error
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ env.go_version }}
check-latest: true
- name: Build AvalancheGo Binary
shell: bash
run: ./scripts/build.sh -r
- name: Run e2e tests
shell: bash
run: E2E_SERIAL=1 ./scripts/tests.e2e.sh --network-id=909
- name: Upload tmpnet network dir
uses: actions/upload-artifact@v3
if: always()
with:
name: e2e-fuji-tmpnet-data
path: ~/.tmpnet/networks/909*
if-no-files-found: error
e2e_existing_network:
runs-on: ubuntu-latest
steps:
Expand Down
7 changes: 6 additions & 1 deletion tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/onsi/gomega"

"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"

Expand All @@ -35,7 +36,11 @@ func init() {

var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
// Run only once in the first ginkgo process
return e2e.NewTestEnvironment(flagVars, &tmpnet.Network{}).Marshal()
return e2e.NewTestEnvironment(flagVars, &tmpnet.Network{
Genesis: &genesis.UnparsedConfig{
NetworkID: uint32(flagVars.NetworkID()),
},
}).Marshal()
}, func(envBytes []byte) {
// Run in every ginkgo process

Expand Down
101 changes: 101 additions & 0 deletions tests/e2e/p/validator_sets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package p

import (
"fmt"
"time"

ginkgo "github.com/onsi/ginkgo/v2"

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/tests"
"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/vms/platformvm"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
)

var _ = e2e.DescribePChain("[Validator Sets]", func() {
require := require.New(ginkgo.GinkgoT())

ginkgo.It("should be identical for every height for all nodes in the network", func() {
network := e2e.Env.GetNetwork()

ginkgo.By("creating wallet with a funded key to source delegated funds from")
keychain := e2e.Env.NewKeychain(1)
nodeURI := e2e.Env.GetRandomNodeURI()
baseWallet := e2e.NewWallet(keychain, nodeURI)
pWallet := baseWallet.P()

const delegatorCount = 15
ginkgo.By(fmt.Sprintf("adding %d delegators", delegatorCount), func() {
rewardKey, err := secp256k1.NewPrivateKey()
require.NoError(err)
avaxAssetID := pWallet.AVAXAssetID()
startTime := time.Now().Add(tmpnet.DefaultValidatorStartTimeDiff)
endTime := startTime.Add(time.Second * 360)
// TODO(marun) Ensure this is appropriate for the targeted network (is it accessible from the API?)
weight := genesis.LocalParams.StakingConfig.MinDelegatorStake

for i := 0; i < delegatorCount; i++ {
_, err = pWallet.IssueAddPermissionlessDelegatorTx(
&txs.SubnetValidator{
Validator: txs.Validator{
NodeID: nodeURI.NodeID,
Start: uint64(startTime.Unix()),
End: uint64(endTime.Unix()),
Wght: weight,
},
Subnet: constants.PrimaryNetworkID,
},
avaxAssetID,
&secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{rewardKey.Address()},
},
e2e.WithDefaultContext(),
)
require.NoError(err)
}
})

ginkgo.By("checking that validator sets are equal across all heights for all nodes", func() {
pvmClients := make([]platformvm.Client, len(e2e.Env.URIs))
for i, nodeURI := range e2e.Env.URIs {
pvmClients[i] = platformvm.NewClient(nodeURI.URI)
}

initialHeight, err := pvmClients[0].GetHeight(e2e.DefaultContext())
require.NoError(err)

for height := initialHeight; height > 0; height-- {
tests.Outf(" checked validator sets for height %d\n", height)
var observedValidatorSet map[ids.NodeID]*validators.GetValidatorOutput
for _, pvmClient := range pvmClients {
validatorSet, err := pvmClient.GetValidatorsAt(
e2e.DefaultContext(),
constants.PrimaryNetworkID,
height,
)
require.NoError(err)
if observedValidatorSet == nil {
observedValidatorSet = validatorSet
continue
}
require.Equal(observedValidatorSet, validatorSet)
}
}
})

e2e.CheckBootstrapIsPossible(network)
})
})
11 changes: 11 additions & 0 deletions tests/fixture/e2e/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type FlagVars struct {
avalancheGoExecPath string
pluginDir string
networkDir string
networkID uint
useExistingNetwork bool
}

Expand All @@ -36,6 +37,10 @@ func (v *FlagVars) NetworkDir() string {
return os.Getenv(tmpnet.NetworkDirEnvName)
}

func (v *FlagVars) NetworkID() uint {
return v.networkID
}

func (v *FlagVars) UseExistingNetwork() bool {
return v.useExistingNetwork
}
Expand Down Expand Up @@ -66,6 +71,12 @@ func RegisterFlags() *FlagVars {
false,
"[optional] whether to target the existing network identified by --network-dir.",
)
flag.UintVar(
&vars.networkID,
"network-id",
0,
"[optional] the network ID to use. By default a compatible network ID will be generated. Use 808 for mainnet configuration and 909 for fuji configuration.",
)

return &vars
}
12 changes: 12 additions & 0 deletions tests/fixture/tmpnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,24 @@ the `TMPNET_NETWORK_DIR` env var to this symlink ensures that
`--use-existing-network` will target the most recently deployed temporary
network.

#### Configuring a network with mainnet or fuji configuration

The `tmpnetctl startnetwork` command supports creating a network with
the same ruleset as mainnet or fuji by using specific network IDs. The
network ID `808` is used to configure a local test network with the
same ruleset as mainnet, and `909` is used to configure a local test
network with the same ruleset as fuji. The network ID can be specified
to `tmpnetctl start-network` via the `--network-id` flag.

### Via code

A temporary network can be managed in code:

```golang
network := &tmpnet.Network{ // Configure non-default values for the new network
Genesis: &genesis.UnparsedConfig{
NetworkID: 808, // (Optional) Configure the network with the mainnet ruleset (see previous section)
},
DefaultFlags: tmpnet.FlagsMap{
config.LogLevelKey: "INFO", // Change one of the network's defaults
},
Expand Down
9 changes: 8 additions & 1 deletion tests/fixture/tmpnet/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/spf13/cobra"

"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
"github.com/ava-labs/avalanchego/version"
)
Expand Down Expand Up @@ -52,6 +53,7 @@ func main() {
avalancheGoPath string
pluginDir string
nodeCount uint8
networkID uint32
)
startNetworkCmd := &cobra.Command{
Use: "start-network",
Expand All @@ -63,7 +65,11 @@ func main() {

// Root dir will be defaulted on start if not provided

network := &tmpnet.Network{}
network := &tmpnet.Network{
Genesis: &genesis.UnparsedConfig{
NetworkID: networkID,
},
}

// Extreme upper bound, should never take this long
networkStartTimeout := 2 * time.Minute
Expand Down Expand Up @@ -106,6 +112,7 @@ func main() {
startNetworkCmd.PersistentFlags().StringVar(&avalancheGoPath, "avalanchego-path", os.Getenv(tmpnet.AvalancheGoPathEnvName), "The path to an avalanchego binary")
startNetworkCmd.PersistentFlags().StringVar(&pluginDir, "plugin-dir", os.ExpandEnv("$HOME/.avalanchego/plugins"), "[optional] the dir containing VM plugins")
startNetworkCmd.PersistentFlags().Uint8Var(&nodeCount, "node-count", tmpnet.DefaultNodeCount, "Number of nodes the network should initially consist of")
startNetworkCmd.PersistentFlags().Uint32Var(&networkID, "network-id", 0, "The network ID to use. By default a compatible network ID will be generated. Use 808 for mainnet configuration and 909 for fuji configuration.")
rootCmd.AddCommand(startNetworkCmd)

stopNetworkCmd := &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion tests/fixture/tmpnet/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (n *Network) Create(rootDir string) error {
}
}

if n.Genesis == nil {
if n.Genesis == nil || len(n.Genesis.Allocations) == 0 {
// Pre-fund known legacy keys to support ad-hoc testing. Usage of a legacy key will
// require knowing the key beforehand rather than retrieving it from the set of pre-funded
// keys exposed by a network. Since allocation will not be exclusive, a test using a
Expand Down
4 changes: 4 additions & 0 deletions utils/constants/network_ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const (
UnitTestID uint32 = 10
LocalID uint32 = 12345

// IDs used for validating mainnet and fuji in e2e
TestMainnetID uint32 = 808
TestFujiID uint32 = 909

MainnetName = "mainnet"
CascadeName = "cascade"
DenaliName = "denali"
Expand Down
27 changes: 27 additions & 0 deletions version/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ var (
constants.MainnetID: time.Date(10000, time.December, 1, 0, 0, 0, 0, time.UTC),
constants.FujiID: time.Date(10000, time.December, 1, 0, 0, 0, 0, time.UTC),
}

// Reminder: Add new time variables to updateTimesForTesting to
// ensure e2e is able to test with new versions.
)

func init() {
Expand Down Expand Up @@ -174,6 +177,30 @@ func init() {
constants.MainnetID: mainnetXChainStopVertexID,
constants.FujiID: fujiXChainStopVertexID,
}

updateTimesForTesting(
ApricotPhase1Times,
ApricotPhase2Times,
ApricotPhase3Times,
ApricotPhase4Times,
ApricotPhase5Times,
ApricotPhasePre6Times,
ApricotPhase6Times,
ApricotPhasePost6Times,
BanffTimes,
CortinaTimes,
DurangoTimes,
)
}

// updateTimesForTesting ensures that the provided time maps are updated
// to include entries for contants.TestMainnetID and constants.TestFujiID
// that mirror their mainnet and fuji equivalents to enable e2e testing.
func updateTimesForTesting(timeMaps ...map[uint32]time.Time) {
for _, timeMap := range timeMaps {
timeMap[constants.TestFujiID] = timeMap[constants.FujiID]
timeMap[constants.TestMainnetID] = timeMap[constants.MainnetID]
}
}

func GetApricotPhase1Time(networkID uint32) time.Time {
Expand Down

0 comments on commit 756fc35

Please sign in to comment.