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  }