github.com/MetalBlockchain/subnet-evm@v0.6.3/tests/utils/subnet.go (about) 1 // Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package utils 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/MetalBlockchain/metalgo/api/health" 17 "github.com/MetalBlockchain/metalgo/api/info" 18 "github.com/MetalBlockchain/metalgo/genesis" 19 "github.com/MetalBlockchain/metalgo/ids" 20 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 21 wallet "github.com/MetalBlockchain/metalgo/wallet/subnet/primary" 22 "github.com/MetalBlockchain/subnet-evm/core" 23 "github.com/MetalBlockchain/subnet-evm/plugin/evm" 24 "github.com/ethereum/go-ethereum/log" 25 "github.com/go-cmd/cmd" 26 "github.com/onsi/ginkgo/v2" 27 "github.com/onsi/gomega" 28 ) 29 30 type SubnetSuite struct { 31 blockchainIDs map[string]string 32 lock sync.RWMutex 33 } 34 35 func (s *SubnetSuite) GetBlockchainID(alias string) string { 36 s.lock.RLock() 37 defer s.lock.RUnlock() 38 return s.blockchainIDs[alias] 39 } 40 41 func (s *SubnetSuite) SetBlockchainIDs(blockchainIDs map[string]string) { 42 s.lock.Lock() 43 defer s.lock.Unlock() 44 s.blockchainIDs = blockchainIDs 45 } 46 47 // CreateSubnetsSuite creates subnets for given [genesisFiles], and registers a before suite that starts an AvalancheGo process to use for the e2e tests. 48 // genesisFiles is a map of test aliases to genesis file paths. 49 func CreateSubnetsSuite(genesisFiles map[string]string) *SubnetSuite { 50 // Keep track of the AvalancheGo external bash script, it is null for most 51 // processes except the first process that starts AvalancheGo 52 var startCmd *cmd.Cmd 53 54 // This is used to pass the blockchain IDs from the SynchronizedBeforeSuite() to the tests 55 var globalSuite SubnetSuite 56 57 // Our test suite runs in separate processes, ginkgo has 58 // SynchronizedBeforeSuite() which runs once, and its return value is passed 59 // over to each worker. 60 // 61 // Here an AvalancheGo node instance is started, and subnets are created for 62 // each test case. Each test case has its own subnet, therefore all tests 63 // can run in parallel without any issue. 64 // 65 var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { 66 ctx, cancel := context.WithTimeout(context.Background(), BootAvalancheNodeTimeout) 67 defer cancel() 68 69 wd, err := os.Getwd() 70 gomega.Expect(err).Should(gomega.BeNil()) 71 log.Info("Starting AvalancheGo node", "wd", wd) 72 cmd, err := RunCommand("./scripts/run.sh") 73 startCmd = cmd 74 gomega.Expect(err).Should(gomega.BeNil()) 75 76 // Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI] 77 healthClient := health.NewClient(DefaultLocalNodeURI) 78 healthy, err := health.AwaitReady(ctx, healthClient, HealthCheckTimeout, nil) 79 gomega.Expect(err).Should(gomega.BeNil()) 80 gomega.Expect(healthy).Should(gomega.BeTrue()) 81 log.Info("AvalancheGo node is healthy") 82 83 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 84 blockchainIDs := make(map[string]string) 85 for alias, file := range genesisFiles { 86 blockchainIDs[alias] = CreateNewSubnet(ctx, file) 87 } 88 89 blockchainIDsBytes, err := json.Marshal(blockchainIDs) 90 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 91 return blockchainIDsBytes 92 }, func(ctx ginkgo.SpecContext, data []byte) { 93 blockchainIDs := make(map[string]string) 94 err := json.Unmarshal(data, &blockchainIDs) 95 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 96 97 globalSuite.SetBlockchainIDs(blockchainIDs) 98 }) 99 100 // SynchronizedAfterSuite() takes two functions, the first runs after each test suite is done and the second 101 // function is executed once when all the tests are done. This function is used 102 // to gracefully shutdown the AvalancheGo node. 103 var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { 104 gomega.Expect(startCmd).ShouldNot(gomega.BeNil()) 105 gomega.Expect(startCmd.Stop()).Should(gomega.BeNil()) 106 }) 107 108 return &globalSuite 109 } 110 111 // CreateNewSubnet creates a new subnet and Subnet-EVM blockchain with the given genesis file. 112 // returns the ID of the new created blockchain. 113 func CreateNewSubnet(ctx context.Context, genesisFilePath string) string { 114 kc := secp256k1fx.NewKeychain(genesis.EWOQKey) 115 116 // MakeWallet fetches the available UTXOs owned by [kc] on the network 117 // that [LocalAPIURI] is hosting. 118 wallet, err := wallet.MakeWallet(ctx, &wallet.WalletConfig{ 119 URI: DefaultLocalNodeURI, 120 AVAXKeychain: kc, 121 EthKeychain: kc, 122 }) 123 gomega.Expect(err).Should(gomega.BeNil()) 124 125 pWallet := wallet.P() 126 127 owner := &secp256k1fx.OutputOwners{ 128 Threshold: 1, 129 Addrs: []ids.ShortID{ 130 genesis.EWOQKey.PublicKey().Address(), 131 }, 132 } 133 134 wd, err := os.Getwd() 135 gomega.Expect(err).Should(gomega.BeNil()) 136 log.Info("Reading genesis file", "filePath", genesisFilePath, "wd", wd) 137 genesisBytes, err := os.ReadFile(genesisFilePath) 138 gomega.Expect(err).Should(gomega.BeNil()) 139 140 log.Info("Creating new subnet") 141 createSubnetTx, err := pWallet.IssueCreateSubnetTx(owner) 142 gomega.Expect(err).Should(gomega.BeNil()) 143 144 genesis := &core.Genesis{} 145 err = json.Unmarshal(genesisBytes, genesis) 146 gomega.Expect(err).Should(gomega.BeNil()) 147 148 log.Info("Creating new Subnet-EVM blockchain", "genesis", genesis) 149 createChainTx, err := pWallet.IssueCreateChainTx( 150 createSubnetTx.ID(), 151 genesisBytes, 152 evm.ID, 153 nil, 154 "testChain", 155 ) 156 gomega.Expect(err).Should(gomega.BeNil()) 157 createChainTxID := createChainTx.ID() 158 159 // Confirm the new blockchain is ready by waiting for the readiness endpoint 160 infoClient := info.NewClient(DefaultLocalNodeURI) 161 bootstrapped, err := info.AwaitBootstrapped(ctx, infoClient, createChainTxID.String(), 2*time.Second) 162 gomega.Expect(err).Should(gomega.BeNil()) 163 gomega.Expect(bootstrapped).Should(gomega.BeTrue()) 164 165 // Return the blockchainID of the newly created blockchain 166 return createChainTxID.String() 167 } 168 169 // GetDefaultChainURI returns the default chain URI for a given blockchainID 170 func GetDefaultChainURI(blockchainID string) string { 171 return fmt.Sprintf("%s/ext/bc/%s/rpc", DefaultLocalNodeURI, blockchainID) 172 } 173 174 // GetFilesAndAliases returns a map of aliases to file paths in given [dir]. 175 func GetFilesAndAliases(dir string) (map[string]string, error) { 176 files, err := filepath.Glob(dir) 177 if err != nil { 178 return nil, err 179 } 180 aliasesToFiles := make(map[string]string) 181 for _, file := range files { 182 alias := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) 183 aliasesToFiles[alias] = file 184 } 185 return aliasesToFiles, nil 186 }