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.

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