bitbucket.org/number571/tendermint@v0.8.14/test/e2e/generator/generate.go (about)

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