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