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