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