github.com/Finschia/ostracon@v1.1.5/test/e2e/generator/generate.go (about)

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