github.com/consideritdone/landslidecore@v0.0.0-20230718131026-a8b21c5cf8a7/test/e2e/pkg/testnet.go (about) 1 // nolint: gosec 2 package e2e 3 4 import ( 5 "errors" 6 "fmt" 7 "io" 8 "math/rand" 9 "net" 10 "path/filepath" 11 "sort" 12 "strconv" 13 "strings" 14 15 "github.com/consideritdone/landslidecore/crypto" 16 "github.com/consideritdone/landslidecore/crypto/ed25519" 17 "github.com/consideritdone/landslidecore/crypto/secp256k1" 18 rpchttp "github.com/consideritdone/landslidecore/rpc/client/http" 19 mcs "github.com/consideritdone/landslidecore/test/maverick/consensus" 20 ) 21 22 const ( 23 randomSeed int64 = 2308084734268 24 proxyPortFirst uint32 = 5701 25 networkIPv4 = "10.186.73.0/24" 26 networkIPv6 = "fd80:b10c::/48" 27 ) 28 29 type Mode string 30 type Protocol string 31 type Perturbation string 32 33 const ( 34 ModeValidator Mode = "validator" 35 ModeFull Mode = "full" 36 ModeLight Mode = "light" 37 ModeSeed Mode = "seed" 38 39 ProtocolBuiltin Protocol = "builtin" 40 ProtocolFile Protocol = "file" 41 ProtocolGRPC Protocol = "grpc" 42 ProtocolTCP Protocol = "tcp" 43 ProtocolUNIX Protocol = "unix" 44 45 PerturbationDisconnect Perturbation = "disconnect" 46 PerturbationKill Perturbation = "kill" 47 PerturbationPause Perturbation = "pause" 48 PerturbationRestart Perturbation = "restart" 49 ) 50 51 // Testnet represents a single testnet. 52 type Testnet struct { 53 Name string 54 File string 55 Dir string 56 IP *net.IPNet 57 InitialHeight int64 58 InitialState map[string]string 59 Validators map[*Node]int64 60 ValidatorUpdates map[int64]map[*Node]int64 61 Nodes []*Node 62 KeyType string 63 ABCIProtocol string 64 } 65 66 // Node represents a Tendermint node in a testnet. 67 type Node struct { 68 Name string 69 Testnet *Testnet 70 Mode Mode 71 PrivvalKey crypto.PrivKey 72 NodeKey crypto.PrivKey 73 IP net.IP 74 ProxyPort uint32 75 StartAt int64 76 FastSync string 77 StateSync bool 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(file string) (*Testnet, error) { 96 manifest, err := LoadManifest(file) 97 if err != nil { 98 return nil, err 99 } 100 dir := strings.TrimSuffix(file, filepath.Ext(file)) 101 102 // Set up resource generators. These must be deterministic. 103 netAddress := networkIPv4 104 if manifest.IPv6 { 105 netAddress = networkIPv6 106 } 107 _, ipNet, err := net.ParseCIDR(netAddress) 108 if err != nil { 109 return nil, fmt.Errorf("invalid IP network address %q: %w", netAddress, err) 110 } 111 112 ipGen := newIPGenerator(ipNet) 113 keyGen := newKeyGenerator(randomSeed) 114 proxyPortGen := newPortGenerator(proxyPortFirst) 115 116 testnet := &Testnet{ 117 Name: filepath.Base(dir), 118 File: file, 119 Dir: dir, 120 IP: ipGen.Network(), 121 InitialHeight: 1, 122 InitialState: manifest.InitialState, 123 Validators: map[*Node]int64{}, 124 ValidatorUpdates: map[int64]map[*Node]int64{}, 125 Nodes: []*Node{}, 126 ABCIProtocol: manifest.ABCIProtocol, 127 } 128 if len(manifest.KeyType) != 0 { 129 testnet.KeyType = manifest.KeyType 130 } 131 if manifest.InitialHeight > 0 { 132 testnet.InitialHeight = manifest.InitialHeight 133 } 134 if testnet.ABCIProtocol == "" { 135 testnet.ABCIProtocol = string(ProtocolBuiltin) 136 } 137 138 // Set up nodes, in alphabetical order (IPs and ports get same order). 139 nodeNames := []string{} 140 for name := range manifest.Nodes { 141 nodeNames = append(nodeNames, name) 142 } 143 sort.Strings(nodeNames) 144 145 for _, name := range nodeNames { 146 nodeManifest := manifest.Nodes[name] 147 node := &Node{ 148 Name: name, 149 Testnet: testnet, 150 PrivvalKey: keyGen.Generate(manifest.KeyType), 151 NodeKey: keyGen.Generate("ed25519"), 152 IP: ipGen.Next(), 153 ProxyPort: proxyPortGen.Next(), 154 Mode: ModeValidator, 155 Database: "goleveldb", 156 ABCIProtocol: Protocol(testnet.ABCIProtocol), 157 PrivvalProtocol: ProtocolFile, 158 StartAt: nodeManifest.StartAt, 159 FastSync: nodeManifest.FastSync, 160 StateSync: nodeManifest.StateSync, 161 PersistInterval: 1, 162 SnapshotInterval: nodeManifest.SnapshotInterval, 163 RetainBlocks: nodeManifest.RetainBlocks, 164 Perturbations: []Perturbation{}, 165 Misbehaviors: make(map[int64]string), 166 } 167 if node.StartAt == testnet.InitialHeight { 168 node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this 169 } 170 if nodeManifest.Mode != "" { 171 node.Mode = Mode(nodeManifest.Mode) 172 } 173 if node.Mode == ModeLight { 174 node.ABCIProtocol = ProtocolBuiltin 175 } 176 if nodeManifest.Database != "" { 177 node.Database = nodeManifest.Database 178 } 179 if nodeManifest.PrivvalProtocol != "" { 180 node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol) 181 } 182 if nodeManifest.PersistInterval != nil { 183 node.PersistInterval = *nodeManifest.PersistInterval 184 } 185 for _, p := range nodeManifest.Perturb { 186 node.Perturbations = append(node.Perturbations, Perturbation(p)) 187 } 188 for heightString, misbehavior := range nodeManifest.Misbehaviors { 189 height, err := strconv.ParseInt(heightString, 10, 64) 190 if err != nil { 191 return nil, fmt.Errorf("unable to parse height %s to int64: %w", heightString, err) 192 } 193 node.Misbehaviors[height] = misbehavior 194 } 195 testnet.Nodes = append(testnet.Nodes, node) 196 } 197 198 // We do a second pass to set up seeds and persistent peers, which allows graph cycles. 199 for _, node := range testnet.Nodes { 200 nodeManifest, ok := manifest.Nodes[node.Name] 201 if !ok { 202 return nil, fmt.Errorf("failed to look up manifest for node %q", node.Name) 203 } 204 for _, seedName := range nodeManifest.Seeds { 205 seed := testnet.LookupNode(seedName) 206 if seed == nil { 207 return nil, fmt.Errorf("unknown seed %q for node %q", seedName, node.Name) 208 } 209 node.Seeds = append(node.Seeds, seed) 210 } 211 for _, peerName := range nodeManifest.PersistentPeers { 212 peer := testnet.LookupNode(peerName) 213 if peer == nil { 214 return nil, fmt.Errorf("unknown persistent peer %q for node %q", peerName, node.Name) 215 } 216 node.PersistentPeers = append(node.PersistentPeers, peer) 217 } 218 219 // If there are no seeds or persistent peers specified, default to persistent 220 // connections to all other nodes. 221 if len(node.PersistentPeers) == 0 && len(node.Seeds) == 0 { 222 for _, peer := range testnet.Nodes { 223 if peer.Name == node.Name { 224 continue 225 } 226 node.PersistentPeers = append(node.PersistentPeers, peer) 227 } 228 } 229 } 230 231 // Set up genesis validators. If not specified explicitly, use all validator nodes. 232 if manifest.Validators != nil { 233 for validatorName, power := range *manifest.Validators { 234 validator := testnet.LookupNode(validatorName) 235 if validator == nil { 236 return nil, fmt.Errorf("unknown validator %q", validatorName) 237 } 238 testnet.Validators[validator] = power 239 } 240 } else { 241 for _, node := range testnet.Nodes { 242 if node.Mode == ModeValidator { 243 testnet.Validators[node] = 100 244 } 245 } 246 } 247 248 // Set up validator updates. 249 for heightStr, validators := range manifest.ValidatorUpdates { 250 height, err := strconv.Atoi(heightStr) 251 if err != nil { 252 return nil, fmt.Errorf("invalid validator update height %q: %w", height, err) 253 } 254 valUpdate := map[*Node]int64{} 255 for name, power := range validators { 256 node := testnet.LookupNode(name) 257 if node == nil { 258 return nil, fmt.Errorf("unknown validator %q for update at height %v", name, height) 259 } 260 valUpdate[node] = power 261 } 262 testnet.ValidatorUpdates[int64(height)] = valUpdate 263 } 264 265 return testnet, testnet.Validate() 266 } 267 268 // Validate validates a testnet. 269 func (t Testnet) Validate() error { 270 if t.Name == "" { 271 return errors.New("network has no name") 272 } 273 if t.IP == nil { 274 return errors.New("network has no IP") 275 } 276 if len(t.Nodes) == 0 { 277 return errors.New("network has no nodes") 278 } 279 for _, node := range t.Nodes { 280 if err := node.Validate(t); err != nil { 281 return fmt.Errorf("invalid node %q: %w", node.Name, err) 282 } 283 } 284 return nil 285 } 286 287 // Validate validates a node. 288 func (n Node) Validate(testnet Testnet) error { 289 if n.Name == "" { 290 return errors.New("node has no name") 291 } 292 if n.IP == nil { 293 return errors.New("node has no IP address") 294 } 295 if !testnet.IP.Contains(n.IP) { 296 return fmt.Errorf("node IP %v is not in testnet network %v", n.IP, testnet.IP) 297 } 298 if n.ProxyPort > 0 { 299 if n.ProxyPort <= 1024 { 300 return fmt.Errorf("local port %v must be >1024", n.ProxyPort) 301 } 302 for _, peer := range testnet.Nodes { 303 if peer.Name != n.Name && peer.ProxyPort == n.ProxyPort { 304 return fmt.Errorf("peer %q also has local port %v", peer.Name, n.ProxyPort) 305 } 306 } 307 } 308 switch n.FastSync { 309 case "", "v0", "v1", "v2": 310 default: 311 return fmt.Errorf("invalid fast sync setting %q", n.FastSync) 312 } 313 switch n.Database { 314 case "goleveldb", "cleveldb", "boltdb", "rocksdb", "badgerdb": 315 default: 316 return fmt.Errorf("invalid database setting %q", n.Database) 317 } 318 switch n.ABCIProtocol { 319 case ProtocolBuiltin, ProtocolUNIX, ProtocolTCP, ProtocolGRPC: 320 default: 321 return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol) 322 } 323 if n.Mode == ModeLight && n.ABCIProtocol != ProtocolBuiltin { 324 return errors.New("light client must use builtin protocol") 325 } 326 switch n.PrivvalProtocol { 327 case ProtocolFile, ProtocolUNIX, ProtocolTCP: 328 default: 329 return fmt.Errorf("invalid privval protocol setting %q", n.PrivvalProtocol) 330 } 331 332 if n.StartAt > 0 && n.StartAt < n.Testnet.InitialHeight { 333 return fmt.Errorf("cannot start at height %v lower than initial height %v", 334 n.StartAt, n.Testnet.InitialHeight) 335 } 336 if n.StateSync && n.StartAt == 0 { 337 return errors.New("state synced nodes cannot start at the initial height") 338 } 339 if n.PersistInterval == 0 && n.RetainBlocks > 0 { 340 return errors.New("persist_interval=0 requires retain_blocks=0") 341 } 342 if n.PersistInterval > 1 && n.RetainBlocks > 0 && n.RetainBlocks < n.PersistInterval { 343 return errors.New("persist_interval must be less than or equal to retain_blocks") 344 } 345 if n.SnapshotInterval > 0 && n.RetainBlocks > 0 && n.RetainBlocks < n.SnapshotInterval { 346 return errors.New("snapshot_interval must be less than er equal to retain_blocks") 347 } 348 349 for _, perturbation := range n.Perturbations { 350 switch perturbation { 351 case PerturbationDisconnect, PerturbationKill, PerturbationPause, PerturbationRestart: 352 default: 353 return fmt.Errorf("invalid perturbation %q", perturbation) 354 } 355 } 356 357 if (n.PrivvalProtocol != "file" || n.Mode != "validator") && len(n.Misbehaviors) != 0 { 358 return errors.New("must be using \"file\" privval protocol to implement misbehaviors") 359 } 360 361 for height, misbehavior := range n.Misbehaviors { 362 if height < n.StartAt { 363 return fmt.Errorf("misbehavior height %d is below node start height %d", 364 height, n.StartAt) 365 } 366 if height < testnet.InitialHeight { 367 return fmt.Errorf("misbehavior height %d is below network initial height %d", 368 height, testnet.InitialHeight) 369 } 370 exists := false 371 for possibleBehaviors := range mcs.MisbehaviorList { 372 if possibleBehaviors == misbehavior { 373 exists = true 374 } 375 } 376 if !exists { 377 return fmt.Errorf("misbehavior %s does not exist", misbehavior) 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))] 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)), 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 }