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