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.

349 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. "queueType": {"priority"}, // "fifo"
  16. "initialHeight": {0, 1000},
  17. "initialState": {
  18. map[string]string{},
  19. map[string]string{"initial01": "a", "initial02": "b", "initial03": "c"},
  20. },
  21. "validators": {"genesis", "initchain"},
  22. }
  23. // The following specify randomly chosen values for testnet nodes.
  24. nodeDatabases = weightedChoice{
  25. "goleveldb": 35,
  26. "badgerdb": 35,
  27. "boltdb": 15,
  28. "rocksdb": 10,
  29. "cleveldb": 5,
  30. }
  31. nodeABCIProtocols = weightedChoice{
  32. "builtin": 50,
  33. "tcp": 20,
  34. "grpc": 20,
  35. "unix": 10,
  36. }
  37. nodePrivvalProtocols = weightedChoice{
  38. "file": 50,
  39. "grpc": 20,
  40. "tcp": 20,
  41. "unix": 10,
  42. }
  43. nodeMempools = weightedChoice{
  44. "v0": 20,
  45. "v1": 80,
  46. }
  47. nodeStateSyncs = weightedChoice{
  48. e2e.StateSyncDisabled: 10,
  49. e2e.StateSyncP2P: 45,
  50. e2e.StateSyncRPC: 45,
  51. }
  52. nodePersistIntervals = uniformChoice{0, 1, 5}
  53. nodeSnapshotIntervals = uniformChoice{0, 5}
  54. nodeRetainBlocks = uniformChoice{
  55. 0,
  56. 2 * int(e2e.EvidenceAgeHeight),
  57. 4 * int(e2e.EvidenceAgeHeight),
  58. }
  59. nodePerturbations = probSetChoice{
  60. "disconnect": 0.1,
  61. "pause": 0.1,
  62. "kill": 0.1,
  63. "restart": 0.1,
  64. }
  65. evidence = uniformChoice{0, 1, 10}
  66. txSize = uniformChoice{1024, 4096} // either 1kb or 4kb
  67. ipv6 = uniformChoice{false, true}
  68. keyType = uniformChoice{types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1}
  69. )
  70. // Generate generates random testnets using the given RNG.
  71. func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) {
  72. manifests := []e2e.Manifest{}
  73. for _, opt := range combinations(testnetCombinations) {
  74. manifest, err := generateTestnet(r, opt)
  75. if err != nil {
  76. return nil, err
  77. }
  78. if len(manifest.Nodes) < opts.MinNetworkSize {
  79. continue
  80. }
  81. if opts.MaxNetworkSize > 0 && len(manifest.Nodes) >= opts.MaxNetworkSize {
  82. continue
  83. }
  84. manifests = append(manifests, manifest)
  85. }
  86. return manifests, nil
  87. }
  88. type Options struct {
  89. MinNetworkSize int
  90. MaxNetworkSize int
  91. NumGroups int
  92. Directory string
  93. Reverse bool
  94. }
  95. // generateTestnet generates a single testnet with the given options.
  96. func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) {
  97. manifest := e2e.Manifest{
  98. IPv6: ipv6.Choose(r).(bool),
  99. ABCIProtocol: nodeABCIProtocols.Choose(r),
  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 numSeeds, numValidators, numFulls, numLightClients int
  111. switch opt["topology"].(string) {
  112. case "single":
  113. numValidators = 1
  114. case "quad":
  115. numValidators = 4
  116. case "large":
  117. // FIXME Networks are kept small since large ones use too much CPU.
  118. numSeeds = r.Intn(1)
  119. numLightClients = r.Intn(2)
  120. numValidators = 4 + r.Intn(4)
  121. numFulls = r.Intn(4)
  122. default:
  123. return manifest, fmt.Errorf("unknown topology %q", opt["topology"])
  124. }
  125. // First we generate seed nodes, starting at the initial height.
  126. for i := 1; i <= numSeeds; i++ {
  127. node := generateNode(r, manifest, e2e.ModeSeed, 0, false)
  128. manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node
  129. }
  130. var numSyncingNodes = 0
  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 && numSyncingNodes < 2 && r.Float64() >= 0.25 {
  139. numSyncingNodes++
  140. startAt = nextStartAt
  141. nextStartAt += 5
  142. }
  143. name := fmt.Sprintf("validator%02d", i)
  144. node := generateNode(r, manifest, e2e.ModeValidator, startAt, i <= 2)
  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 numSyncingNodes < 2 && r.Float64() >= 0.5 {
  167. numSyncingNodes++
  168. startAt = nextStartAt
  169. nextStartAt += 5
  170. }
  171. node := generateNode(r, manifest, e2e.ModeFull, startAt, false)
  172. manifest.Nodes[fmt.Sprintf("full%02d", i)] = node
  173. }
  174. // We now set up peer discovery for nodes. Seed nodes are fully meshed with
  175. // each other, while non-seed nodes either use a set of random seeds or a
  176. // set of random peers that start before themselves.
  177. var seedNames, peerNames, lightProviders []string
  178. for name, node := range manifest.Nodes {
  179. if node.Mode == string(e2e.ModeSeed) {
  180. seedNames = append(seedNames, name)
  181. } else {
  182. // if the full node or validator is an ideal candidate, it is added as a light provider.
  183. // There are at least two archive nodes so there should be at least two ideal candidates
  184. if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 {
  185. lightProviders = append(lightProviders, name)
  186. }
  187. peerNames = append(peerNames, name)
  188. }
  189. }
  190. for _, name := range seedNames {
  191. for _, otherName := range seedNames {
  192. if name != otherName {
  193. manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName)
  194. }
  195. }
  196. }
  197. sort.Slice(peerNames, func(i, j int) bool {
  198. iName, jName := peerNames[i], peerNames[j]
  199. switch {
  200. case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt:
  201. return true
  202. case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt:
  203. return false
  204. default:
  205. return strings.Compare(iName, jName) == -1
  206. }
  207. })
  208. for i, name := range peerNames {
  209. // there are seeds, statesync is disabled, and it's
  210. // either the first peer by the sort order, and
  211. // (randomly half of the remaining peers use a seed
  212. // node; otherwise, choose some remaining set of the
  213. // peers.
  214. if len(seedNames) > 0 &&
  215. manifest.Nodes[name].StateSync == e2e.StateSyncDisabled &&
  216. (i == 0 || r.Float64() >= 0.5) {
  217. // choose one of the seeds
  218. manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r)
  219. } else if i > 1 && r.Float64() >= 0.5 {
  220. peers := uniformSetChoice(peerNames[:i])
  221. manifest.Nodes[name].PersistentPeers = peers.ChooseAtLeast(r, 2)
  222. }
  223. }
  224. // lastly, set up the light clients
  225. for i := 1; i <= numLightClients; i++ {
  226. startAt := manifest.InitialHeight + 5
  227. node := generateLightNode(r, startAt+(5*int64(i)), lightProviders)
  228. manifest.Nodes[fmt.Sprintf("light%02d", i)] = node
  229. }
  230. return manifest, nil
  231. }
  232. // generateNode randomly generates a node, with some constraints to avoid
  233. // generating invalid configurations. We do not set Seeds or PersistentPeers
  234. // here, since we need to know the overall network topology and startup
  235. // sequencing.
  236. func generateNode(
  237. r *rand.Rand,
  238. manifest e2e.Manifest,
  239. mode e2e.Mode,
  240. startAt int64,
  241. forceArchive bool,
  242. ) *e2e.ManifestNode {
  243. node := e2e.ManifestNode{
  244. Mode: string(mode),
  245. StartAt: startAt,
  246. Database: nodeDatabases.Choose(r),
  247. PrivvalProtocol: nodePrivvalProtocols.Choose(r),
  248. Mempool: nodeMempools.Choose(r),
  249. StateSync: e2e.StateSyncDisabled,
  250. PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))),
  251. SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)),
  252. RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)),
  253. Perturb: nodePerturbations.Choose(r),
  254. }
  255. if node.Mempool == "" {
  256. node.Mempool = "v1"
  257. }
  258. if node.PrivvalProtocol == "" {
  259. node.PrivvalProtocol = "file"
  260. }
  261. if startAt > 0 {
  262. node.StateSync = nodeStateSyncs.Choose(r)
  263. if manifest.InitialHeight-startAt <= 5 && node.StateSync == e2e.StateSyncDisabled {
  264. // avoid needing to blocsync more than five total blocks.
  265. node.StateSync = uniformSetChoice([]string{
  266. e2e.StateSyncP2P,
  267. e2e.StateSyncRPC,
  268. }).Choose(r)[0]
  269. }
  270. }
  271. // If this node is forced to be an archive node, retain all blocks and
  272. // enable state sync snapshotting.
  273. if forceArchive {
  274. node.RetainBlocks = 0
  275. node.SnapshotInterval = 3
  276. }
  277. // If a node which does not persist state also does not retain blocks, randomly
  278. // choose to either persist state or retain all blocks.
  279. if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 {
  280. if r.Float64() > 0.5 {
  281. node.RetainBlocks = 0
  282. } else {
  283. node.PersistInterval = ptrUint64(node.RetainBlocks)
  284. }
  285. }
  286. // If either PersistInterval or SnapshotInterval are greater than RetainBlocks,
  287. // expand the block retention time.
  288. if node.RetainBlocks > 0 {
  289. if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval {
  290. node.RetainBlocks = *node.PersistInterval
  291. }
  292. if node.RetainBlocks < node.SnapshotInterval {
  293. node.RetainBlocks = node.SnapshotInterval
  294. }
  295. }
  296. return &node
  297. }
  298. func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode {
  299. return &e2e.ManifestNode{
  300. Mode: string(e2e.ModeLight),
  301. StartAt: startAt,
  302. Database: nodeDatabases.Choose(r),
  303. PersistInterval: ptrUint64(0),
  304. PersistentPeers: providers,
  305. }
  306. }
  307. func ptrUint64(i uint64) *uint64 {
  308. return &i
  309. }