github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/test/e2e/generator/generate.go (about)

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