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.

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