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.

437 lines
12 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. "p2p": {NewP2PMode, LegacyP2PMode, HybridP2PMode},
  16. "queueType": {"priority"}, // "fifo", "wdrr"
  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. }
  24. // The following specify randomly chosen values for testnet nodes.
  25. nodeDatabases = weightedChoice{
  26. "goleveldb": 35,
  27. "badgerdb": 35,
  28. "boltdb": 15,
  29. "rocksdb": 10,
  30. "cleveldb": 5,
  31. }
  32. nodeABCIProtocols = weightedChoice{
  33. "builtin": 50,
  34. "tcp": 20,
  35. "grpc": 20,
  36. "unix": 10,
  37. }
  38. nodePrivvalProtocols = weightedChoice{
  39. "file": 50,
  40. "grpc": 20,
  41. "tcp": 20,
  42. "unix": 10,
  43. }
  44. // FIXME: v2 disabled due to flake
  45. nodeBlockSyncs = uniformChoice{"v0"} // "v2"
  46. nodeMempools = uniformChoice{"v0", "v1"}
  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{0, 2 * int(e2e.EvidenceAgeHeight), 4 * int(e2e.EvidenceAgeHeight)}
  55. nodePerturbations = probSetChoice{
  56. "disconnect": 0.1,
  57. "pause": 0.1,
  58. "kill": 0.1,
  59. "restart": 0.1,
  60. }
  61. evidence = uniformChoice{0, 1, 10}
  62. txSize = uniformChoice{1024, 4096} // either 1kb or 4kb
  63. ipv6 = uniformChoice{false, true}
  64. keyType = uniformChoice{types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1}
  65. )
  66. // Generate generates random testnets using the given RNG.
  67. func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) {
  68. manifests := []e2e.Manifest{}
  69. switch opts.P2P {
  70. case NewP2PMode, LegacyP2PMode, HybridP2PMode:
  71. defer func() {
  72. // avoid modifying the global state.
  73. original := make([]interface{}, len(testnetCombinations["p2p"]))
  74. copy(original, testnetCombinations["p2p"])
  75. testnetCombinations["p2p"] = original
  76. }()
  77. testnetCombinations["p2p"] = []interface{}{opts.P2P}
  78. case MixedP2PMode:
  79. testnetCombinations["p2p"] = []interface{}{NewP2PMode, LegacyP2PMode, HybridP2PMode}
  80. }
  81. for _, opt := range combinations(testnetCombinations) {
  82. manifest, err := generateTestnet(r, opt)
  83. if err != nil {
  84. return nil, err
  85. }
  86. if len(manifest.Nodes) < opts.MinNetworkSize {
  87. continue
  88. }
  89. if len(manifest.Nodes) == 1 {
  90. if opt["p2p"] == HybridP2PMode {
  91. continue
  92. }
  93. }
  94. if opts.MaxNetworkSize > 0 && len(manifest.Nodes) >= opts.MaxNetworkSize {
  95. continue
  96. }
  97. manifests = append(manifests, manifest)
  98. }
  99. return manifests, nil
  100. }
  101. type Options struct {
  102. MinNetworkSize int
  103. MaxNetworkSize int
  104. NumGroups int
  105. Directory string
  106. P2P P2PMode
  107. Reverse bool
  108. }
  109. type P2PMode string
  110. const (
  111. NewP2PMode P2PMode = "new"
  112. LegacyP2PMode P2PMode = "legacy"
  113. HybridP2PMode P2PMode = "hybrid"
  114. // mixed means that all combination are generated
  115. MixedP2PMode P2PMode = "mixed"
  116. )
  117. // generateTestnet generates a single testnet with the given options.
  118. func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) {
  119. manifest := e2e.Manifest{
  120. IPv6: ipv6.Choose(r).(bool),
  121. ABCIProtocol: nodeABCIProtocols.Choose(r),
  122. InitialHeight: int64(opt["initialHeight"].(int)),
  123. InitialState: opt["initialState"].(map[string]string),
  124. Validators: &map[string]int64{},
  125. ValidatorUpdates: map[string]map[string]int64{},
  126. Nodes: map[string]*e2e.ManifestNode{},
  127. KeyType: keyType.Choose(r).(string),
  128. Evidence: evidence.Choose(r).(int),
  129. QueueType: opt["queueType"].(string),
  130. TxSize: int64(txSize.Choose(r).(int)),
  131. }
  132. p2pMode := opt["p2p"].(P2PMode)
  133. switch p2pMode {
  134. case NewP2PMode, LegacyP2PMode, HybridP2PMode:
  135. default:
  136. return manifest, fmt.Errorf("unknown p2p mode %s", p2pMode)
  137. }
  138. var numSeeds, numValidators, numFulls, numLightClients int
  139. switch opt["topology"].(string) {
  140. case "single":
  141. numValidators = 1
  142. case "quad":
  143. numValidators = 4
  144. case "large":
  145. // FIXME Networks are kept small since large ones use too much CPU.
  146. numSeeds = r.Intn(1)
  147. numLightClients = r.Intn(2)
  148. numValidators = 4 + r.Intn(4)
  149. numFulls = r.Intn(4)
  150. default:
  151. return manifest, fmt.Errorf("unknown topology %q", opt["topology"])
  152. }
  153. const legacyP2PFactor float64 = 0.5
  154. // First we generate seed nodes, starting at the initial height.
  155. for i := 1; i <= numSeeds; i++ {
  156. node := generateNode(r, manifest, e2e.ModeSeed, 0, false)
  157. switch p2pMode {
  158. case LegacyP2PMode:
  159. node.UseLegacyP2P = true
  160. case HybridP2PMode:
  161. node.UseLegacyP2P = r.Float64() < legacyP2PFactor
  162. }
  163. manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node
  164. }
  165. var (
  166. numSyncingNodes = 0
  167. hybridNumNew = 0
  168. hybridNumLegacy = 0
  169. )
  170. // Next, we generate validators. We make sure a BFT quorum of validators start
  171. // at the initial height, and that we have two archive nodes. We also set up
  172. // the initial validator set, and validator set updates for delayed nodes.
  173. nextStartAt := manifest.InitialHeight + 5
  174. quorum := numValidators*2/3 + 1
  175. for i := 1; i <= numValidators; i++ {
  176. startAt := int64(0)
  177. if i > quorum && numSyncingNodes < 2 && r.Float64() >= 0.25 {
  178. numSyncingNodes++
  179. startAt = nextStartAt
  180. nextStartAt += 5
  181. }
  182. name := fmt.Sprintf("validator%02d", i)
  183. node := generateNode(r, manifest, e2e.ModeValidator, startAt, i <= 2)
  184. switch p2pMode {
  185. case LegacyP2PMode:
  186. node.UseLegacyP2P = true
  187. case HybridP2PMode:
  188. node.UseLegacyP2P = r.Float64() < legacyP2PFactor
  189. if node.UseLegacyP2P {
  190. hybridNumLegacy++
  191. if hybridNumNew == 0 {
  192. hybridNumNew++
  193. hybridNumLegacy--
  194. node.UseLegacyP2P = false
  195. }
  196. } else {
  197. hybridNumNew++
  198. if hybridNumLegacy == 0 {
  199. hybridNumNew--
  200. hybridNumLegacy++
  201. node.UseLegacyP2P = true
  202. }
  203. }
  204. }
  205. manifest.Nodes[name] = node
  206. if startAt == 0 {
  207. (*manifest.Validators)[name] = int64(30 + r.Intn(71))
  208. } else {
  209. manifest.ValidatorUpdates[fmt.Sprint(startAt+5)] = map[string]int64{
  210. name: int64(30 + r.Intn(71)),
  211. }
  212. }
  213. }
  214. // Move validators to InitChain if specified.
  215. switch opt["validators"].(string) {
  216. case "genesis":
  217. case "initchain":
  218. manifest.ValidatorUpdates["0"] = *manifest.Validators
  219. manifest.Validators = &map[string]int64{}
  220. default:
  221. return manifest, fmt.Errorf("invalid validators option %q", opt["validators"])
  222. }
  223. // Finally, we generate random full nodes.
  224. for i := 1; i <= numFulls; i++ {
  225. startAt := int64(0)
  226. if numSyncingNodes < 2 && r.Float64() >= 0.5 {
  227. numSyncingNodes++
  228. startAt = nextStartAt
  229. nextStartAt += 5
  230. }
  231. node := generateNode(r, manifest, e2e.ModeFull, startAt, false)
  232. switch p2pMode {
  233. case LegacyP2PMode:
  234. node.UseLegacyP2P = true
  235. case HybridP2PMode:
  236. node.UseLegacyP2P = r.Float64() > legacyP2PFactor
  237. }
  238. manifest.Nodes[fmt.Sprintf("full%02d", i)] = node
  239. }
  240. // We now set up peer discovery for nodes. Seed nodes are fully meshed with
  241. // each other, while non-seed nodes either use a set of random seeds or a
  242. // set of random peers that start before themselves.
  243. var seedNames, peerNames, lightProviders []string
  244. for name, node := range manifest.Nodes {
  245. if node.Mode == string(e2e.ModeSeed) {
  246. seedNames = append(seedNames, name)
  247. } else {
  248. // if the full node or validator is an ideal candidate, it is added as a light provider.
  249. // There are at least two archive nodes so there should be at least two ideal candidates
  250. if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 {
  251. lightProviders = append(lightProviders, name)
  252. }
  253. peerNames = append(peerNames, name)
  254. }
  255. }
  256. for _, name := range seedNames {
  257. for _, otherName := range seedNames {
  258. if name != otherName {
  259. manifest.Nodes[name].Seeds = append(manifest.Nodes[name].Seeds, otherName)
  260. }
  261. }
  262. }
  263. sort.Slice(peerNames, func(i, j int) bool {
  264. iName, jName := peerNames[i], peerNames[j]
  265. switch {
  266. case manifest.Nodes[iName].StartAt < manifest.Nodes[jName].StartAt:
  267. return true
  268. case manifest.Nodes[iName].StartAt > manifest.Nodes[jName].StartAt:
  269. return false
  270. default:
  271. return strings.Compare(iName, jName) == -1
  272. }
  273. })
  274. for i, name := range peerNames {
  275. // there are seeds, statesync is disabled, and it's
  276. // either the first peer by the sort order, and
  277. // (randomly half of the remaining peers use a seed
  278. // node; otherwise, choose some remaining set of the
  279. // peers.
  280. if len(seedNames) > 0 &&
  281. manifest.Nodes[name].StateSync == e2e.StateSyncDisabled &&
  282. (i == 0 || r.Float64() >= 0.5) {
  283. // choose one of the seeds
  284. manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r)
  285. } else if i > 0 {
  286. peers := uniformSetChoice(peerNames[:i])
  287. if manifest.Nodes[name].StateSync == e2e.StateSyncP2P {
  288. manifest.Nodes[name].PersistentPeers = peers.ChooseAtLeast(r, 2)
  289. } else {
  290. manifest.Nodes[name].PersistentPeers = peers.Choose(r)
  291. }
  292. }
  293. }
  294. // lastly, set up the light clients
  295. for i := 1; i <= numLightClients; i++ {
  296. startAt := manifest.InitialHeight + 5
  297. node := generateLightNode(
  298. r, startAt+(5*int64(i)), lightProviders,
  299. )
  300. switch p2pMode {
  301. case LegacyP2PMode:
  302. node.UseLegacyP2P = true
  303. case HybridP2PMode:
  304. node.UseLegacyP2P = r.Float64() < legacyP2PFactor
  305. }
  306. manifest.Nodes[fmt.Sprintf("light%02d", i)] = node
  307. }
  308. return manifest, nil
  309. }
  310. // generateNode randomly generates a node, with some constraints to avoid
  311. // generating invalid configurations. We do not set Seeds or PersistentPeers
  312. // here, since we need to know the overall network topology and startup
  313. // sequencing.
  314. func generateNode(
  315. r *rand.Rand,
  316. manifest e2e.Manifest,
  317. mode e2e.Mode,
  318. startAt int64,
  319. forceArchive bool,
  320. ) *e2e.ManifestNode {
  321. node := e2e.ManifestNode{
  322. Mode: string(mode),
  323. StartAt: startAt,
  324. Database: nodeDatabases.Choose(r),
  325. PrivvalProtocol: nodePrivvalProtocols.Choose(r),
  326. BlockSync: nodeBlockSyncs.Choose(r).(string),
  327. Mempool: nodeMempools.Choose(r).(string),
  328. StateSync: e2e.StateSyncDisabled,
  329. PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))),
  330. SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)),
  331. RetainBlocks: uint64(nodeRetainBlocks.Choose(r).(int)),
  332. Perturb: nodePerturbations.Choose(r),
  333. }
  334. if startAt > 0 {
  335. node.StateSync = nodeStateSyncs.Choose(r)
  336. if manifest.InitialHeight-startAt <= 5 && node.StateSync == e2e.StateSyncDisabled {
  337. // avoid needing to blocsync more than five total blocks.
  338. node.StateSync = uniformSetChoice([]string{
  339. e2e.StateSyncP2P,
  340. e2e.StateSyncRPC,
  341. }).Choose(r)[0]
  342. }
  343. }
  344. // If this node is forced to be an archive node, retain all blocks and
  345. // enable state sync snapshotting.
  346. if forceArchive {
  347. node.RetainBlocks = 0
  348. node.SnapshotInterval = 3
  349. }
  350. // If a node which does not persist state also does not retain blocks, randomly
  351. // choose to either persist state or retain all blocks.
  352. if node.PersistInterval != nil && *node.PersistInterval == 0 && node.RetainBlocks > 0 {
  353. if r.Float64() > 0.5 {
  354. node.RetainBlocks = 0
  355. } else {
  356. node.PersistInterval = ptrUint64(node.RetainBlocks)
  357. }
  358. }
  359. // If either PersistInterval or SnapshotInterval are greater than RetainBlocks,
  360. // expand the block retention time.
  361. if node.RetainBlocks > 0 {
  362. if node.PersistInterval != nil && node.RetainBlocks < *node.PersistInterval {
  363. node.RetainBlocks = *node.PersistInterval
  364. }
  365. if node.RetainBlocks < node.SnapshotInterval {
  366. node.RetainBlocks = node.SnapshotInterval
  367. }
  368. }
  369. if node.StateSync != e2e.StateSyncDisabled {
  370. node.BlockSync = "v0"
  371. }
  372. return &node
  373. }
  374. func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode {
  375. return &e2e.ManifestNode{
  376. Mode: string(e2e.ModeLight),
  377. StartAt: startAt,
  378. Database: nodeDatabases.Choose(r),
  379. PersistInterval: ptrUint64(0),
  380. PersistentPeers: providers,
  381. }
  382. }
  383. func ptrUint64(i uint64) *uint64 {
  384. return &i
  385. }