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.

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