diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1dfd862d56..b3f8911a842 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ concurrency: env: go_version: '~1.21.8' - tmpnet_data_path: ~/.tmpnet/networks/1000 + tmpnet_data_path: ~/.tmpnet/networks jobs: Unit: diff --git a/go.mod b/go.mod index 7f960396638..18587575260 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/ethereum/go-ethereum v1.12.0 github.com/google/btree v1.1.2 github.com/google/renameio/v2 v2.0.0 + github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 @@ -99,7 +100,6 @@ require ( github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 50ab608a3c4..fa28ecd8850 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -77,12 +77,12 @@ $ ./scripts/build_tmpnetctl.sh # Start a new network $ ./build/tmpnetctl start-network --avalanchego-path=/path/to/avalanchego ... -Started network 1000 @ /home/me/.tmpnet/networks/1000 +Started network /home/me/.tmpnet/networks/20240306-152305.924531 (UUID: abaab590-b375-44f6-9ca5-f8a6dc061725) Configure tmpnetctl and the test suite to target this network by default with one of the following statements: - - source /home/me/.tmpnet/networks/1000/network.env - - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/1000 + - source /home/me/.tmpnet/networks/20240306-152305.924531/network.env + - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/20240306-152305.924531 - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/latest # Start a new test run using the existing network diff --git a/tests/e2e/c/dynamic_fees.go b/tests/e2e/c/dynamic_fees.go index d218f2df4fa..9af074894af 100644 --- a/tests/e2e/c/dynamic_fees.go +++ b/tests/e2e/c/dynamic_fees.go @@ -37,7 +37,10 @@ var _ = e2e.DescribeCChain("[Dynamic Fees]", func() { ginkgo.It("should ensure that the gas price is affected by load", func() { ginkgo.By("creating a new private network to ensure isolation from other tests") - privateNetwork := e2e.Env.NewPrivateNetwork() + privateNetwork := &tmpnet.Network{ + Owner: "avalanchego-e2e-dynamic-fees", + } + e2e.Env.StartPrivateNetwork(privateNetwork) ginkgo.By("allocating a pre-funded key") key := privateNetwork.PreFundedKeys[0] diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 3f526d84a94..7642c3ce3e9 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -35,7 +35,12 @@ 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{ + Owner: "avalanchego-e2e", + }, + ).Marshal() }, func(envBytes []byte) { // Run in every ginkgo process diff --git a/tests/fixture/e2e/env.go b/tests/fixture/e2e/env.go index a5fe08d4d89..036e5218f62 100644 --- a/tests/fixture/e2e/env.go +++ b/tests/fixture/e2e/env.go @@ -6,8 +6,6 @@ package e2e import ( "encoding/json" "math/rand" - "os" - "path/filepath" "time" "github.com/stretchr/testify/require" @@ -18,7 +16,6 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ginkgo "github.com/onsi/ginkgo/v2" @@ -77,7 +74,7 @@ func NewTestEnvironment(flagVars *FlagVars, desiredNetwork *tmpnet.Network) *Tes } } else { network = desiredNetwork - StartNetwork(network, DefaultNetworkDir, flagVars.AvalancheGoExecPath(), flagVars.PluginDir()) + StartNetwork(network, flagVars.AvalancheGoExecPath(), flagVars.PluginDir()) } // A new network will always need subnet creation and an existing @@ -158,27 +155,17 @@ func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { } // Create a new private network that is not shared with other tests. -func (te *TestEnvironment) NewPrivateNetwork() *tmpnet.Network { - // Load the shared network to retrieve its path and exec path +func (te *TestEnvironment) StartPrivateNetwork(network *tmpnet.Network) { + // Use the same configuration as the shared network sharedNetwork, err := tmpnet.ReadNetwork(te.NetworkDir) te.require.NoError(err) - network := &tmpnet.Network{} - - // The private networks dir is under the shared network dir to ensure it - // will be included in the artifact uploaded in CI. - privateNetworksDir := filepath.Join(sharedNetwork.Dir, PrivateNetworksDirName) - te.require.NoError(os.MkdirAll(privateNetworksDir, perms.ReadWriteExecute)) - pluginDir, err := sharedNetwork.DefaultFlags.GetStringVal(config.PluginDirKey) te.require.NoError(err) StartNetwork( network, - privateNetworksDir, sharedNetwork.DefaultRuntimeConfig.AvalancheGoPath, pluginDir, ) - - return network } diff --git a/tests/fixture/e2e/helpers.go b/tests/fixture/e2e/helpers.go index c88f3cacfb1..c2382cee9c7 100644 --- a/tests/fixture/e2e/helpers.go +++ b/tests/fixture/e2e/helpers.go @@ -216,7 +216,7 @@ func CheckBootstrapIsPossible(network *tmpnet.Network) { } // Start a temporary network with the provided avalanchego binary. -func StartNetwork(network *tmpnet.Network, rootNetworkDir string, avalancheGoExecPath string, pluginDir string) { +func StartNetwork(network *tmpnet.Network, avalancheGoExecPath string, pluginDir string) { require := require.New(ginkgo.GinkgoT()) require.NoError( @@ -224,7 +224,7 @@ func StartNetwork(network *tmpnet.Network, rootNetworkDir string, avalancheGoExe DefaultContext(), ginkgo.GinkgoWriter, network, - rootNetworkDir, + DefaultNetworkDir, avalancheGoExecPath, pluginDir, tmpnet.DefaultNodeCount, diff --git a/tests/fixture/tmpnet/README.md b/tests/fixture/tmpnet/README.md index 909a29c6ee1..bfeebf93b98 100644 --- a/tests/fixture/tmpnet/README.md +++ b/tests/fixture/tmpnet/README.md @@ -52,11 +52,11 @@ $ ./scripts/build_tmpnetctl.sh # Start a new network $ ./build/tmpnetctl start-network --avalanchego-path=/path/to/avalanchego ... -Started network 1000 @ /home/me/.tmpnet/networks/1000 +Started network /home/me/.tmpnet/networks/20240306-152305.924531 (UUID: abaab590-b375-44f6-9ca5-f8a6dc061725) Configure tmpnetctl to target this network by default with one of the following statements: - - source /home/me/.tmpnet/networks/1000/network.env - - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/1000 + - source /home/me/.tmpnet/networks/20240306-152305.924531/network.env + - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/20240306-152305.924531 - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/latest # Stop the network @@ -129,7 +129,7 @@ A temporary network relies on configuration written to disk in the following str HOME └── .tmpnet // Root path for the temporary network fixture └── networks // Default parent directory for temporary networks - └── 1000 // The networkID is used to name the network dir and starts at 1000 + └── 20240306-152305.924531 // The timestamp of creation is the name of a network's directory ├── NodeID-37E8UK3x2YFsHE3RdALmfWcppcZ1eTuj9 // The ID of a node is the name of its data dir │ ├── chainData │ │ └── ... diff --git a/tests/fixture/tmpnet/cmd/main.go b/tests/fixture/tmpnet/cmd/main.go index dd59c300bbb..af717a50a3c 100644 --- a/tests/fixture/tmpnet/cmd/main.go +++ b/tests/fixture/tmpnet/cmd/main.go @@ -49,6 +49,7 @@ func main() { var ( rootDir string + networkOwner string avalancheGoPath string pluginDir string nodeCount uint8 @@ -63,7 +64,9 @@ func main() { // Root dir will be defaulted on start if not provided - network := &tmpnet.Network{} + network := &tmpnet.Network{ + Owner: networkOwner, + } // Extreme upper bound, should never take this long networkStartTimeout := 2 * time.Minute @@ -106,6 +109,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().StringVar(&networkOwner, "network-owner", "", "The string identifying the intended owner of the network") rootCmd.AddCommand(startNetworkCmd) stopNetworkCmd := &cobra.Command{ diff --git a/tests/fixture/tmpnet/network.go b/tests/fixture/tmpnet/network.go index 28626af3c4d..954162c3fcf 100644 --- a/tests/fixture/tmpnet/network.go +++ b/tests/fixture/tmpnet/network.go @@ -9,17 +9,17 @@ import ( "errors" "fmt" "io" - "io/fs" "os" "path/filepath" "strconv" "strings" "time" + "github.com/google/uuid" + "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/set" @@ -40,6 +40,9 @@ const ( // increase the time for a network's nodes to be seen as healthy. networkHealthCheckInterval = 200 * time.Millisecond + // All temporary networks will use this arbitrary network ID by default. + defaultNetworkID = 88888 + // eth address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC HardHatKeyStr = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" ) @@ -61,6 +64,16 @@ func init() { // Collects the configuration for running a temporary avalanchego network type Network struct { + // Uniquely identifies the temporary network for metrics + // collection. Distinct from avalanchego's concept of network ID + // since the utility of special network ID values (e.g. to trigger + // specific fork behavior in a given network) precludes requiring + // unique network ID values across all temporary networks. + UUID string + + // A string identifying the entity that started or maintains this network. + Owner string + // Path where network configuration and data is stored Dir string @@ -150,6 +163,11 @@ func (n *Network) EnsureDefaultConfig(w io.Writer, avalancheGoPath string, plugi return err } + // A UUID supports centralized metrics collection + if len(n.UUID) == 0 { + n.UUID = uuid.NewString() + } + // Ensure default flags if n.DefaultFlags == nil { n.DefaultFlags = FlagsMap{} @@ -206,8 +224,9 @@ func (n *Network) EnsureDefaultConfig(w io.Writer, avalancheGoPath string, plugi return nil } -// Creates the network on disk, choosing its network id and generating its genesis in the process. +// Creates the network on disk, generating its genesis and configuring its nodes in the process. func (n *Network) Create(rootDir string) error { + // Ensure creation of the root dir if len(rootDir) == 0 { // Use the default root dir var err error @@ -216,35 +235,21 @@ func (n *Network) Create(rootDir string) error { return err } } - - // Ensure creation of the root dir if err := os.MkdirAll(rootDir, perms.ReadWriteExecute); err != nil { return fmt.Errorf("failed to create root network dir: %w", err) } - // Determine the network path and ID - var ( - networkDir string - networkID uint32 - ) - if n.Genesis != nil && n.Genesis.NetworkID > 0 { - // Use the network ID defined in the provided genesis - networkID = n.Genesis.NetworkID + // A time-based name ensures consistent directory ordering + dirName := time.Now().Format("20060102-150405.999999") + if len(n.Owner) > 0 { + // Include the owner to differentiate networks created at similar times + dirName = fmt.Sprintf("%s-%s", dirName, n.Owner) } - if networkID > 0 { - // Use a directory with a random suffix - var err error - networkDir, err = os.MkdirTemp(rootDir, fmt.Sprintf("%d.", n.Genesis.NetworkID)) - if err != nil { - return fmt.Errorf("failed to create network dir: %w", err) - } - } else { - // Find the next available network ID based on the contents of the root dir - var err error - networkID, networkDir, err = findNextNetworkID(rootDir) - if err != nil { - return err - } + + // Ensure creation of the network dir + networkDir := filepath.Join(rootDir, dirName) + if err := os.MkdirAll(networkDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create network dir: %w", err) } canonicalDir, err := toCanonicalDir(networkDir) if err != nil { @@ -252,12 +257,12 @@ func (n *Network) Create(rootDir string) error { } n.Dir = canonicalDir + // Ensure the existence of the plugin directory or nodes won't be able to start. pluginDir, err := n.DefaultFlags.GetStringVal(config.PluginDirKey) if err != nil { return err } if len(pluginDir) > 0 { - // Ensure the existence of the plugin directory or nodes won't be able to start. if err := os.MkdirAll(pluginDir, perms.ReadWriteExecute); err != nil { return fmt.Errorf("failed to create plugin dir: %w", err) } @@ -275,7 +280,7 @@ func (n *Network) Create(rootDir string) error { } keysToFund = append(keysToFund, n.PreFundedKeys...) - genesis, err := NewTestGenesis(networkID, n.Nodes, keysToFund) + genesis, err := NewTestGenesis(defaultNetworkID, n.Nodes, keysToFund) if err != nil { return err } @@ -296,7 +301,7 @@ func (n *Network) Create(rootDir string) error { // Starts all nodes in the network func (n *Network) Start(ctx context.Context, w io.Writer) error { - if _, err := fmt.Fprintf(w, "Starting network %d @ %s\n", n.Genesis.NetworkID, n.Dir); err != nil { + if _, err := fmt.Fprintf(w, "Starting network %s (UUID: %s)\n", n.Dir, n.UUID); err != nil { return err } @@ -313,7 +318,7 @@ func (n *Network) Start(ctx context.Context, w io.Writer) error { if err := n.WaitForHealthy(ctx, w); err != nil { return err } - if _, err := fmt.Fprintf(w, "\nStarted network %d @ %s\n", n.Genesis.NetworkID, n.Dir); err != nil { + if _, err := fmt.Fprintf(w, "\nStarted network %s (UUID: %s)\n", n.Dir, n.UUID); err != nil { return err } @@ -676,33 +681,3 @@ func getDefaultRootDir() (string, error) { } return filepath.Join(homeDir, ".tmpnet", "networks"), nil } - -// Finds the next available network ID by attempting to create a -// directory numbered from 1000 until creation succeeds. Returns the -// network id and the full path of the created directory. -func findNextNetworkID(rootDir string) (uint32, string, error) { - var ( - networkID uint32 = 1000 - dirPath string - ) - for { - _, reserved := constants.NetworkIDToNetworkName[networkID] - if reserved { - networkID++ - continue - } - - dirPath = filepath.Join(rootDir, strconv.FormatUint(uint64(networkID), 10)) - err := os.Mkdir(dirPath, perms.ReadWriteExecute) - if err == nil { - return networkID, dirPath, nil - } - - if !errors.Is(err, fs.ErrExist) { - return 0, "", fmt.Errorf("failed to create network directory: %w", err) - } - - // Directory already exists, keep iterating - networkID++ - } -} diff --git a/tests/fixture/tmpnet/network_config.go b/tests/fixture/tmpnet/network_config.go index 1ae4e96788d..7aee35cb8a3 100644 --- a/tests/fixture/tmpnet/network_config.go +++ b/tests/fixture/tmpnet/network_config.go @@ -185,6 +185,8 @@ func (n *Network) readConfig() error { // The subset of network fields to store in the network config file. type serializedNetworkConfig struct { + UUID string + Owner string DefaultFlags FlagsMap DefaultRuntimeConfig NodeRuntimeConfig PreFundedKeys []*secp256k1.PrivateKey @@ -192,6 +194,8 @@ type serializedNetworkConfig struct { func (n *Network) writeNetworkConfig() error { config := &serializedNetworkConfig{ + UUID: n.UUID, + Owner: n.Owner, DefaultFlags: n.DefaultFlags, DefaultRuntimeConfig: n.DefaultRuntimeConfig, PreFundedKeys: n.PreFundedKeys, diff --git a/tests/upgrade/upgrade_test.go b/tests/upgrade/upgrade_test.go index 131d3d53cd2..ed4bfd0fa13 100644 --- a/tests/upgrade/upgrade_test.go +++ b/tests/upgrade/upgrade_test.go @@ -45,8 +45,10 @@ var _ = ginkgo.Describe("[Upgrade]", func() { require := require.New(ginkgo.GinkgoT()) ginkgo.It("can upgrade versions", func() { - network := &tmpnet.Network{} - e2e.StartNetwork(network, e2e.DefaultNetworkDir, avalancheGoExecPath, "" /* pluginDir */) + network := &tmpnet.Network{ + Owner: "avalanchego-upgrade", + } + e2e.StartNetwork(network, avalancheGoExecPath, "" /* pluginDir */) ginkgo.By(fmt.Sprintf("restarting all nodes with %q binary", avalancheGoExecPathToUpgradeTo)) for _, node := range network.Nodes {