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.

288 lines
9.2 KiB

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