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.

353 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, int(e2e.EvidenceAgeHeight), int(e2e.EvidenceAgeHeight) + 5}
  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. return manifests, nil
  84. }
  85. type Options struct {
  86. P2P P2PMode
  87. }
  88. type P2PMode string
  89. const (
  90. NewP2PMode P2PMode = "new"
  91. LegacyP2PMode P2PMode = "legacy"
  92. HybridP2PMode P2PMode = "hybrid"
  93. // mixed means that all combination are generated
  94. MixedP2PMode P2PMode = "mixed"
  95. )
  96. // generateTestnet generates a single testnet with the given options.
  97. func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) {
  98. manifest := e2e.Manifest{
  99. IPv6: ipv6.Choose(r).(bool),
  100. InitialHeight: int64(opt["initialHeight"].(int)),
  101. InitialState: opt["initialState"].(map[string]string),
  102. Validators: &map[string]int64{},
  103. ValidatorUpdates: map[string]map[string]int64{},
  104. Nodes: map[string]*e2e.ManifestNode{},
  105. KeyType: keyType.Choose(r).(string),
  106. Evidence: evidence.Choose(r).(int),
  107. QueueType: opt["queueType"].(string),
  108. TxSize: int64(txSize.Choose(r).(int)),
  109. }
  110. var p2pNodeFactor int
  111. switch opt["p2p"].(P2PMode) {
  112. case NewP2PMode:
  113. manifest.UseLegacyP2P = true
  114. case LegacyP2PMode:
  115. manifest.UseLegacyP2P = false
  116. case HybridP2PMode:
  117. manifest.UseLegacyP2P = true
  118. p2pNodeFactor = 2
  119. default:
  120. return manifest, fmt.Errorf("unknown p2p mode %s", opt["p2p"])
  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. if p2pNodeFactor == 0 {
  141. node.UseLegacyP2P = manifest.UseLegacyP2P
  142. } else if p2pNodeFactor%i == 0 {
  143. node.UseLegacyP2P = !manifest.UseLegacyP2P
  144. }
  145. manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node
  146. }
  147. // Next, we generate validators. We make sure a BFT quorum of validators start
  148. // at the initial height, and that we have two archive nodes. We also set up
  149. // the initial validator set, and validator set updates for delayed nodes.
  150. nextStartAt := manifest.InitialHeight + 5
  151. quorum := numValidators*2/3 + 1
  152. for i := 1; i <= numValidators; i++ {
  153. startAt := int64(0)
  154. if i > quorum {
  155. startAt = nextStartAt
  156. nextStartAt += 5
  157. }
  158. name := fmt.Sprintf("validator%02d", i)
  159. node := generateNode(
  160. r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2)
  161. if p2pNodeFactor == 0 {
  162. node.UseLegacyP2P = manifest.UseLegacyP2P
  163. } else if p2pNodeFactor%i == 0 {
  164. node.UseLegacyP2P = !manifest.UseLegacyP2P
  165. }
  166. manifest.Nodes[name] = node
  167. if startAt == 0 {
  168. (*manifest.Validators)[name] = int64(30 + r.Intn(71))
  169. } else {
  170. manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{
  171. name: int64(30 + r.Intn(71)),
  172. }
  173. }
  174. }
  175. // Move validators to InitChain if specified.
  176. switch opt["validators"].(string) {
  177. case "genesis":
  178. case "initchain":
  179. manifest.ValidatorUpdates["0"] = *manifest.Validators
  180. manifest.Validators = &map[string]int64{}
  181. default:
  182. return manifest, fmt.Errorf("invalid validators option %q", opt["validators"])
  183. }
  184. // Finally, we generate random full nodes.
  185. for i := 1; i <= numFulls; i++ {
  186. startAt := int64(0)
  187. if r.Float64() >= 0.5 {
  188. startAt = nextStartAt
  189. nextStartAt += 5
  190. }
  191. node := generateNode(r, e2e.ModeFull, startAt, manifest.InitialHeight, false)
  192. if p2pNodeFactor == 0 {
  193. node.UseLegacyP2P = manifest.UseLegacyP2P
  194. } else if p2pNodeFactor%i == 0 {
  195. node.UseLegacyP2P = !manifest.UseLegacyP2P
  196. }
  197. manifest.Nodes[fmt.Sprintf("full%02d", i)] = node
  198. }
  199. // We now set up peer discovery for nodes. Seed nodes are fully meshed with
  200. // each other, while non-seed nodes either use a set of random seeds or a
  201. // set of random peers that start before themselves.
  202. var seedNames, peerNames, lightProviders []string
  203. for name, node := range manifest.Nodes {
  204. if node.Mode == string(e2e.ModeSeed) {
  205. seedNames = append(seedNames, name)
  206. } else {
  207. // if the full node or validator is an ideal candidate, it is added as a light provider.
  208. // There are at least two archive nodes so there should be at least two ideal candidates
  209. if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 {
  210. lightProviders = append(lightProviders, name)
  211. }
  212. peerNames = append(peerNames, name)
  213. }
  214. }
  215. for _, name := range seedNames {
  216. for _, otherName := range seedNames {
  217. if name != otherName {
  218. manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName)
  219. }
  220. }
  221. }
  222. sort.Slice(peerNames, func(i, j int) bool {
  223. iName, jName := peerNames[i], peerNames[j]
  224. switch {
  225. case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt:
  226. return true
  227. case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt:
  228. return false
  229. default:
  230. return strings.Compare(iName, jName) == -1
  231. }
  232. })
  233. for i, name := range peerNames {
  234. if len(seedNames) > 0 && (i == 0 || r.Float64() >= 0.5) {
  235. manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r)
  236. } else if i > 0 {
  237. manifest.Nodes[name].PersistentPeers = uniformSetChoice(peerNames[:i]).Choose(r)
  238. }
  239. }
  240. // lastly, set up the light clients
  241. for i := 1; i <= numLightClients; i++ {
  242. startAt := manifest.InitialHeight + 5
  243. manifest.Nodes[fmt.Sprintf("light%02d", i)] = generateLightNode(
  244. r, startAt+(5*int64(i)), lightProviders,
  245. )
  246. }
  247. return manifest, nil
  248. }
  249. // generateNode randomly generates a node, with some constraints to avoid
  250. // generating invalid configurations. We do not set Seeds or PersistentPeers
  251. // here, since we need to know the overall network topology and startup
  252. // sequencing.
  253. func generateNode(
  254. r *rand.Rand, mode e2e.Mode, startAt int64, initialHeight int64, forceArchive bool,
  255. ) *e2e.ManifestNode {
  256. node := e2e.ManifestNode{
  257. Mode: string(mode),
  258. StartAt: startAt,
  259. Database: nodeDatabases.Choose(r),
  260. ABCIProtocol: nodeABCIProtocols.Choose(r),
  261. PrivvalProtocol: nodePrivvalProtocols.Choose(r),
  262. BlockSync: nodeBlockSyncs.Choose(r).(string),
  263. Mempool: nodeMempools.Choose(r).(string),
  264. StateSync: e2e.StateSyncDisabled,
  265. PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))),
  266. SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)),
  267. RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)),
  268. Perturb: nodePerturbations.Choose(r),
  269. }
  270. if startAt > 0 {
  271. node.StateSync = nodeStateSyncs.Choose(r).(string)
  272. }
  273. // If this node is forced to be an archive node, retain all blocks and
  274. // enable state sync snapshotting.
  275. if forceArchive {
  276. node.RetainBlocks = 0
  277. node.SnapshotInterval = 3
  278. }
  279. // If a node which does not persist state also does not retain blocks, randomly
  280. // choose to either persist state or retain all blocks.
  281. if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 {
  282. if r.Float64() > 0.5 {
  283. node.RetainBlocks = 0
  284. } else {
  285. node.PersistInterval = ptrUint64(node.RetainBlocks)
  286. }
  287. }
  288. // If either PersistInterval or SnapshotInterval are greater than RetainBlocks,
  289. // expand the block retention time.
  290. if node.RetainBlocks > 0 {
  291. if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval {
  292. node.RetainBlocks = *node.PersistInterval
  293. }
  294. if node.RetainBlocks < node.SnapshotInterval {
  295. node.RetainBlocks = node.SnapshotInterval
  296. }
  297. }
  298. if node.StateSync != e2e.StateSyncDisabled {
  299. node.BlockSync = "v0"
  300. }
  301. return &node
  302. }
  303. func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode {
  304. return &e2e.ManifestNode{
  305. Mode: string(e2e.ModeLight),
  306. StartAt: startAt,
  307. Database: nodeDatabases.Choose(r),
  308. ABCIProtocol: "builtin",
  309. PersistInterval: ptrUint64(0),
  310. PersistentPeers: providers,
  311. }
  312. }
  313. func ptrUint64(i uint64) *uint64 {
  314. return &i
  315. }