github.com/vipernet-xyz/tm@v0.34.24/test/e2e/pkg/testnet.go (about)

     1  package e2e
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"math/rand"
     8  	"net"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/vipernet-xyz/tm/crypto"
    15  	"github.com/vipernet-xyz/tm/crypto/ed25519"
    16  	"github.com/vipernet-xyz/tm/crypto/secp256k1"
    17  	rpchttp "github.com/vipernet-xyz/tm/rpc/client/http"
    18  	mcs "github.com/vipernet-xyz/tm/test/maverick/consensus"
    19  )
    20  
    21  const (
    22  	randomSeed     int64  = 2308084734268
    23  	proxyPortFirst uint32 = 5701
    24  )
    25  
    26  type (
    27  	Mode         string
    28  	Protocol     string
    29  	Perturbation string
    30  )
    31  
    32  const (
    33  	ModeValidator Mode = "validator"
    34  	ModeFull      Mode = "full"
    35  	ModeLight     Mode = "light"
    36  	ModeSeed      Mode = "seed"
    37  
    38  	ProtocolBuiltin Protocol = "builtin"
    39  	ProtocolFile    Protocol = "file"
    40  	ProtocolGRPC    Protocol = "grpc"
    41  	ProtocolTCP     Protocol = "tcp"
    42  	ProtocolUNIX    Protocol = "unix"
    43  
    44  	PerturbationDisconnect Perturbation = "disconnect"
    45  	PerturbationKill       Perturbation = "kill"
    46  	PerturbationPause      Perturbation = "pause"
    47  	PerturbationRestart    Perturbation = "restart"
    48  )
    49  
    50  // Testnet represents a single testnet.
    51  type Testnet struct {
    52  	Name             string
    53  	File             string
    54  	Dir              string
    55  	IP               *net.IPNet
    56  	InitialHeight    int64
    57  	InitialState     map[string]string
    58  	Validators       map[*Node]int64
    59  	ValidatorUpdates map[int64]map[*Node]int64
    60  	Nodes            []*Node
    61  	KeyType          string
    62  	ABCIProtocol     string
    63  }
    64  
    65  // Node represents a Tendermint node in a testnet.
    66  type Node struct {
    67  	Name             string
    68  	Testnet          *Testnet
    69  	Mode             Mode
    70  	PrivvalKey       crypto.PrivKey
    71  	NodeKey          crypto.PrivKey
    72  	IP               net.IP
    73  	ProxyPort        uint32
    74  	StartAt          int64
    75  	FastSync         string
    76  	StateSync        bool
    77  	Mempool          string
    78  	Database         string
    79  	ABCIProtocol     Protocol
    80  	PrivvalProtocol  Protocol
    81  	PersistInterval  uint64
    82  	SnapshotInterval uint64
    83  	RetainBlocks     uint64
    84  	Seeds            []*Node
    85  	PersistentPeers  []*Node
    86  	Perturbations    []Perturbation
    87  	Misbehaviors     map[int64]string
    88  }
    89  
    90  // LoadTestnet loads a testnet from a manifest file, using the filename to
    91  // determine the testnet name and directory (from the basename of the file).
    92  // The testnet generation must be deterministic, since it is generated
    93  // separately by the runner and the test cases. For this reason, testnets use a
    94  // random seed to generate e.g. keys.
    95  func LoadTestnet(manifest Manifest, fname string, ifd InfrastructureData) (*Testnet, error) {
    96  	dir := strings.TrimSuffix(fname, filepath.Ext(fname))
    97  	keyGen := newKeyGenerator(randomSeed)
    98  	proxyPortGen := newPortGenerator(proxyPortFirst)
    99  	_, ipNet, err := net.ParseCIDR(ifd.Network)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("invalid IP network address %q: %w", ifd.Network, err)
   102  	}
   103  
   104  	testnet := &Testnet{
   105  		Name:             filepath.Base(dir),
   106  		File:             fname,
   107  		Dir:              dir,
   108  		IP:               ipNet,
   109  		InitialHeight:    1,
   110  		InitialState:     manifest.InitialState,
   111  		Validators:       map[*Node]int64{},
   112  		ValidatorUpdates: map[int64]map[*Node]int64{},
   113  		Nodes:            []*Node{},
   114  		ABCIProtocol:     manifest.ABCIProtocol,
   115  	}
   116  	if len(manifest.KeyType) != 0 {
   117  		testnet.KeyType = manifest.KeyType
   118  	}
   119  	if manifest.InitialHeight > 0 {
   120  		testnet.InitialHeight = manifest.InitialHeight
   121  	}
   122  	if testnet.ABCIProtocol == "" {
   123  		testnet.ABCIProtocol = string(ProtocolBuiltin)
   124  	}
   125  
   126  	// Set up nodes, in alphabetical order (IPs and ports get same order).
   127  	nodeNames := []string{}
   128  	for name := range manifest.Nodes {
   129  		nodeNames = append(nodeNames, name)
   130  	}
   131  	sort.Strings(nodeNames)
   132  
   133  	for _, name := range nodeNames {
   134  		nodeManifest := manifest.Nodes[name]
   135  		ind, ok := ifd.Instances[name]
   136  		if !ok {
   137  			return nil, fmt.Errorf("information for node '%s' missing from infrastucture data", name)
   138  		}
   139  		node := &Node{
   140  			Name:             name,
   141  			Testnet:          testnet,
   142  			PrivvalKey:       keyGen.Generate(manifest.KeyType),
   143  			NodeKey:          keyGen.Generate("ed25519"),
   144  			IP:               ind.IPAddress,
   145  			ProxyPort:        proxyPortGen.Next(),
   146  			Mode:             ModeValidator,
   147  			Database:         "goleveldb",
   148  			ABCIProtocol:     Protocol(testnet.ABCIProtocol),
   149  			PrivvalProtocol:  ProtocolFile,
   150  			StartAt:          nodeManifest.StartAt,
   151  			FastSync:         nodeManifest.FastSync,
   152  			Mempool:          nodeManifest.Mempool,
   153  			StateSync:        nodeManifest.StateSync,
   154  			PersistInterval:  1,
   155  			SnapshotInterval: nodeManifest.SnapshotInterval,
   156  			RetainBlocks:     nodeManifest.RetainBlocks,
   157  			Perturbations:    []Perturbation{},
   158  			Misbehaviors:     make(map[int64]string),
   159  		}
   160  		if node.StartAt == testnet.InitialHeight {
   161  			node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this
   162  		}
   163  		if nodeManifest.Mode != "" {
   164  			node.Mode = Mode(nodeManifest.Mode)
   165  		}
   166  		if node.Mode == ModeLight {
   167  			node.ABCIProtocol = ProtocolBuiltin
   168  		}
   169  		if nodeManifest.Database != "" {
   170  			node.Database = nodeManifest.Database
   171  		}
   172  		if nodeManifest.PrivvalProtocol != "" {
   173  			node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol)
   174  		}
   175  		if nodeManifest.PersistInterval != nil {
   176  			node.PersistInterval = *nodeManifest.PersistInterval
   177  		}
   178  		for _, p := range nodeManifest.Perturb {
   179  			node.Perturbations = append(node.Perturbations, Perturbation(p))
   180  		}
   181  		for heightString, misbehavior := range nodeManifest.Misbehaviors {
   182  			height, err := strconv.ParseInt(heightString, 10, 64)
   183  			if err != nil {
   184  				return nil, fmt.Errorf("unable to parse height %s to int64: %w", heightString, err)
   185  			}
   186  			node.Misbehaviors[height] = misbehavior
   187  		}
   188  		testnet.Nodes = append(testnet.Nodes, node)
   189  	}
   190  
   191  	// We do a second pass to set up seeds and persistent peers, which allows graph cycles.
   192  	for _, node := range testnet.Nodes {
   193  		nodeManifest, ok := manifest.Nodes[node.Name]
   194  		if !ok {
   195  			return nil, fmt.Errorf("failed to look up manifest for node %q", node.Name)
   196  		}
   197  		for _, seedName := range nodeManifest.Seeds {
   198  			seed := testnet.LookupNode(seedName)
   199  			if seed == nil {
   200  				return nil, fmt.Errorf("unknown seed %q for node %q", seedName, node.Name)
   201  			}
   202  			node.Seeds = append(node.Seeds, seed)
   203  		}
   204  		for _, peerName := range nodeManifest.PersistentPeers {
   205  			peer := testnet.LookupNode(peerName)
   206  			if peer == nil {
   207  				return nil, fmt.Errorf("unknown persistent peer %q for node %q", peerName, node.Name)
   208  			}
   209  			node.PersistentPeers = append(node.PersistentPeers, peer)
   210  		}
   211  
   212  		// If there are no seeds or persistent peers specified, default to persistent
   213  		// connections to all other nodes.
   214  		if len(node.PersistentPeers) == 0 && len(node.Seeds) == 0 {
   215  			for _, peer := range testnet.Nodes {
   216  				if peer.Name == node.Name {
   217  					continue
   218  				}
   219  				node.PersistentPeers = append(node.PersistentPeers, peer)
   220  			}
   221  		}
   222  	}
   223  
   224  	// Set up genesis validators. If not specified explicitly, use all validator nodes.
   225  	if manifest.Validators != nil {
   226  		for validatorName, power := range *manifest.Validators {
   227  			validator := testnet.LookupNode(validatorName)
   228  			if validator == nil {
   229  				return nil, fmt.Errorf("unknown validator %q", validatorName)
   230  			}
   231  			testnet.Validators[validator] = power
   232  		}
   233  	} else {
   234  		for _, node := range testnet.Nodes {
   235  			if node.Mode == ModeValidator {
   236  				testnet.Validators[node] = 100
   237  			}
   238  		}
   239  	}
   240  
   241  	// Set up validator updates.
   242  	for heightStr, validators := range manifest.ValidatorUpdates {
   243  		height, err := strconv.Atoi(heightStr)
   244  		if err != nil {
   245  			return nil, fmt.Errorf("invalid validator update height %q: %w", height, err)
   246  		}
   247  		valUpdate := map[*Node]int64{}
   248  		for name, power := range validators {
   249  			node := testnet.LookupNode(name)
   250  			if node == nil {
   251  				return nil, fmt.Errorf("unknown validator %q for update at height %v", name, height)
   252  			}
   253  			valUpdate[node] = power
   254  		}
   255  		testnet.ValidatorUpdates[int64(height)] = valUpdate
   256  	}
   257  
   258  	return testnet, testnet.Validate()
   259  }
   260  
   261  // Validate validates a testnet.
   262  func (t Testnet) Validate() error {
   263  	if t.Name == "" {
   264  		return errors.New("network has no name")
   265  	}
   266  	if t.IP == nil {
   267  		return errors.New("network has no IP")
   268  	}
   269  	if len(t.Nodes) == 0 {
   270  		return errors.New("network has no nodes")
   271  	}
   272  	for _, node := range t.Nodes {
   273  		if err := node.Validate(t); err != nil {
   274  			return fmt.Errorf("invalid node %q: %w", node.Name, err)
   275  		}
   276  	}
   277  	return nil
   278  }
   279  
   280  // Validate validates a node.
   281  func (n Node) Validate(testnet Testnet) error {
   282  	if n.Name == "" {
   283  		return errors.New("node has no name")
   284  	}
   285  	if n.IP == nil {
   286  		return errors.New("node has no IP address")
   287  	}
   288  	if !testnet.IP.Contains(n.IP) {
   289  		return fmt.Errorf("node IP %v is not in testnet network %v", n.IP, testnet.IP)
   290  	}
   291  	if n.ProxyPort > 0 {
   292  		if n.ProxyPort <= 1024 {
   293  			return fmt.Errorf("local port %v must be >1024", n.ProxyPort)
   294  		}
   295  		for _, peer := range testnet.Nodes {
   296  			if peer.Name != n.Name && peer.ProxyPort == n.ProxyPort {
   297  				return fmt.Errorf("peer %q also has local port %v", peer.Name, n.ProxyPort)
   298  			}
   299  		}
   300  	}
   301  	switch n.FastSync {
   302  	case "", "v0", "v1", "v2":
   303  	default:
   304  		return fmt.Errorf("invalid fast sync setting %q", n.FastSync)
   305  
   306  	}
   307  	switch n.Mempool {
   308  	case "", "v0", "v1":
   309  	default:
   310  		return fmt.Errorf("invalid mempool version %q", n.Mempool)
   311  	}
   312  	switch n.Database {
   313  	case "goleveldb", "cleveldb", "boltdb", "rocksdb", "badgerdb":
   314  	default:
   315  		return fmt.Errorf("invalid database setting %q", n.Database)
   316  	}
   317  	switch n.ABCIProtocol {
   318  	case ProtocolBuiltin, ProtocolUNIX, ProtocolTCP, ProtocolGRPC:
   319  	default:
   320  		return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol)
   321  	}
   322  	if n.Mode == ModeLight && n.ABCIProtocol != ProtocolBuiltin {
   323  		return errors.New("light client must use builtin protocol")
   324  	}
   325  	switch n.PrivvalProtocol {
   326  	case ProtocolFile, ProtocolUNIX, ProtocolTCP:
   327  	default:
   328  		return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol)
   329  	}
   330  
   331  	if n.StartAt > 0 && n.StartAt < n.Testnet.InitialHeight {
   332  		return fmt.Errorf("cannot start at height %v lower than initial height %v",
   333  			n.StartAt, n.Testnet.InitialHeight)
   334  	}
   335  	if n.StateSync && n.StartAt == 0 {
   336  		return errors.New("state synced nodes cannot start at the initial height")
   337  	}
   338  	if n.PersistInterval == 0 && n.RetainBlocks > 0 {
   339  		return errors.New("persist_interval=0 requires retain_blocks=0")
   340  	}
   341  	if n.PersistInterval > 1 && n.RetainBlocks > 0 && n.RetainBlocks < n.PersistInterval {
   342  		return errors.New("persist_interval must be less than or equal to retain_blocks")
   343  	}
   344  	if n.SnapshotInterval > 0 && n.RetainBlocks > 0 && n.RetainBlocks < n.SnapshotInterval {
   345  		return errors.New("snapshot_interval must be less than er equal to retain_blocks")
   346  	}
   347  
   348  	for _, perturbation := range n.Perturbations {
   349  		switch perturbation {
   350  		case PerturbationDisconnect, PerturbationKill, PerturbationPause, PerturbationRestart:
   351  		default:
   352  			return fmt.Errorf("invalid perturbation %q", perturbation)
   353  		}
   354  	}
   355  
   356  	if (n.PrivvalProtocol != "file" || n.Mode != "validator") && len(n.Misbehaviors) != 0 {
   357  		return errors.New("must be using \"file\" privval protocol to implement misbehaviors")
   358  	}
   359  
   360  	for height, misbehavior := range n.Misbehaviors {
   361  		if height < n.StartAt {
   362  			return fmt.Errorf("misbehavior height %d is below node start height %d",
   363  				height, n.StartAt)
   364  		}
   365  		if height < testnet.InitialHeight {
   366  			return fmt.Errorf("misbehavior height %d is below network initial height %d",
   367  				height, testnet.InitialHeight)
   368  		}
   369  		exists := false
   370  		for possibleBehaviors := range mcs.MisbehaviorList {
   371  			if possibleBehaviors == misbehavior {
   372  				exists = true
   373  			}
   374  		}
   375  		if !exists {
   376  			return fmt.Errorf("misbehavior %s does not exist", misbehavior)
   377  		}
   378  	}
   379  
   380  	return nil
   381  }
   382  
   383  // LookupNode looks up a node by name. For now, simply do a linear search.
   384  func (t Testnet) LookupNode(name string) *Node {
   385  	for _, node := range t.Nodes {
   386  		if node.Name == name {
   387  			return node
   388  		}
   389  	}
   390  	return nil
   391  }
   392  
   393  // ArchiveNodes returns a list of archive nodes that start at the initial height
   394  // and contain the entire blockchain history. They are used e.g. as light client
   395  // RPC servers.
   396  func (t Testnet) ArchiveNodes() []*Node {
   397  	nodes := []*Node{}
   398  	for _, node := range t.Nodes {
   399  		if !node.Stateless() && node.StartAt == 0 && node.RetainBlocks == 0 {
   400  			nodes = append(nodes, node)
   401  		}
   402  	}
   403  	return nodes
   404  }
   405  
   406  // RandomNode returns a random non-seed node.
   407  func (t Testnet) RandomNode() *Node {
   408  	for {
   409  		//nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand)
   410  		node := t.Nodes[rand.Intn(len(t.Nodes))]
   411  		if node.Mode != ModeSeed {
   412  			return node
   413  		}
   414  	}
   415  }
   416  
   417  // IPv6 returns true if the testnet is an IPv6 network.
   418  func (t Testnet) IPv6() bool {
   419  	return t.IP.IP.To4() == nil
   420  }
   421  
   422  // HasPerturbations returns whether the network has any perturbations.
   423  func (t Testnet) HasPerturbations() bool {
   424  	for _, node := range t.Nodes {
   425  		if len(node.Perturbations) > 0 {
   426  			return true
   427  		}
   428  	}
   429  	return false
   430  }
   431  
   432  // LastMisbehaviorHeight returns the height of the last misbehavior.
   433  func (t Testnet) LastMisbehaviorHeight() int64 {
   434  	lastHeight := int64(0)
   435  	for _, node := range t.Nodes {
   436  		for height := range node.Misbehaviors {
   437  			if height > lastHeight {
   438  				lastHeight = height
   439  			}
   440  		}
   441  	}
   442  	return lastHeight
   443  }
   444  
   445  // Address returns a P2P endpoint address for the node.
   446  func (n Node) AddressP2P(withID bool) string {
   447  	ip := n.IP.String()
   448  	if n.IP.To4() == nil {
   449  		// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
   450  		ip = fmt.Sprintf("[%v]", ip)
   451  	}
   452  	addr := fmt.Sprintf("%v:26656", ip)
   453  	if withID {
   454  		addr = fmt.Sprintf("%x@%v", n.NodeKey.PubKey().Address().Bytes(), addr)
   455  	}
   456  	return addr
   457  }
   458  
   459  // Address returns an RPC endpoint address for the node.
   460  func (n Node) AddressRPC() string {
   461  	ip := n.IP.String()
   462  	if n.IP.To4() == nil {
   463  		// IPv6 addresses must be wrapped in [] to avoid conflict with : port separator
   464  		ip = fmt.Sprintf("[%v]", ip)
   465  	}
   466  	return fmt.Sprintf("%v:26657", ip)
   467  }
   468  
   469  // Client returns an RPC client for a node.
   470  func (n Node) Client() (*rpchttp.HTTP, error) {
   471  	return rpchttp.New(fmt.Sprintf("http://127.0.0.1:%v", n.ProxyPort), "/websocket")
   472  }
   473  
   474  // Stateless returns true if the node is either a seed node or a light node
   475  func (n Node) Stateless() bool {
   476  	return n.Mode == ModeLight || n.Mode == ModeSeed
   477  }
   478  
   479  // keyGenerator generates pseudorandom Ed25519 keys based on a seed.
   480  type keyGenerator struct {
   481  	random *rand.Rand
   482  }
   483  
   484  func newKeyGenerator(seed int64) *keyGenerator {
   485  	return &keyGenerator{
   486  		random: rand.New(rand.NewSource(seed)), //nolint:gosec
   487  	}
   488  }
   489  
   490  func (g *keyGenerator) Generate(keyType string) crypto.PrivKey {
   491  	seed := make([]byte, ed25519.SeedSize)
   492  
   493  	_, err := io.ReadFull(g.random, seed)
   494  	if err != nil {
   495  		panic(err) // this shouldn't happen
   496  	}
   497  	switch keyType {
   498  	case "secp256k1":
   499  		return secp256k1.GenPrivKeySecp256k1(seed)
   500  	case "", "ed25519":
   501  		return ed25519.GenPrivKeyFromSecret(seed)
   502  	default:
   503  		panic("KeyType not supported") // should not make it this far
   504  	}
   505  }
   506  
   507  // portGenerator generates local Docker proxy ports for each node.
   508  type portGenerator struct {
   509  	nextPort uint32
   510  }
   511  
   512  func newPortGenerator(firstPort uint32) *portGenerator {
   513  	return &portGenerator{nextPort: firstPort}
   514  }
   515  
   516  func (g *portGenerator) Next() uint32 {
   517  	port := g.nextPort
   518  	g.nextPort++
   519  	if g.nextPort == 0 {
   520  		panic("port overflow")
   521  	}
   522  	return port
   523  }
   524  
   525  // ipGenerator generates sequential IP addresses for each node, using a random
   526  // network address.
   527  type ipGenerator struct {
   528  	network *net.IPNet
   529  	nextIP  net.IP
   530  }
   531  
   532  func newIPGenerator(network *net.IPNet) *ipGenerator {
   533  	nextIP := make([]byte, len(network.IP))
   534  	copy(nextIP, network.IP)
   535  	gen := &ipGenerator{network: network, nextIP: nextIP}
   536  	// Skip network and gateway addresses
   537  	gen.Next()
   538  	gen.Next()
   539  	return gen
   540  }
   541  
   542  func (g *ipGenerator) Network() *net.IPNet {
   543  	n := &net.IPNet{
   544  		IP:   make([]byte, len(g.network.IP)),
   545  		Mask: make([]byte, len(g.network.Mask)),
   546  	}
   547  	copy(n.IP, g.network.IP)
   548  	copy(n.Mask, g.network.Mask)
   549  	return n
   550  }
   551  
   552  func (g *ipGenerator) Next() net.IP {
   553  	ip := make([]byte, len(g.nextIP))
   554  	copy(ip, g.nextIP)
   555  	for i := len(g.nextIP) - 1; i >= 0; i-- {
   556  		g.nextIP[i]++
   557  		if g.nextIP[i] != 0 {
   558  			break
   559  		}
   560  	}
   561  	return ip
   562  }