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