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