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