github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/generator/generate.go (about)

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