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.

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