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 }