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", "wdrr"
  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. // FIXME: v2 disabled due to flake
  44. nodeBlockSyncs = uniformChoice{"v0"} // "v2"
  45. nodeMempools = uniformChoice{"v0", "v1"}
  46. nodeStateSyncs = weightedChoice{
  47. e2e.StateSyncDisabled: 10,
  48. e2e.StateSyncP2P: 45,
  49. e2e.StateSyncRPC: 45,
  50. }
  51. nodePersistIntervals = uniformChoice{0, 1, 5}
  52. nodeSnapshotIntervals = uniformChoice{0, 3}
  53. nodeRetainBlocks = uniformChoice{0, 2 * int(e2e.EvidenceAgeHeight), 4 * int(e2e.EvidenceAgeHeight)}
  54. nodePerturbations = probSetChoice{
  55. "disconnect": 0.1,
  56. "pause": 0.1,
  57. "kill": 0.1,
  58. "restart": 0.1,
  59. }
  60. evidence = uniformChoice{0, 1, 10}
  61. txSize = uniformChoice{1024, 4096} // either 1kb or 4kb
  62. ipv6 = uniformChoice{false, true}
  63. keyType = uniformChoice{types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1}
  64. )
  65. // Generate generates random testnets using the given RNG.
  66. func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) {
  67. manifests := []e2e.Manifest{}
  68. for _, opt := range combinations(testnetCombinations) {
  69. manifest, err := generateTestnet(r, opt)
  70. if err != nil {
  71. return nil, err
  72. }
  73. if len(manifest.Nodes) < opts.MinNetworkSize {
  74. continue
  75. }
  76. if opts.MaxNetworkSize > 0 && len(manifest.Nodes) >= opts.MaxNetworkSize {
  77. continue
  78. }
  79. manifests = append(manifests, manifest)
  80. }
  81. return manifests, nil
  82. }
  83. type Options struct {
  84. MinNetworkSize int
  85. MaxNetworkSize int
  86. NumGroups int
  87. Directory string
  88. Reverse bool
  89. }
  90. // generateTestnet generates a single testnet with the given options.
  91. func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) {
  92. manifest := e2e.Manifest{
  93. IPv6: ipv6.Choose(r).(bool),
  94. InitialHeight: int64(opt["initialHeight"].(int)),
  95. InitialState: opt["initialState"].(map[string]string),
  96. Validators: &map[string]int64{},
  97. ValidatorUpdates: map[string]map[string]int64{},
  98. Nodes: map[string]*e2e.ManifestNode{},
  99. KeyType: keyType.Choose(r).(string),
  100. Evidence: evidence.Choose(r).(int),
  101. QueueType: opt["queueType"].(string),
  102. TxSize: int64(txSize.Choose(r).(int)),
  103. }
  104. var numSeeds, numValidators, numFulls, numLightClients int
  105. switch opt["topology"].(string) {
  106. case "single":
  107. numValidators = 1
  108. case "quad":
  109. numValidators = 4
  110. case "large":
  111. // FIXME Networks are kept small since large ones use too much CPU.
  112. numSeeds = r.Intn(1)
  113. numLightClients = r.Intn(2)
  114. numValidators = 4 + r.Intn(4)
  115. numFulls = r.Intn(4)
  116. default:
  117. return manifest, fmt.Errorf("unknown topology %q", opt["topology"])
  118. }
  119. // First we generate seed nodes, starting at the initial height.
  120. for i := 1; i <= numSeeds; i++ {
  121. node := generateNode(r, manifest, e2e.ModeSeed, 0, false)
  122. manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node
  123. }
  124. var numSyncingNodes = 0
  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 && numSyncingNodes < 2 && r.Float64() >= 0.25 {
  133. numSyncingNodes++
  134. startAt = nextStartAt
  135. nextStartAt += 5
  136. }
  137. name := fmt.Sprintf("validator%02d", i)
  138. node := generateNode(r, manifest, e2e.ModeValidator, startAt, i <= 2)
  139. manifest.Nodes[name] = node
  140. if startAt == 0 {
  141. (*manifest.Validators)[name] = int64(30 + r.Intn(71))
  142. } else {
  143. manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{
  144. name: int64(30 + r.Intn(71)),
  145. }
  146. }
  147. }
  148. // Move validators to InitChain if specified.
  149. switch opt["validators"].(string) {
  150. case "genesis":
  151. case "initchain":
  152. manifest.ValidatorUpdates["0"] = *manifest.Validators
  153. manifest.Validators = &map[string]int64{}
  154. default:
  155. return manifest, fmt.Errorf("invalid validators option %q", opt["validators"])
  156. }
  157. // Finally, we generate random full nodes.
  158. for i := 1; i <= numFulls; i++ {
  159. startAt := int64(0)
  160. if numSyncingNodes < 2 && r.Float64() >= 0.5 {
  161. numSyncingNodes++
  162. startAt = nextStartAt
  163. nextStartAt += 5
  164. }
  165. node := generateNode(r, manifest, e2e.ModeFull, startAt, false)
  166. manifest.Nodes[fmt.Sprintf("full%02d", i)] = node
  167. }
  168. // We now set up peer discovery for nodes. Seed nodes are fully meshed with
  169. // each other, while non-seed nodes either use a set of random seeds or a
  170. // set of random peers that start before themselves.
  171. var seedNames, peerNames, lightProviders []string
  172. for name, node := range manifest.Nodes {
  173. if node.Mode == string(e2e.ModeSeed) {
  174. seedNames = append(seedNames, name)
  175. } else {
  176. // if the full node or validator is an ideal candidate, it is added as a light provider.
  177. // There are at least two archive nodes so there should be at least two ideal candidates
  178. if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 {
  179. lightProviders = append(lightProviders, name)
  180. }
  181. peerNames = append(peerNames, name)
  182. }
  183. }
  184. for _, name := range seedNames {
  185. for _, otherName := range seedNames {
  186. if name != otherName {
  187. manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName)
  188. }
  189. }
  190. }
  191. sort.Slice(peerNames, func(i, j int) bool {
  192. iName, jName := peerNames[i], peerNames[j]
  193. switch {
  194. case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt:
  195. return true
  196. case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt:
  197. return false
  198. default:
  199. return strings.Compare(iName, jName) == -1
  200. }
  201. })
  202. for i, name := range peerNames {
  203. // there are seeds, statesync is disabled, and it's
  204. // either the first peer by the sort order, and
  205. // (randomly half of the remaining peers use a seed
  206. // node; otherwise, choose some remaining set of the
  207. // peers.
  208. if len(seedNames) > 0 &&
  209. manifest.Nodes[name].StateSync == e2e.StateSyncDisabled &&
  210. (i == 0 || r.Float64() >= 0.5) {
  211. // choose one of the seeds
  212. manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r)
  213. } else if i > 0 {
  214. peers := uniformSetChoice(peerNames[:i])
  215. if manifest.Nodes[name].StateSync == e2e.StateSyncP2P {
  216. manifest.Nodes[name].PersistentPeers = peers.ChooseAtLeast(r, 2)
  217. } else {
  218. manifest.Nodes[name].PersistentPeers = peers.Choose(r)
  219. }
  220. }
  221. }
  222. // lastly, set up the light clients
  223. for i := 1; i <= numLightClients; i++ {
  224. startAt := manifest.InitialHeight + 5
  225. node := generateLightNode(
  226. r, startAt+(5*int64(i)), lightProviders,
  227. )
  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. ABCIProtocol: nodeABCIProtocols.Choose(r),
  248. PrivvalProtocol: nodePrivvalProtocols.Choose(r),
  249. BlockSync: nodeBlockSyncs.Choose(r).(string),
  250. Mempool: nodeMempools.Choose(r).(string),
  251. StateSync: e2e.StateSyncDisabled,
  252. PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))),
  253. SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)),
  254. RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)),
  255. Perturb: nodePerturbations.Choose(r),
  256. }
  257. if startAt > 0 {
  258. node.StateSync = nodeStateSyncs.Choose(r)
  259. if manifest.InitialHeight-startAt <= 5 && node.StateSync == e2e.StateSyncDisabled {
  260. // avoid needing to blocsync more than five total blocks.
  261. node.StateSync = uniformSetChoice([]string{
  262. e2e.StateSyncP2P,
  263. e2e.StateSyncRPC,
  264. }).Choose(r)[0]
  265. }
  266. }
  267. // If this node is forced to be an archive node, retain all blocks and
  268. // enable state sync snapshotting.
  269. if forceArchive {
  270. node.RetainBlocks = 0
  271. node.SnapshotInterval = 3
  272. }
  273. // If a node which does not persist state also does not retain blocks, randomly
  274. // choose to either persist state or retain all blocks.
  275. if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 {
  276. if r.Float64() > 0.5 {
  277. node.RetainBlocks = 0
  278. } else {
  279. node.PersistInterval = ptrUint64(node.RetainBlocks)
  280. }
  281. }
  282. // If either PersistInterval or SnapshotInterval are greater than RetainBlocks,
  283. // expand the block retention time.
  284. if node.RetainBlocks > 0 {
  285. if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval {
  286. node.RetainBlocks = *node.PersistInterval
  287. }
  288. if node.RetainBlocks < node.SnapshotInterval {
  289. node.RetainBlocks = node.SnapshotInterval
  290. }
  291. }
  292. if node.StateSync != e2e.StateSyncDisabled {
  293. node.BlockSync = "v0"
  294. }
  295. return &node
  296. }
  297. func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode {
  298. return &e2e.ManifestNode{
  299. Mode: string(e2e.ModeLight),
  300. StartAt: startAt,
  301. Database: nodeDatabases.Choose(r),
  302. ABCIProtocol: "builtin",
  303. PersistInterval: ptrUint64(0),
  304. PersistentPeers: providers,
  305. }
  306. }
  307. func ptrUint64(i uint64) *uint64 {
  308. return &i
  309. }