You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

358 lines
10 KiB

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