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 }