|
package e2e
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
// Manifest represents a TOML testnet manifest.
|
|
type Manifest struct {
|
|
// IPv6 uses IPv6 networking instead of IPv4. Defaults to IPv4.
|
|
IPv6 bool `toml:"ipv6"`
|
|
|
|
// InitialHeight specifies the initial block height, set in genesis. Defaults to 1.
|
|
InitialHeight int64 `toml:"initial_height"`
|
|
|
|
// InitialState is an initial set of key/value pairs for the application,
|
|
// set in genesis. Defaults to nothing.
|
|
InitialState map[string]string `toml:"initial_state"`
|
|
|
|
// Validators is the initial validator set in genesis, given as node names
|
|
// and power:
|
|
//
|
|
// validators = { validator01 = 10; validator02 = 20; validator03 = 30 }
|
|
//
|
|
// Defaults to all nodes that have mode=validator at power 100. Explicitly
|
|
// specifying an empty set will start with no validators in genesis, and
|
|
// the application must return the validator set in InitChain via the
|
|
// setting validator_update.0 (see below).
|
|
Validators *map[string]int64 `toml:"validators"`
|
|
|
|
// ValidatorUpdates is a map of heights to validator names and their power,
|
|
// and will be returned by the ABCI application. For example, the following
|
|
// changes the power of validator01 and validator02 at height 1000:
|
|
//
|
|
// [validator_update.1000]
|
|
// validator01 = 20
|
|
// validator02 = 10
|
|
//
|
|
// Specifying height 0 returns the validator update during InitChain. The
|
|
// application returns the validator updates as-is, i.e. removing a
|
|
// validator must be done by returning it with power 0, and any validators
|
|
// not specified are not changed.
|
|
ValidatorUpdates map[string]map[string]int64 `toml:"validator_update"`
|
|
|
|
// Nodes specifies the network nodes. At least one node must be given.
|
|
Nodes map[string]*ManifestNode `toml:"node"`
|
|
|
|
// KeyType sets the curve that will be used by validators.
|
|
// Options are ed25519 & secp256k1
|
|
KeyType string `toml:"key_type"`
|
|
|
|
// Evidence indicates the amount of evidence that will be injected into the
|
|
// testnet via the RPC endpoint of a random node. Default is 0
|
|
Evidence int `toml:"evidence"`
|
|
|
|
// LogLevel sets the log level of the entire testnet. This can be overridden
|
|
// by individual nodes.
|
|
LogLevel string `toml:"log_level"`
|
|
|
|
// QueueType describes the type of queue that the system uses internally
|
|
QueueType string `toml:"queue_type"`
|
|
|
|
// Number of bytes per tx. Default is 1kb (1024)
|
|
TxSize int64
|
|
}
|
|
|
|
// ManifestNode represents a node in a testnet manifest.
|
|
type ManifestNode struct {
|
|
// Mode specifies the type of node: "validator", "full", "light" or "seed".
|
|
// Defaults to "validator". Full nodes do not get a signing key (a dummy key
|
|
// is generated), and seed nodes run in seed mode with the PEX reactor enabled.
|
|
Mode string `toml:"mode"`
|
|
|
|
// Seeds is the list of node names to use as P2P seed nodes. Defaults to none.
|
|
Seeds []string `toml:"seeds"`
|
|
|
|
// PersistentPeers is a list of node names to maintain persistent P2P
|
|
// connections to. If neither seeds nor persistent peers are specified,
|
|
// this defaults to all other nodes in the network. For light clients,
|
|
// this relates to the providers the light client is connected to.
|
|
PersistentPeers []string `toml:"persistent_peers"`
|
|
|
|
// Database specifies the database backend: "goleveldb", "cleveldb",
|
|
// "rocksdb", "boltdb", or "badgerdb". Defaults to goleveldb.
|
|
Database string `toml:"database"`
|
|
|
|
// ABCIProtocol specifies the protocol used to communicate with the ABCI
|
|
// application: "unix", "tcp", "grpc", or "builtin". Defaults to unix.
|
|
// builtin will build a complete Tendermint node into the application and
|
|
// launch it instead of launching a separate Tendermint process.
|
|
ABCIProtocol string `toml:"abci_protocol"`
|
|
|
|
// PrivvalProtocol specifies the protocol used to sign consensus messages:
|
|
// "file", "unix", "tcp", or "grpc". Defaults to "file". For tcp and unix, the ABCI
|
|
// application will launch a remote signer client in a separate goroutine.
|
|
// For grpc the ABCI application will launch a remote signer server.
|
|
// Only nodes with mode=validator will actually make use of this.
|
|
PrivvalProtocol string `toml:"privval_protocol"`
|
|
|
|
// StartAt specifies the block height at which the node will be started. The
|
|
// runner will wait for the network to reach at least this block height.
|
|
StartAt int64 `toml:"start_at"`
|
|
|
|
// BlockSync specifies the block sync mode: "" (disable), "v0" or "v2".
|
|
// Defaults to disabled.
|
|
BlockSync string `toml:"block_sync"`
|
|
|
|
// Mempool specifies which version of mempool to use. Either "v0" or "v1"
|
|
Mempool string `toml:"mempool_version"`
|
|
|
|
// StateSync enables state sync. The runner automatically configures trusted
|
|
// block hashes and RPC servers. At least one node in the network must have
|
|
// SnapshotInterval set to non-zero, and the state syncing node must have
|
|
// StartAt set to an appropriate height where a snapshot is available.
|
|
// StateSync can either be "p2p" or "rpc" or an empty string to disable
|
|
StateSync string `toml:"state_sync"`
|
|
|
|
// PersistInterval specifies the height interval at which the application
|
|
// will persist state to disk. Defaults to 1 (every height), setting this to
|
|
// 0 disables state persistence.
|
|
PersistInterval *uint64 `toml:"persist_interval"`
|
|
|
|
// SnapshotInterval specifies the height interval at which the application
|
|
// will take state sync snapshots. Defaults to 0 (disabled).
|
|
SnapshotInterval uint64 `toml:"snapshot_interval"`
|
|
|
|
// RetainBlocks specifies the number of recent blocks to retain. Defaults to
|
|
// 0, which retains all blocks. Must be greater that PersistInterval,
|
|
// SnapshotInterval and EvidenceAgeHeight.
|
|
RetainBlocks uint64 `toml:"retain_blocks"`
|
|
|
|
// Perturb lists perturbations to apply to the node after it has been
|
|
// started and synced with the network:
|
|
//
|
|
// disconnect: temporarily disconnects the node from the network
|
|
// kill: kills the node with SIGKILL then restarts it
|
|
// pause: temporarily pauses (freezes) the node
|
|
// restart: restarts the node, shutting it down with SIGTERM
|
|
Perturb []string `toml:"perturb"`
|
|
|
|
// Log level sets the log level of the specific node i.e. "info".
|
|
// This is helpful when debugging a specific problem. This overrides the network
|
|
// level.
|
|
LogLevel string `toml:"log_level"`
|
|
|
|
// UseLegacyP2P enables use of the legacy p2p layer for this node.
|
|
UseLegacyP2P bool `toml:"use_legacy_p2p"`
|
|
}
|
|
|
|
// Stateless reports whether m is a node that does not own state, including light and seed nodes.
|
|
func (m ManifestNode) Stateless() bool {
|
|
return m.Mode == string(ModeLight) || m.Mode == string(ModeSeed)
|
|
}
|
|
|
|
// Save saves the testnet manifest to a file.
|
|
func (m Manifest) Save(file string) error {
|
|
f, err := os.Create(file)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create manifest file %q: %w", file, err)
|
|
}
|
|
return toml.NewEncoder(f).Encode(m)
|
|
}
|
|
|
|
// LoadManifest loads a testnet manifest from a file.
|
|
func LoadManifest(file string) (Manifest, error) {
|
|
manifest := Manifest{}
|
|
_, err := toml.DecodeFile(file, &manifest)
|
|
if err != nil {
|
|
return manifest, fmt.Errorf("failed to load testnet manifest %q: %w", file, err)
|
|
}
|
|
return manifest, nil
|
|
}
|
|
|
|
// SortManifests orders (in-place) a list of manifests such that the
|
|
// manifests will be ordered in terms of complexity (or expected
|
|
// runtime). Complexity is determined first by the number of nodes,
|
|
// and then by the total number of perturbations in the network.
|
|
//
|
|
// If reverse is true, then the manifests are ordered with the most
|
|
// complex networks before the less complex networks.
|
|
func SortManifests(manifests []Manifest, reverse bool) {
|
|
sort.SliceStable(manifests, func(i, j int) bool {
|
|
// sort based on a point-based comparison between two
|
|
// manifests.
|
|
var (
|
|
left = manifests[i]
|
|
right = manifests[j]
|
|
)
|
|
|
|
// scores start with 100 points for each node. The
|
|
// number of nodes in a network is the most important
|
|
// factor in the complexity of the test.
|
|
leftScore := len(left.Nodes) * 100
|
|
rightScore := len(right.Nodes) * 100
|
|
|
|
// add two points for every node perturbation, and one
|
|
// point for every node that starts after genesis.
|
|
for _, n := range left.Nodes {
|
|
leftScore += (len(n.Perturb) * 2)
|
|
|
|
if n.StartAt > 0 {
|
|
leftScore += 3
|
|
}
|
|
}
|
|
for _, n := range right.Nodes {
|
|
rightScore += (len(n.Perturb) * 2)
|
|
if n.StartAt > 0 {
|
|
rightScore += 3
|
|
}
|
|
}
|
|
|
|
// add one point if the network has evidence.
|
|
if left.Evidence > 0 {
|
|
leftScore += 2
|
|
}
|
|
|
|
if right.Evidence > 0 {
|
|
rightScore += 2
|
|
}
|
|
|
|
if left.TxSize > right.TxSize {
|
|
leftScore++
|
|
}
|
|
|
|
if right.TxSize > left.TxSize {
|
|
rightScore++
|
|
}
|
|
|
|
if reverse {
|
|
return leftScore >= rightScore
|
|
}
|
|
|
|
return leftScore < rightScore
|
|
})
|
|
}
|
|
|
|
// SplitGroups divides a list of manifests into n groups of
|
|
// manifests.
|
|
func SplitGroups(groups int, manifests []Manifest) [][]Manifest {
|
|
groupSize := (len(manifests) + groups - 1) / groups
|
|
splitManifests := make([][]Manifest, 0, groups)
|
|
|
|
for i := 0; i < len(manifests); i += groupSize {
|
|
grp := make([]Manifest, groupSize)
|
|
n := copy(grp, manifests[i:])
|
|
splitManifests = append(splitManifests, grp[:n])
|
|
}
|
|
|
|
return splitManifests
|
|
}
|
|
|
|
// WriteManifests writes a collection of manifests into files with the
|
|
// specified path prefix.
|
|
func WriteManifests(prefix string, manifests []Manifest) error {
|
|
for i, manifest := range manifests {
|
|
if err := manifest.Save(fmt.Sprintf("%s-%04d.toml", prefix, i)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|