bitbucket.org/number571/tendermint@v0.8.14/test/e2e/generator/generate.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sort" 7 "strings" 8 9 e2e "bitbucket.org/number571/tendermint/test/e2e/pkg" 10 "bitbucket.org/number571/tendermint/types" 11 ) 12 13 var ( 14 // testnetCombinations defines global testnet options, where we generate a 15 // separate testnet for each combination (Cartesian product) of options. 16 testnetCombinations = map[string][]interface{}{ 17 "topology": {"single", "quad", "large"}, 18 "p2p": {NewP2PMode, LegacyP2PMode, HybridP2PMode}, 19 "queueType": {"priority"}, // "fifo", "wdrr" 20 "initialHeight": {0, 1000}, 21 "initialState": { 22 map[string]string{}, 23 map[string]string{"initial01": "a", "initial02": "b", "initial03": "c"}, 24 }, 25 "validators": {"genesis", "initchain"}, 26 } 27 28 // The following specify randomly chosen values for testnet nodes. 29 nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} 30 nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin", "grpc"} 31 nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp", "grpc"} 32 // FIXME: v2 disabled due to flake 33 nodeFastSyncs = uniformChoice{"v0"} // "v2" 34 nodeMempools = uniformChoice{"v0", "v1"} 35 nodeStateSyncs = uniformChoice{false, true} 36 nodePersistIntervals = uniformChoice{0, 1, 5} 37 nodeSnapshotIntervals = uniformChoice{0, 3} 38 nodeRetainBlocks = uniformChoice{0, int(e2e.EvidenceAgeHeight), int(e2e.EvidenceAgeHeight) + 5} 39 nodePerturbations = probSetChoice{ 40 "disconnect": 0.1, 41 "pause": 0.1, 42 "kill": 0.1, 43 "restart": 0.1, 44 } 45 evidence = uniformChoice{0, 1, 10} 46 txSize = uniformChoice{1024, 10240} // either 1kb or 10kb 47 ipv6 = uniformChoice{false, true} 48 keyType = uniformChoice{types.ABCIPubKeyTypeGost512, types.ABCIPubKeyTypeGost256} 49 ) 50 51 // Generate generates random testnets using the given RNG. 52 func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) { 53 manifests := []e2e.Manifest{} 54 switch opts.P2P { 55 case NewP2PMode, LegacyP2PMode, HybridP2PMode: 56 testnetCombinations["p2p"] = []interface{}{opts.P2P} 57 default: 58 testnetCombinations["p2p"] = []interface{}{NewP2PMode, LegacyP2PMode, HybridP2PMode} 59 } 60 61 for _, opt := range combinations(testnetCombinations) { 62 manifest, err := generateTestnet(r, opt) 63 if err != nil { 64 return nil, err 65 } 66 67 if len(manifest.Nodes) == 1 { 68 if opt["p2p"] == HybridP2PMode { 69 continue 70 } 71 } 72 manifests = append(manifests, manifest) 73 } 74 return manifests, nil 75 } 76 77 type Options struct { 78 P2P P2PMode 79 } 80 81 type P2PMode string 82 83 const ( 84 NewP2PMode P2PMode = "new" 85 LegacyP2PMode P2PMode = "legacy" 86 HybridP2PMode P2PMode = "hybrid" 87 // mixed means that all combination are generated 88 MixedP2PMode P2PMode = "mixed" 89 ) 90 91 // generateTestnet generates a single testnet with the given options. 92 func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) { 93 manifest := e2e.Manifest{ 94 IPv6: ipv6.Choose(r).(bool), 95 InitialHeight: int64(opt["initialHeight"].(int)), 96 InitialState: opt["initialState"].(map[string]string), 97 Validators: &map[string]int64{}, 98 ValidatorUpdates: map[string]map[string]int64{}, 99 Nodes: map[string]*e2e.ManifestNode{}, 100 KeyType: keyType.Choose(r).(string), 101 Evidence: evidence.Choose(r).(int), 102 QueueType: opt["queueType"].(string), 103 TxSize: int64(txSize.Choose(r).(int)), 104 } 105 106 var p2pNodeFactor int 107 108 switch opt["p2p"].(P2PMode) { 109 case NewP2PMode: 110 manifest.DisableLegacyP2P = true 111 case LegacyP2PMode: 112 manifest.DisableLegacyP2P = false 113 case HybridP2PMode: 114 manifest.DisableLegacyP2P = false 115 p2pNodeFactor = 2 116 default: 117 return manifest, fmt.Errorf("unknown p2p mode %s", opt["p2p"]) 118 } 119 120 var numSeeds, numValidators, numFulls, numLightClients int 121 switch opt["topology"].(string) { 122 case "single": 123 numValidators = 1 124 case "quad": 125 numValidators = 4 126 case "large": 127 // FIXME Networks are kept small since large ones use too much CPU. 128 numSeeds = r.Intn(2) 129 numLightClients = r.Intn(3) 130 numValidators = 4 + r.Intn(4) 131 numFulls = r.Intn(4) 132 default: 133 return manifest, fmt.Errorf("unknown topology %q", opt["topology"]) 134 } 135 136 // First we generate seed nodes, starting at the initial height. 137 for i := 1; i <= numSeeds; i++ { 138 node := generateNode(r, e2e.ModeSeed, 0, manifest.InitialHeight, false) 139 140 if p2pNodeFactor == 0 { 141 node.DisableLegacyP2P = manifest.DisableLegacyP2P 142 } else if p2pNodeFactor%i == 0 { 143 node.DisableLegacyP2P = !manifest.DisableLegacyP2P 144 } 145 146 manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node 147 } 148 149 // Next, we generate validators. We make sure a BFT quorum of validators start 150 // at the initial height, and that we have two archive nodes. We also set up 151 // the initial validator set, and validator set updates for delayed nodes. 152 nextStartAt := manifest.InitialHeight + 5 153 quorum := numValidators*2/3 + 1 154 for i := 1; i <= numValidators; i++ { 155 startAt := int64(0) 156 if i > quorum { 157 startAt = nextStartAt 158 nextStartAt += 5 159 } 160 name := fmt.Sprintf("validator%02d", i) 161 node := generateNode( 162 r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2) 163 164 if p2pNodeFactor == 0 { 165 node.DisableLegacyP2P = manifest.DisableLegacyP2P 166 } else if p2pNodeFactor%i == 0 { 167 node.DisableLegacyP2P = !manifest.DisableLegacyP2P 168 } 169 170 manifest.Nodes[name] = node 171 172 if startAt == 0 { 173 (*manifest.Validators)[name] = int64(30 + r.Intn(71)) 174 } else { 175 manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{ 176 name: int64(30 + r.Intn(71)), 177 } 178 } 179 } 180 181 // Move validators to InitChain if specified. 182 switch opt["validators"].(string) { 183 case "genesis": 184 case "initchain": 185 manifest.ValidatorUpdates["0"] = *manifest.Validators 186 manifest.Validators = &map[string]int64{} 187 default: 188 return manifest, fmt.Errorf("invalid validators option %q", opt["validators"]) 189 } 190 191 // Finally, we generate random full nodes. 192 for i := 1; i <= numFulls; i++ { 193 startAt := int64(0) 194 if r.Float64() >= 0.5 { 195 startAt = nextStartAt 196 nextStartAt += 5 197 } 198 node := generateNode(r, e2e.ModeFull, startAt, manifest.InitialHeight, false) 199 200 if p2pNodeFactor == 0 { 201 node.DisableLegacyP2P = manifest.DisableLegacyP2P 202 } else if p2pNodeFactor%i == 0 { 203 node.DisableLegacyP2P = !manifest.DisableLegacyP2P 204 } 205 manifest.Nodes[fmt.Sprintf("full%02d", i)] = node 206 } 207 208 // We now set up peer discovery for nodes. Seed nodes are fully meshed with 209 // each other, while non-seed nodes either use a set of random seeds or a 210 // set of random peers that start before themselves. 211 var seedNames, peerNames, lightProviders []string 212 for name, node := range manifest.Nodes { 213 if node.Mode == string(e2e.ModeSeed) { 214 seedNames = append(seedNames, name) 215 } else { 216 // if the full node or validator is an ideal candidate, it is added as a light provider. 217 // There are at least two archive nodes so there should be at least two ideal candidates 218 if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 { 219 lightProviders = append(lightProviders, name) 220 } 221 peerNames = append(peerNames, name) 222 } 223 } 224 225 for _, name := range seedNames { 226 for _, otherName := range seedNames { 227 if name != otherName { 228 manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName) 229 } 230 } 231 } 232 233 sort.Slice(peerNames, func(i, j int) bool { 234 iName, jName := peerNames[i], peerNames[j] 235 switch { 236 case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt: 237 return true 238 case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt: 239 return false 240 default: 241 return strings.Compare(iName, jName) == -1 242 } 243 }) 244 for i, name := range peerNames { 245 if len(seedNames) > 0 && (i == 0 || r.Float64() >= 0.5) { 246 manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r) 247 } else if i > 0 { 248 manifest.Nodes[name].PersistentPeers = uniformSetChoice(peerNames[:i]).Choose(r) 249 } 250 } 251 252 // lastly, set up the light clients 253 for i := 1; i <= numLightClients; i++ { 254 startAt := manifest.InitialHeight + 5 255 manifest.Nodes[fmt.Sprintf("light%02d", i)] = generateLightNode( 256 r, startAt+(5*int64(i)), lightProviders, 257 ) 258 } 259 260 return manifest, nil 261 } 262 263 // generateNode randomly generates a node, with some constraints to avoid 264 // generating invalid configurations. We do not set Seeds or PersistentPeers 265 // here, since we need to know the overall network topology and startup 266 // sequencing. 267 func generateNode( 268 r *rand.Rand, mode e2e.Mode, startAt int64, initialHeight int64, forceArchive bool, 269 ) *e2e.ManifestNode { 270 node := e2e.ManifestNode{ 271 Mode: string(mode), 272 StartAt: startAt, 273 Database: nodeDatabases.Choose(r).(string), 274 ABCIProtocol: nodeABCIProtocols.Choose(r).(string), 275 PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string), 276 FastSync: nodeFastSyncs.Choose(r).(string), 277 Mempool: nodeMempools.Choose(r).(string), 278 StateSync: nodeStateSyncs.Choose(r).(bool) && startAt > 0, 279 PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))), 280 SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)), 281 RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)), 282 Perturb: nodePerturbations.Choose(r), 283 } 284 285 // If this node is forced to be an archive node, retain all blocks and 286 // enable state sync snapshotting. 287 if forceArchive { 288 node.RetainBlocks = 0 289 node.SnapshotInterval = 3 290 } 291 292 // If a node which does not persist state also does not retain blocks, randomly 293 // choose to either persist state or retain all blocks. 294 if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 { 295 if r.Float64() > 0.5 { 296 node.RetainBlocks = 0 297 } else { 298 node.PersistInterval = ptrUint64(node.RetainBlocks) 299 } 300 } 301 302 // If either PersistInterval or SnapshotInterval are greater than RetainBlocks, 303 // expand the block retention time. 304 if node.RetainBlocks > 0 { 305 if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval { 306 node.RetainBlocks = *node.PersistInterval 307 } 308 if node.RetainBlocks < node.SnapshotInterval { 309 node.RetainBlocks = node.SnapshotInterval 310 } 311 } 312 313 if node.StateSync { 314 node.FastSync = "v0" 315 } 316 317 return &node 318 } 319 320 func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode { 321 return &e2e.ManifestNode{ 322 Mode: string(e2e.ModeLight), 323 StartAt: startAt, 324 Database: nodeDatabases.Choose(r).(string), 325 ABCIProtocol: "builtin", 326 PersistInterval: ptrUint64(0), 327 PersistentPeers: providers, 328 } 329 } 330 331 func ptrUint64(i uint64) *uint64 { 332 return &i 333 }