mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 06:31:57 +00:00
Merge remote-tracking branch 'origin/master' into wb/proposer-based-timestamps
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"context"
|
||||
|
||||
tmjson "github.com/tendermint/tendermint/libs/json"
|
||||
coregrpc "github.com/tendermint/tendermint/rpc/grpc"
|
||||
)
|
||||
|
||||
var grpcAddr = "tcp://localhost:36656"
|
||||
|
||||
func main() {
|
||||
args := os.Args
|
||||
if len(args) == 1 {
|
||||
fmt.Println("Must enter a transaction to send (hex)")
|
||||
os.Exit(1)
|
||||
}
|
||||
tx := args[1]
|
||||
txBytes, err := hex.DecodeString(tx)
|
||||
if err != nil {
|
||||
fmt.Println("Invalid hex", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
clientGRPC := coregrpc.StartGRPCClient(grpcAddr)
|
||||
res, err := clientGRPC.BroadcastTx(context.Background(), &coregrpc.RequestBroadcastTx{Tx: txBytes})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
bz, err := tmjson.Marshal(res)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(bz))
|
||||
}
|
||||
+3
-6
@@ -3,11 +3,8 @@ all: docker generator runner tests
|
||||
docker:
|
||||
docker build --tag tendermint/e2e-node -f docker/Dockerfile ../..
|
||||
|
||||
# We need to build support for database backends into the app in
|
||||
# order to build a binary with a Tendermint node in it (for built-in
|
||||
# ABCI testing).
|
||||
app:
|
||||
go build -o build/app -tags badgerdb,boltdb,cleveldb,rocksdb ./app
|
||||
node:
|
||||
go build -o build/node -tags badgerdb,boltdb,cleveldb,rocksdb ./node
|
||||
|
||||
generator:
|
||||
go build -o build/generator ./generator
|
||||
@@ -18,4 +15,4 @@ runner:
|
||||
tests:
|
||||
go test -o build/tests ./tests
|
||||
|
||||
.PHONY: all app docker generator runner tests
|
||||
.PHONY: all docker generator runner tests node
|
||||
|
||||
+33
-1
@@ -142,10 +142,42 @@ Docker does not enable IPv6 by default. To do so, enter the following in
|
||||
}
|
||||
```
|
||||
|
||||
## Benchmarking testnets
|
||||
## Benchmarking Testnets
|
||||
|
||||
It is also possible to run a simple benchmark on a testnet. This is done through the `benchmark` command. This manages the entire process: setting up the environment, starting the test net, waiting for a considerable amount of blocks to be used (currently 100), and then returning the following metrics from the sample of the blockchain:
|
||||
|
||||
- Average time to produce a block
|
||||
- Standard deviation of producing a block
|
||||
- Minimum and maximum time to produce a block
|
||||
|
||||
## Running Individual Nodes
|
||||
|
||||
The E2E test harness is designed to run several nodes of varying configurations within docker. It is also possible to run a single node in the case of running larger, geographically-dispersed testnets. To run a single node you can either run:
|
||||
|
||||
**Built-in**
|
||||
|
||||
```bash
|
||||
make node
|
||||
tendermint init validator
|
||||
TMHOME=$HOME/.tendermint ./build/node ./node/built-in.toml
|
||||
```
|
||||
|
||||
To make things simpler the e2e application can also be run in the tendermint binary
|
||||
by running
|
||||
|
||||
```bash
|
||||
tendermint start --proxy-app e2e
|
||||
```
|
||||
|
||||
However this won't offer the same level of configurability of the application.
|
||||
|
||||
**Socket**
|
||||
|
||||
```bash
|
||||
make node
|
||||
tendermint init validator
|
||||
tendermint start
|
||||
./build/node ./node.socket.toml
|
||||
```
|
||||
|
||||
Check `node/config.go` to see how the settings of the test application can be tweaked.
|
||||
+61
-2
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/tendermint/tendermint/abci/example/code"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
@@ -27,6 +28,55 @@ type Application struct {
|
||||
restoreChunks [][]byte
|
||||
}
|
||||
|
||||
// Config allows for the setting of high level parameters for running the e2e Application
|
||||
// KeyType and ValidatorUpdates must be the same for all nodes running the same application.
|
||||
type Config struct {
|
||||
// The directory with which state.json will be persisted in. Usually $HOME/.tendermint/data
|
||||
Dir string `toml:"dir"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// KeyType sets the curve that will be used by validators.
|
||||
// Options are ed25519 & secp256k1
|
||||
KeyType string `toml:"key_type"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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.
|
||||
//
|
||||
// height <-> pubkey <-> voting power
|
||||
ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"`
|
||||
}
|
||||
|
||||
func DefaultConfig(dir string) *Config {
|
||||
return &Config{
|
||||
PersistInterval: 1,
|
||||
SnapshotInterval: 100,
|
||||
Dir: dir,
|
||||
}
|
||||
}
|
||||
|
||||
// NewApplication creates the application.
|
||||
func NewApplication(cfg *Config) (*Application, error) {
|
||||
state, err := NewState(filepath.Join(cfg.Dir, "state.json"), cfg.PersistInterval)
|
||||
@@ -67,6 +117,11 @@ func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitCh
|
||||
}
|
||||
resp := abci.ResponseInitChain{
|
||||
AppHash: app.state.Hash,
|
||||
ConsensusParams: &types.ConsensusParams{
|
||||
Version: &types.VersionParams{
|
||||
AppVersion: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
if resp.Validators, err = app.validatorUpdates(0); err != nil {
|
||||
panic(err)
|
||||
@@ -134,7 +189,11 @@ func (app *Application) Commit() abci.ResponseCommit {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger.Info("Created state sync snapshot", "height", snapshot.Height)
|
||||
app.logger.Info("Created state sync snapshot", "height", snapshot.Height)
|
||||
err = app.snapshots.Prune(maxSnapshotCount)
|
||||
if err != nil {
|
||||
app.logger.Error("Failed to prune snapshots", "err", err)
|
||||
}
|
||||
}
|
||||
retainHeight := int64(0)
|
||||
if app.cfg.RetainBlocks > 0 {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// nolint: gosec
|
||||
package main
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -16,6 +15,9 @@ import (
|
||||
|
||||
const (
|
||||
snapshotChunkSize = 1e6
|
||||
|
||||
// Keep only the most recent 10 snapshots. Older snapshots are pruned
|
||||
maxSnapshotCount = 10
|
||||
)
|
||||
|
||||
// SnapshotStore stores state sync snapshots. Snapshots are stored simply as
|
||||
@@ -45,7 +47,7 @@ func (s *SnapshotStore) loadMetadata() error {
|
||||
file := filepath.Join(s.dir, "metadata.json")
|
||||
metadata := []abci.Snapshot{}
|
||||
|
||||
bz, err := ioutil.ReadFile(file)
|
||||
bz, err := os.ReadFile(file)
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
case err != nil:
|
||||
@@ -72,7 +74,7 @@ func (s *SnapshotStore) saveMetadata() error {
|
||||
// save the file to a new file and move it to make saving atomic.
|
||||
newFile := filepath.Join(s.dir, "metadata.json.new")
|
||||
file := filepath.Join(s.dir, "metadata.json")
|
||||
err = ioutil.WriteFile(newFile, bz, 0644) // nolint: gosec
|
||||
err = os.WriteFile(newFile, bz, 0644) // nolint: gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +95,7 @@ func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) {
|
||||
Hash: hashItems(state.Values),
|
||||
Chunks: byteChunks(bz),
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644)
|
||||
err = os.WriteFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", state.Height)), bz, 0644)
|
||||
if err != nil {
|
||||
return abci.Snapshot{}, err
|
||||
}
|
||||
@@ -105,6 +107,27 @@ func (s *SnapshotStore) Create(state *State) (abci.Snapshot, error) {
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
// Prune removes old snapshots ensuring only the most recent n snapshots remain
|
||||
func (s *SnapshotStore) Prune(n int) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
// snapshots are appended to the metadata struct, hence pruning removes from
|
||||
// the front of the array
|
||||
i := 0
|
||||
for ; i < len(s.metadata)-n; i++ {
|
||||
h := s.metadata[i].Height
|
||||
if err := os.Remove(filepath.Join(s.dir, fmt.Sprintf("%v.json", h))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update metadata by removing the deleted snapshots
|
||||
pruned := make([]abci.Snapshot, len(s.metadata[i:]))
|
||||
copy(pruned, s.metadata[i:])
|
||||
s.metadata = pruned
|
||||
return nil
|
||||
}
|
||||
|
||||
// List lists available snapshots.
|
||||
func (s *SnapshotStore) List() ([]*abci.Snapshot, error) {
|
||||
s.RLock()
|
||||
@@ -122,7 +145,7 @@ func (s *SnapshotStore) LoadChunk(height uint64, format uint32, chunk uint32) ([
|
||||
defer s.RUnlock()
|
||||
for _, snapshot := range s.metadata {
|
||||
if snapshot.Height == height && snapshot.Format == format {
|
||||
bz, err := ioutil.ReadFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", height)))
|
||||
bz, err := os.ReadFile(filepath.Join(s.dir, fmt.Sprintf("%v.json", height)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//nolint: gosec
|
||||
package main
|
||||
package app
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -45,7 +44,7 @@ func NewState(file string, persistInterval uint64) (*State, error) {
|
||||
// load loads state from disk. It does not take out a lock, since it is called
|
||||
// during construction.
|
||||
func (s *State) load() error {
|
||||
bz, err := ioutil.ReadFile(s.file)
|
||||
bz, err := os.ReadFile(s.file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read state from %q: %w", s.file, err)
|
||||
}
|
||||
@@ -66,7 +65,7 @@ func (s *State) save() error {
|
||||
// We write the state to a separate file and move it to the destination, to
|
||||
// make it atomic.
|
||||
newFile := fmt.Sprintf("%v.new", s.file)
|
||||
err = ioutil.WriteFile(newFile, bz, 0644)
|
||||
err = os.WriteFile(newFile, bz, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write state to %q: %w", s.file, err)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ COPY . .
|
||||
RUN make build && cp build/tendermint /usr/bin/tendermint
|
||||
COPY test/e2e/docker/entrypoint* /usr/bin/
|
||||
|
||||
RUN cd test/e2e && make app && cp build/app /usr/bin/app
|
||||
RUN cd test/e2e && make node && cp build/node /usr/bin/app
|
||||
|
||||
# Set up runtime directory. We don't use a separate runtime image since we need
|
||||
# e.g. leveldb and rocksdb which are already installed in the build image.
|
||||
|
||||
@@ -15,8 +15,7 @@ var (
|
||||
// separate testnet for each combination (Cartesian product) of options.
|
||||
testnetCombinations = map[string][]interface{}{
|
||||
"topology": {"single", "quad", "large"},
|
||||
"p2p": {NewP2PMode, LegacyP2PMode, HybridP2PMode},
|
||||
"queueType": {"priority"}, // "fifo", "wdrr"
|
||||
"queueType": {"priority"}, // "fifo"
|
||||
"initialHeight": {0, 1000},
|
||||
"initialState": {
|
||||
map[string]string{},
|
||||
@@ -45,21 +44,26 @@ var (
|
||||
"tcp": 20,
|
||||
"unix": 10,
|
||||
}
|
||||
// FIXME: v2 disabled due to flake
|
||||
nodeBlockSyncs = uniformChoice{"v0"} // "v2"
|
||||
nodeMempools = uniformChoice{"v0", "v1"}
|
||||
nodeStateSyncs = uniformChoice{e2e.StateSyncDisabled, e2e.StateSyncP2P, e2e.StateSyncRPC}
|
||||
nodeStateSyncs = weightedChoice{
|
||||
e2e.StateSyncDisabled: 10,
|
||||
e2e.StateSyncP2P: 45,
|
||||
e2e.StateSyncRPC: 45,
|
||||
}
|
||||
nodePersistIntervals = uniformChoice{0, 1, 5}
|
||||
nodeSnapshotIntervals = uniformChoice{0, 3}
|
||||
nodeRetainBlocks = uniformChoice{0, 2 * int(e2e.EvidenceAgeHeight), 4 * int(e2e.EvidenceAgeHeight)}
|
||||
nodePerturbations = probSetChoice{
|
||||
nodeSnapshotIntervals = uniformChoice{0, 5}
|
||||
nodeRetainBlocks = uniformChoice{
|
||||
0,
|
||||
2 * int(e2e.EvidenceAgeHeight),
|
||||
4 * int(e2e.EvidenceAgeHeight),
|
||||
}
|
||||
nodePerturbations = probSetChoice{
|
||||
"disconnect": 0.1,
|
||||
"pause": 0.1,
|
||||
"kill": 0.1,
|
||||
"restart": 0.1,
|
||||
}
|
||||
evidence = uniformChoice{0, 1, 10}
|
||||
txSize = uniformChoice{1024, 10240} // either 1kb or 10kb
|
||||
txSize = uniformChoice{1024, 4096} // either 1kb or 4kb
|
||||
ipv6 = uniformChoice{false, true}
|
||||
keyType = uniformChoice{types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1}
|
||||
)
|
||||
@@ -67,12 +71,6 @@ var (
|
||||
// Generate generates random testnets using the given RNG.
|
||||
func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) {
|
||||
manifests := []e2e.Manifest{}
|
||||
switch opts.P2P {
|
||||
case NewP2PMode, LegacyP2PMode, HybridP2PMode:
|
||||
testnetCombinations["p2p"] = []interface{}{opts.P2P}
|
||||
default:
|
||||
testnetCombinations["p2p"] = []interface{}{NewP2PMode, LegacyP2PMode, HybridP2PMode}
|
||||
}
|
||||
|
||||
for _, opt := range combinations(testnetCombinations) {
|
||||
manifest, err := generateTestnet(r, opt)
|
||||
@@ -80,42 +78,33 @@ func Generate(r *rand.Rand, opts Options) ([]e2e.Manifest, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(manifest.Nodes) == 1 {
|
||||
if opt["p2p"] == HybridP2PMode {
|
||||
continue
|
||||
}
|
||||
if len(manifest.Nodes) < opts.MinNetworkSize {
|
||||
continue
|
||||
}
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
|
||||
if opts.Sorted {
|
||||
// When the sorted flag is set (generally, as long as
|
||||
// groups aren't set),
|
||||
e2e.SortManifests(manifests)
|
||||
if opts.MaxNetworkSize > 0 && len(manifest.Nodes) >= opts.MaxNetworkSize {
|
||||
continue
|
||||
}
|
||||
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
P2P P2PMode
|
||||
Sorted bool
|
||||
MinNetworkSize int
|
||||
MaxNetworkSize int
|
||||
NumGroups int
|
||||
Directory string
|
||||
Reverse bool
|
||||
}
|
||||
|
||||
type P2PMode string
|
||||
|
||||
const (
|
||||
NewP2PMode P2PMode = "new"
|
||||
LegacyP2PMode P2PMode = "legacy"
|
||||
HybridP2PMode P2PMode = "hybrid"
|
||||
// mixed means that all combination are generated
|
||||
MixedP2PMode P2PMode = "mixed"
|
||||
)
|
||||
|
||||
// generateTestnet generates a single testnet with the given options.
|
||||
func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, error) {
|
||||
manifest := e2e.Manifest{
|
||||
IPv6: ipv6.Choose(r).(bool),
|
||||
ABCIProtocol: nodeABCIProtocols.Choose(r),
|
||||
InitialHeight: int64(opt["initialHeight"].(int)),
|
||||
InitialState: opt["initialState"].(map[string]string),
|
||||
Validators: &map[string]int64{},
|
||||
@@ -127,13 +116,6 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
TxSize: int64(txSize.Choose(r).(int)),
|
||||
}
|
||||
|
||||
p2pMode := opt["p2p"].(P2PMode)
|
||||
switch p2pMode {
|
||||
case NewP2PMode, LegacyP2PMode, HybridP2PMode:
|
||||
default:
|
||||
return manifest, fmt.Errorf("unknown p2p mode %s", p2pMode)
|
||||
}
|
||||
|
||||
var numSeeds, numValidators, numFulls, numLightClients int
|
||||
switch opt["topology"].(string) {
|
||||
case "single":
|
||||
@@ -142,8 +124,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
numValidators = 4
|
||||
case "large":
|
||||
// FIXME Networks are kept small since large ones use too much CPU.
|
||||
numSeeds = r.Intn(2)
|
||||
numLightClients = r.Intn(3)
|
||||
numSeeds = r.Intn(1)
|
||||
numLightClients = r.Intn(2)
|
||||
numValidators = 4 + r.Intn(4)
|
||||
numFulls = r.Intn(4)
|
||||
default:
|
||||
@@ -152,18 +134,12 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
|
||||
// First we generate seed nodes, starting at the initial height.
|
||||
for i := 1; i <= numSeeds; i++ {
|
||||
node := generateNode(r, e2e.ModeSeed, 0, manifest.InitialHeight, false)
|
||||
|
||||
switch p2pMode {
|
||||
case LegacyP2PMode:
|
||||
node.UseLegacyP2P = true
|
||||
case HybridP2PMode:
|
||||
node.UseLegacyP2P = r.Intn(2) == 1
|
||||
}
|
||||
|
||||
node := generateNode(r, manifest, e2e.ModeSeed, 0, false)
|
||||
manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node
|
||||
}
|
||||
|
||||
var numSyncingNodes = 0
|
||||
|
||||
// Next, we generate validators. We make sure a BFT quorum of validators start
|
||||
// at the initial height, and that we have two archive nodes. We also set up
|
||||
// the initial validator set, and validator set updates for delayed nodes.
|
||||
@@ -171,20 +147,13 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
quorum := numValidators*2/3 + 1
|
||||
for i := 1; i <= numValidators; i++ {
|
||||
startAt := int64(0)
|
||||
if i > quorum {
|
||||
if i > quorum && numSyncingNodes < 2 && r.Float64() >= 0.25 {
|
||||
numSyncingNodes++
|
||||
startAt = nextStartAt
|
||||
nextStartAt += 5
|
||||
}
|
||||
name := fmt.Sprintf("validator%02d", i)
|
||||
node := generateNode(
|
||||
r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2)
|
||||
|
||||
switch p2pMode {
|
||||
case LegacyP2PMode:
|
||||
node.UseLegacyP2P = true
|
||||
case HybridP2PMode:
|
||||
node.UseLegacyP2P = r.Intn(2) == 1
|
||||
}
|
||||
node := generateNode(r, manifest, e2e.ModeValidator, startAt, i <= 2)
|
||||
|
||||
manifest.Nodes[name] = node
|
||||
|
||||
@@ -210,18 +179,12 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
// Finally, we generate random full nodes.
|
||||
for i := 1; i <= numFulls; i++ {
|
||||
startAt := int64(0)
|
||||
if r.Float64() >= 0.5 {
|
||||
if numSyncingNodes < 2 && r.Float64() >= 0.5 {
|
||||
numSyncingNodes++
|
||||
startAt = nextStartAt
|
||||
nextStartAt += 5
|
||||
}
|
||||
node := generateNode(r, e2e.ModeFull, startAt, manifest.InitialHeight, false)
|
||||
|
||||
switch p2pMode {
|
||||
case LegacyP2PMode:
|
||||
node.UseLegacyP2P = true
|
||||
case HybridP2PMode:
|
||||
node.UseLegacyP2P = r.Intn(2) == 1
|
||||
}
|
||||
node := generateNode(r, manifest, e2e.ModeFull, startAt, false)
|
||||
|
||||
manifest.Nodes[fmt.Sprintf("full%02d", i)] = node
|
||||
}
|
||||
@@ -263,19 +226,32 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
}
|
||||
})
|
||||
for i, name := range peerNames {
|
||||
if len(seedNames) > 0 && (i == 0 || r.Float64() >= 0.5) {
|
||||
// there are seeds, statesync is disabled, and it's
|
||||
// either the first peer by the sort order, and
|
||||
// (randomly half of the remaining peers use a seed
|
||||
// node; otherwise, choose some remaining set of the
|
||||
// peers.
|
||||
|
||||
if len(seedNames) > 0 &&
|
||||
manifest.Nodes[name].StateSync == e2e.StateSyncDisabled &&
|
||||
(i == 0 || r.Float64() >= 0.5) {
|
||||
|
||||
// choose one of the seeds
|
||||
manifest.Nodes[name].Seeds = uniformSetChoice(seedNames).Choose(r)
|
||||
} else if i > 0 {
|
||||
manifest.Nodes[name].PersistentPeers = uniformSetChoice(peerNames[:i]).Choose(r)
|
||||
} else if i > 1 && r.Float64() >= 0.5 {
|
||||
peers := uniformSetChoice(peerNames[:i])
|
||||
manifest.Nodes[name].PersistentPeers = peers.ChooseAtLeast(r, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// lastly, set up the light clients
|
||||
for i := 1; i <= numLightClients; i++ {
|
||||
startAt := manifest.InitialHeight + 5
|
||||
manifest.Nodes[fmt.Sprintf("light%02d", i)] = generateLightNode(
|
||||
r, startAt+(5*int64(i)), lightProviders,
|
||||
)
|
||||
|
||||
node := generateLightNode(r, startAt+(5*int64(i)), lightProviders)
|
||||
|
||||
manifest.Nodes[fmt.Sprintf("light%02d", i)] = node
|
||||
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
@@ -286,16 +262,17 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
// here, since we need to know the overall network topology and startup
|
||||
// sequencing.
|
||||
func generateNode(
|
||||
r *rand.Rand, mode e2e.Mode, startAt int64, initialHeight int64, forceArchive bool,
|
||||
r *rand.Rand,
|
||||
manifest e2e.Manifest,
|
||||
mode e2e.Mode,
|
||||
startAt int64,
|
||||
forceArchive bool,
|
||||
) *e2e.ManifestNode {
|
||||
node := e2e.ManifestNode{
|
||||
Mode: string(mode),
|
||||
StartAt: startAt,
|
||||
Database: nodeDatabases.Choose(r),
|
||||
ABCIProtocol: nodeABCIProtocols.Choose(r),
|
||||
PrivvalProtocol: nodePrivvalProtocols.Choose(r),
|
||||
BlockSync: nodeBlockSyncs.Choose(r).(string),
|
||||
Mempool: nodeMempools.Choose(r).(string),
|
||||
StateSync: e2e.StateSyncDisabled,
|
||||
PersistInterval: ptrUint64(uint64(nodePersistIntervals.Choose(r).(int))),
|
||||
SnapshotInterval: uint64(nodeSnapshotIntervals.Choose(r).(int)),
|
||||
@@ -303,8 +280,19 @@ func generateNode(
|
||||
Perturb: nodePerturbations.Choose(r),
|
||||
}
|
||||
|
||||
if node.PrivvalProtocol == "" {
|
||||
node.PrivvalProtocol = "file"
|
||||
}
|
||||
|
||||
if startAt > 0 {
|
||||
node.StateSync = nodeStateSyncs.Choose(r).(string)
|
||||
node.StateSync = nodeStateSyncs.Choose(r)
|
||||
if manifest.InitialHeight-startAt <= 5 && node.StateSync == e2e.StateSyncDisabled {
|
||||
// avoid needing to blocsync more than five total blocks.
|
||||
node.StateSync = uniformSetChoice([]string{
|
||||
e2e.StateSyncP2P,
|
||||
e2e.StateSyncRPC,
|
||||
}).Choose(r)[0]
|
||||
}
|
||||
}
|
||||
|
||||
// If this node is forced to be an archive node, retain all blocks and
|
||||
@@ -335,10 +323,6 @@ func generateNode(
|
||||
}
|
||||
}
|
||||
|
||||
if node.StateSync != e2e.StateSyncDisabled {
|
||||
node.BlockSync = "v0"
|
||||
}
|
||||
|
||||
return &node
|
||||
}
|
||||
|
||||
@@ -347,7 +331,6 @@ func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.Man
|
||||
Mode: string(e2e.ModeLight),
|
||||
StartAt: startAt,
|
||||
Database: nodeDatabases.Choose(r),
|
||||
ABCIProtocol: "builtin",
|
||||
PersistInterval: ptrUint64(0),
|
||||
PersistentPeers: providers,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
func TestGenerator(t *testing.T) {
|
||||
manifests, err := Generate(rand.New(rand.NewSource(randomSeed)), Options{})
|
||||
require.NoError(t, err)
|
||||
require.True(t, len(manifests) >= 24, "insufficient combinations %d", len(manifests))
|
||||
|
||||
// this just means that the numbers reported by the test
|
||||
// failures map to the test cases that you'd see locally.
|
||||
e2e.SortManifests(manifests, false /* ascending */)
|
||||
|
||||
for idx, m := range manifests {
|
||||
t.Run(fmt.Sprintf("Case%04d", idx), func(t *testing.T) {
|
||||
numStateSyncs := 0
|
||||
for name, node := range m.Nodes {
|
||||
if node.StateSync != e2e.StateSyncDisabled {
|
||||
numStateSyncs++
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("StateSync", func(t *testing.T) {
|
||||
if node.StartAt > m.InitialHeight+5 && !node.Stateless() {
|
||||
require.NotEqual(t, node.StateSync, e2e.StateSyncDisabled)
|
||||
}
|
||||
if node.StateSync != e2e.StateSyncDisabled {
|
||||
require.Zero(t, node.Seeds, node.StateSync)
|
||||
require.True(t, len(node.PersistentPeers) >= 2 || len(node.PersistentPeers) == 0,
|
||||
"peers: %v", node.PersistentPeers)
|
||||
}
|
||||
})
|
||||
if e2e.Mode(node.Mode) != e2e.ModeLight {
|
||||
t.Run("PrivvalProtocol", func(t *testing.T) {
|
||||
require.NotZero(t, node.PrivvalProtocol)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
require.True(t, numStateSyncs <= 2)
|
||||
})
|
||||
}
|
||||
}
|
||||
+30
-48
@@ -3,7 +3,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,6 +26,7 @@ func main() {
|
||||
// CLI is the Cobra-based command-line interface.
|
||||
type CLI struct {
|
||||
root *cobra.Command
|
||||
opts Options
|
||||
}
|
||||
|
||||
// NewCLI sets up the CLI.
|
||||
@@ -37,73 +38,54 @@ func NewCLI() *CLI {
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true, // we'll output them ourselves in Run()
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
dir, err := cmd.Flags().GetString("dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groups, err := cmd.Flags().GetInt("groups")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p2pMode, err := cmd.Flags().GetString("p2p")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var opts Options
|
||||
switch mode := P2PMode(p2pMode); mode {
|
||||
case NewP2PMode, LegacyP2PMode, HybridP2PMode, MixedP2PMode:
|
||||
opts = Options{P2P: mode}
|
||||
default:
|
||||
return fmt.Errorf("p2p mode must be either new, legacy, hybrid or mixed got %s", p2pMode)
|
||||
}
|
||||
|
||||
if groups == 0 {
|
||||
opts.Sorted = true
|
||||
}
|
||||
|
||||
return cli.generate(dir, groups, opts)
|
||||
return cli.generate()
|
||||
},
|
||||
}
|
||||
|
||||
cli.root.PersistentFlags().StringP("dir", "d", "", "Output directory for manifests")
|
||||
cli.root.PersistentFlags().StringVarP(&cli.opts.Directory, "dir", "d", "", "Output directory for manifests")
|
||||
_ = cli.root.MarkPersistentFlagRequired("dir")
|
||||
cli.root.PersistentFlags().IntP("groups", "g", 0, "Number of groups")
|
||||
cli.root.PersistentFlags().StringP("p2p", "p", string(MixedP2PMode),
|
||||
"P2P typology to be generated [\"new\", \"legacy\", \"hybrid\" or \"mixed\" ]")
|
||||
cli.root.Flags().BoolVarP(&cli.opts.Reverse, "reverse", "r", false, "Reverse sort order")
|
||||
cli.root.PersistentFlags().IntVarP(&cli.opts.NumGroups, "groups", "g", 0, "Number of groups")
|
||||
cli.root.PersistentFlags().IntVarP(&cli.opts.MinNetworkSize, "min-size", "", 1,
|
||||
"Minimum network size (nodes)")
|
||||
cli.root.PersistentFlags().IntVarP(&cli.opts.MaxNetworkSize, "max-size", "", 0,
|
||||
"Maxmum network size (nodes), 0 is unlimited")
|
||||
|
||||
return cli
|
||||
}
|
||||
|
||||
// generate generates manifests in a directory.
|
||||
func (cli *CLI) generate(dir string, groups int, opts Options) error {
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
func (cli *CLI) generate() error {
|
||||
err := os.MkdirAll(cli.opts.Directory, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manifests, err := Generate(rand.New(rand.NewSource(randomSeed)), opts)
|
||||
manifests, err := Generate(rand.New(rand.NewSource(randomSeed)), cli.opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if groups <= 0 {
|
||||
for i, manifest := range manifests {
|
||||
err = manifest.Save(filepath.Join(dir, fmt.Sprintf("gen-%04d.toml", i)))
|
||||
if err != nil {
|
||||
|
||||
switch {
|
||||
case cli.opts.NumGroups <= 0:
|
||||
e2e.SortManifests(manifests, cli.opts.Reverse)
|
||||
|
||||
if err := e2e.WriteManifests(filepath.Join(cli.opts.Directory, "gen"), manifests); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
groupManifests := e2e.SplitGroups(cli.opts.NumGroups, manifests)
|
||||
|
||||
for idx, gm := range groupManifests {
|
||||
e2e.SortManifests(gm, cli.opts.Reverse)
|
||||
|
||||
prefix := filepath.Join(cli.opts.Directory, fmt.Sprintf("gen-group%02d", idx))
|
||||
if err := e2e.WriteManifests(prefix, gm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupSize := int(math.Ceil(float64(len(manifests)) / float64(groups)))
|
||||
for g := 0; g < groups; g++ {
|
||||
for i := 0; i < groupSize && g*groupSize+i < len(manifests); i++ {
|
||||
manifest := manifests[g*groupSize+i]
|
||||
err = manifest.Save(filepath.Join(dir, fmt.Sprintf("gen-group%02d-%04d.toml", g, i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -74,18 +74,24 @@ func (pc probSetChoice) Choose(r *rand.Rand) []string {
|
||||
// uniformSetChoice picks a set of strings with uniform probability, picking at least one.
|
||||
type uniformSetChoice []string
|
||||
|
||||
func (usc uniformSetChoice) Choose(r *rand.Rand) []string {
|
||||
func (usc uniformSetChoice) Choose(r *rand.Rand) []string { return usc.ChooseAtLeast(r, 1) }
|
||||
|
||||
func (usc uniformSetChoice) ChooseAtLeast(r *rand.Rand, num int) []string {
|
||||
choices := []string{}
|
||||
indexes := r.Perm(len(usc))
|
||||
if len(indexes) > 1 {
|
||||
indexes = indexes[:1+r.Intn(len(indexes)-1)]
|
||||
if num < len(indexes) {
|
||||
indexes = indexes[:1+randomInRange(r, num, len(indexes)-1)]
|
||||
}
|
||||
|
||||
for _, i := range indexes {
|
||||
choices = append(choices, usc[i])
|
||||
}
|
||||
|
||||
return choices
|
||||
}
|
||||
|
||||
func randomInRange(r *rand.Rand, min, max int) int { return r.Intn(max-min+1) + min }
|
||||
|
||||
type weightedChoice map[string]uint
|
||||
|
||||
func (wc weightedChoice) Choose(r *rand.Rand) string {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCombinations(t *testing.T) {
|
||||
@@ -29,3 +32,28 @@ func TestCombinations(t *testing.T) {
|
||||
{"bool": true, "int": 3, "string": "bar"},
|
||||
}, c)
|
||||
}
|
||||
|
||||
func TestUniformSetChoice(t *testing.T) {
|
||||
set := uniformSetChoice([]string{"a", "b", "c"})
|
||||
r := rand.New(rand.NewSource(2384))
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("Iteration%03d", i), func(t *testing.T) {
|
||||
set = append(set, t.Name())
|
||||
|
||||
t.Run("ChooseAtLeastSubset", func(t *testing.T) {
|
||||
require.True(t, len(set.ChooseAtLeast(r, 1)) >= 1)
|
||||
require.True(t, len(set.ChooseAtLeast(r, 2)) >= 2)
|
||||
require.True(t, len(set.ChooseAtLeast(r, len(set)/2)) >= len(set)/2)
|
||||
})
|
||||
t.Run("ChooseAtLeastEqualOrGreaterToLength", func(t *testing.T) {
|
||||
require.Len(t, set.ChooseAtLeast(r, len(set)), len(set))
|
||||
require.Len(t, set.ChooseAtLeast(r, len(set)+1), len(set))
|
||||
require.Len(t, set.ChooseAtLeast(r, len(set)*10), len(set))
|
||||
})
|
||||
t.Run("ChooseSingle", func(t *testing.T) {
|
||||
require.True(t, len(set.Choose(r)) >= 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This testnet is run by CI, and attempts to cover a broad range of
|
||||
# functionality with a single network.
|
||||
|
||||
disable_legacy_p2p = false
|
||||
evidence = 5
|
||||
initial_height = 1000
|
||||
initial_state = {initial01 = "a", initial02 = "b", initial03 = "c"}
|
||||
@@ -35,6 +34,7 @@ perturb = ["restart"]
|
||||
perturb = ["disconnect"]
|
||||
seeds = ["seed01"]
|
||||
snapshot_interval = 5
|
||||
block_sync = "v0"
|
||||
|
||||
[node.validator02]
|
||||
abci_protocol = "tcp"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
snapshot_interval = 100
|
||||
persist_interval = 1
|
||||
chain_id = "test-chain"
|
||||
protocol = "builtin"
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
"github.com/tendermint/tendermint/test/e2e/app"
|
||||
)
|
||||
|
||||
// Config is the application configuration.
|
||||
@@ -22,10 +24,21 @@ type Config struct {
|
||||
PrivValServer string `toml:"privval_server"`
|
||||
PrivValKey string `toml:"privval_key"`
|
||||
PrivValState string `toml:"privval_state"`
|
||||
Misbehaviors map[string]string `toml:"misbehaviors"`
|
||||
KeyType string `toml:"key_type"`
|
||||
}
|
||||
|
||||
// App extracts out the application specific configuration parameters
|
||||
func (cfg *Config) App() *app.Config {
|
||||
return &app.Config{
|
||||
Dir: cfg.Dir,
|
||||
SnapshotInterval: cfg.SnapshotInterval,
|
||||
RetainBlocks: cfg.RetainBlocks,
|
||||
KeyType: cfg.KeyType,
|
||||
ValidatorUpdates: cfg.ValidatorUpdates,
|
||||
PersistInterval: cfg.PersistInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration from disk.
|
||||
func LoadConfig(file string) (*Config, error) {
|
||||
cfg := &Config{
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/server"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
@@ -28,8 +29,8 @@ import (
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
grpcprivval "github.com/tendermint/tendermint/privval/grpc"
|
||||
privvalproto "github.com/tendermint/tendermint/proto/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
||||
"github.com/tendermint/tendermint/test/e2e/app"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,9 @@ var logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, fals
|
||||
|
||||
// main is the binary entrypoint.
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
fmt.Printf("Usage: %v <configfile>", os.Args[0])
|
||||
return
|
||||
@@ -46,14 +50,14 @@ func main() {
|
||||
configFile = os.Args[1]
|
||||
}
|
||||
|
||||
if err := run(configFile); err != nil {
|
||||
if err := run(ctx, configFile); err != nil {
|
||||
logger.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// run runs the application - basically like main() with error handling.
|
||||
func run(configFile string) error {
|
||||
func run(ctx context.Context, configFile string) error {
|
||||
cfg, err := LoadConfig(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,7 +65,7 @@ func run(configFile string) error {
|
||||
|
||||
// Start remote signer (must start before node if running builtin).
|
||||
if cfg.PrivValServer != "" {
|
||||
if err = startSigner(cfg); err != nil {
|
||||
if err = startSigner(ctx, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.Protocol == "builtin" {
|
||||
@@ -72,15 +76,15 @@ func run(configFile string) error {
|
||||
// Start app server.
|
||||
switch cfg.Protocol {
|
||||
case "socket", "grpc":
|
||||
err = startApp(cfg)
|
||||
err = startApp(ctx, cfg)
|
||||
case "builtin":
|
||||
switch cfg.Mode {
|
||||
case string(e2e.ModeLight):
|
||||
err = startLightNode(cfg)
|
||||
err = startLightNode(ctx, cfg)
|
||||
case string(e2e.ModeSeed):
|
||||
err = startSeedNode(cfg)
|
||||
err = startSeedNode(ctx)
|
||||
default:
|
||||
err = startNode(cfg)
|
||||
err = startNode(ctx, cfg)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("invalid protocol %q", cfg.Protocol)
|
||||
@@ -96,16 +100,16 @@ func run(configFile string) error {
|
||||
}
|
||||
|
||||
// startApp starts the application server, listening for connections from Tendermint.
|
||||
func startApp(cfg *Config) error {
|
||||
app, err := NewApplication(cfg)
|
||||
func startApp(ctx context.Context, cfg *Config) error {
|
||||
app, err := app.NewApplication(cfg.App())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server, err := server.NewServer(cfg.Listen, cfg.Protocol, app)
|
||||
server, err := server.NewServer(logger, cfg.Listen, cfg.Protocol, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = server.Start()
|
||||
err = server.Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,8 +121,8 @@ func startApp(cfg *Config) error {
|
||||
// configuration is in $TMHOME/config/tendermint.toml.
|
||||
//
|
||||
// FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper.
|
||||
func startNode(cfg *Config) error {
|
||||
app, err := NewApplication(cfg)
|
||||
func startNode(ctx context.Context, cfg *Config) error {
|
||||
app, err := app.NewApplication(cfg.App())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -128,18 +132,20 @@ func startNode(cfg *Config) error {
|
||||
return fmt.Errorf("failed to setup config: %w", err)
|
||||
}
|
||||
|
||||
n, err := node.New(tmcfg,
|
||||
n, err := node.New(
|
||||
ctx,
|
||||
tmcfg,
|
||||
nodeLogger,
|
||||
proxy.NewLocalClientCreator(app),
|
||||
abciclient.NewLocalCreator(app),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.Start()
|
||||
return n.Start(ctx)
|
||||
}
|
||||
|
||||
func startSeedNode(cfg *Config) error {
|
||||
func startSeedNode(ctx context.Context) error {
|
||||
tmcfg, nodeLogger, err := setupNode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup config: %w", err)
|
||||
@@ -147,14 +153,14 @@ func startSeedNode(cfg *Config) error {
|
||||
|
||||
tmcfg.Mode = config.ModeSeed
|
||||
|
||||
n, err := node.New(tmcfg, nodeLogger, nil, nil)
|
||||
n, err := node.New(ctx, tmcfg, nodeLogger, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.Start()
|
||||
return n.Start(ctx)
|
||||
}
|
||||
|
||||
func startLightNode(cfg *Config) error {
|
||||
func startLightNode(ctx context.Context, cfg *Config) error {
|
||||
tmcfg, nodeLogger, err := setupNode()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -203,7 +209,7 @@ func startLightNode(cfg *Config) error {
|
||||
}
|
||||
|
||||
logger.Info("Starting proxy...", "laddr", tmcfg.RPC.ListenAddress)
|
||||
if err := p.ListenAndServe(); err != http.ErrServerClosed {
|
||||
if err := p.ListenAndServe(ctx); err != http.ErrServerClosed {
|
||||
// Error starting or closing listener:
|
||||
logger.Error("proxy ListenAndServe", "err", err)
|
||||
}
|
||||
@@ -212,7 +218,7 @@ func startLightNode(cfg *Config) error {
|
||||
}
|
||||
|
||||
// startSigner starts a signer server connecting to the given endpoint.
|
||||
func startSigner(cfg *Config) error {
|
||||
func startSigner(ctx context.Context, cfg *Config) error {
|
||||
filePV, err := privval.LoadFilePV(cfg.PrivValKey, cfg.PrivValState)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -250,7 +256,8 @@ func startSigner(cfg *Config) error {
|
||||
endpoint := privval.NewSignerDialerEndpoint(logger, dialFn,
|
||||
privval.SignerDialerEndpointRetryWaitInterval(1*time.Second),
|
||||
privval.SignerDialerEndpointConnRetries(100))
|
||||
err = privval.NewSignerServer(endpoint, cfg.ChainID, filePV).Start()
|
||||
|
||||
err = privval.NewSignerServer(endpoint, cfg.ChainID, filePV).Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
snapshot_interval = 100
|
||||
persist_interval = 1
|
||||
chain_id = "test-chain"
|
||||
protocol = "socket"
|
||||
listen = "tcp://127.0.0.1:26658"
|
||||
+86
-38
@@ -65,6 +65,12 @@ type Manifest struct {
|
||||
|
||||
// Number of bytes per tx. Default is 1kb (1024)
|
||||
TxSize int64
|
||||
|
||||
// ABCIProtocol specifies the protocol used to communicate with the ABCI
|
||||
// application: "unix", "tcp", "grpc", or "builtin". Defaults to builtin.
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// ManifestNode represents a node in a testnet manifest.
|
||||
@@ -87,12 +93,6 @@ type ManifestNode struct {
|
||||
// "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.
|
||||
@@ -104,10 +104,6 @@ type ManifestNode struct {
|
||||
// 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"`
|
||||
|
||||
@@ -145,9 +141,11 @@ type ManifestNode struct {
|
||||
// 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.
|
||||
@@ -170,41 +168,91 @@ func LoadManifest(file string) (Manifest, error) {
|
||||
}
|
||||
|
||||
// SortManifests orders (in-place) a list of manifests such that the
|
||||
// manifests will be ordered (vaguely) from least complex to most
|
||||
// complex.
|
||||
func SortManifests(manifests []Manifest) {
|
||||
// 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 {
|
||||
left, right := manifests[i], manifests[j]
|
||||
|
||||
if len(left.Nodes) < len(right.Nodes) {
|
||||
return true
|
||||
}
|
||||
|
||||
if left.InitialHeight < right.InitialHeight {
|
||||
return true
|
||||
}
|
||||
|
||||
if left.TxSize < right.TxSize {
|
||||
return true
|
||||
}
|
||||
|
||||
if left.Evidence < right.Evidence {
|
||||
return true
|
||||
}
|
||||
|
||||
// sort based on a point-based comparison between two
|
||||
// manifests.
|
||||
var (
|
||||
leftPerturb int
|
||||
rightPerturb int
|
||||
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 {
|
||||
leftPerturb += len(n.Perturb)
|
||||
leftScore += (len(n.Perturb) * 2)
|
||||
|
||||
if n.StartAt > 0 {
|
||||
leftScore += 3
|
||||
}
|
||||
}
|
||||
for _, n := range right.Nodes {
|
||||
rightPerturb += len(n.Perturb)
|
||||
rightScore += (len(n.Perturb) * 2)
|
||||
if n.StartAt > 0 {
|
||||
rightScore += 3
|
||||
}
|
||||
}
|
||||
|
||||
return leftPerturb < rightPerturb
|
||||
// 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
|
||||
}
|
||||
|
||||
+10
-15
@@ -71,6 +71,7 @@ type Testnet struct {
|
||||
Evidence int
|
||||
LogLevel string
|
||||
TxSize int64
|
||||
ABCIProtocol string
|
||||
}
|
||||
|
||||
// Node represents a Tendermint node in a testnet.
|
||||
@@ -83,7 +84,6 @@ type Node struct {
|
||||
IP net.IP
|
||||
ProxyPort uint32
|
||||
StartAt int64
|
||||
BlockSync string
|
||||
Mempool string
|
||||
StateSync string
|
||||
Database string
|
||||
@@ -96,7 +96,6 @@ type Node struct {
|
||||
PersistentPeers []*Node
|
||||
Perturbations []Perturbation
|
||||
LogLevel string
|
||||
UseLegacyP2P bool
|
||||
QueueType string
|
||||
HasStarted bool
|
||||
}
|
||||
@@ -141,6 +140,7 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
KeyType: "ed25519",
|
||||
LogLevel: manifest.LogLevel,
|
||||
TxSize: manifest.TxSize,
|
||||
ABCIProtocol: manifest.ABCIProtocol,
|
||||
}
|
||||
if len(manifest.KeyType) != 0 {
|
||||
testnet.KeyType = manifest.KeyType
|
||||
@@ -151,6 +151,9 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
if manifest.InitialHeight > 0 {
|
||||
testnet.InitialHeight = manifest.InitialHeight
|
||||
}
|
||||
if testnet.ABCIProtocol == "" {
|
||||
testnet.ABCIProtocol = string(ProtocolBuiltin)
|
||||
}
|
||||
|
||||
// Set up nodes, in alphabetical order (IPs and ports get same order).
|
||||
nodeNames := []string{}
|
||||
@@ -170,10 +173,9 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
ProxyPort: proxyPortGen.Next(),
|
||||
Mode: ModeValidator,
|
||||
Database: "goleveldb",
|
||||
ABCIProtocol: ProtocolBuiltin,
|
||||
ABCIProtocol: Protocol(testnet.ABCIProtocol),
|
||||
PrivvalProtocol: ProtocolFile,
|
||||
StartAt: nodeManifest.StartAt,
|
||||
BlockSync: nodeManifest.BlockSync,
|
||||
Mempool: nodeManifest.Mempool,
|
||||
StateSync: nodeManifest.StateSync,
|
||||
PersistInterval: 1,
|
||||
@@ -182,21 +184,19 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
Perturbations: []Perturbation{},
|
||||
LogLevel: manifest.LogLevel,
|
||||
QueueType: manifest.QueueType,
|
||||
UseLegacyP2P: nodeManifest.UseLegacyP2P,
|
||||
}
|
||||
|
||||
if node.StartAt == testnet.InitialHeight {
|
||||
node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this
|
||||
}
|
||||
if nodeManifest.Mode != "" {
|
||||
node.Mode = Mode(nodeManifest.Mode)
|
||||
}
|
||||
if node.Mode == ModeLight {
|
||||
node.ABCIProtocol = ProtocolBuiltin
|
||||
}
|
||||
if nodeManifest.Database != "" {
|
||||
node.Database = nodeManifest.Database
|
||||
}
|
||||
if nodeManifest.ABCIProtocol != "" {
|
||||
node.ABCIProtocol = Protocol(nodeManifest.ABCIProtocol)
|
||||
}
|
||||
if nodeManifest.PrivvalProtocol != "" {
|
||||
node.PrivvalProtocol = Protocol(nodeManifest.PrivvalProtocol)
|
||||
}
|
||||
@@ -333,11 +333,6 @@ func (n Node) Validate(testnet Testnet) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
switch n.BlockSync {
|
||||
case "", "v0", "v2":
|
||||
default:
|
||||
return fmt.Errorf("invalid block sync setting %q", n.BlockSync)
|
||||
}
|
||||
switch n.StateSync {
|
||||
case StateSyncDisabled, StateSyncP2P, StateSyncRPC:
|
||||
default:
|
||||
@@ -349,7 +344,7 @@ func (n Node) Validate(testnet Testnet) error {
|
||||
return fmt.Errorf("invalid mempool version %q", n.Mempool)
|
||||
}
|
||||
switch n.QueueType {
|
||||
case "", "priority", "wdrr", "fifo":
|
||||
case "", "priority", "fifo":
|
||||
default:
|
||||
return fmt.Errorf("unsupported p2p queue type: %s", n.QueueType)
|
||||
}
|
||||
|
||||
+23
-28
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -28,19 +28,15 @@ const lightClientEvidenceRatio = 4
|
||||
// evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`.
|
||||
// Evidence is random and can be a mixture of LightClientAttackEvidence and
|
||||
// DuplicateVoteEvidence.
|
||||
func InjectEvidence(ctx context.Context, testnet *e2e.Testnet, amount int) error {
|
||||
func InjectEvidence(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet, amount int) error {
|
||||
// select a random node
|
||||
var targetNode *e2e.Node
|
||||
|
||||
for _, idx := range rand.Perm(len(testnet.Nodes)) {
|
||||
targetNode = testnet.Nodes[idx]
|
||||
|
||||
if targetNode.Mode == e2e.ModeSeed {
|
||||
targetNode = nil
|
||||
continue
|
||||
for _, idx := range r.Perm(len(testnet.Nodes)) {
|
||||
if !testnet.Nodes[idx].Stateless() {
|
||||
targetNode = testnet.Nodes[idx]
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if targetNode == nil {
|
||||
@@ -55,15 +51,14 @@ func InjectEvidence(ctx context.Context, testnet *e2e.Testnet, amount int) error
|
||||
}
|
||||
|
||||
// request the latest block and validator set from the node
|
||||
blockRes, err := client.Block(context.Background(), nil)
|
||||
blockRes, err := client.Block(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evidenceHeight := blockRes.Block.Height
|
||||
waitHeight := blockRes.Block.Height + 3
|
||||
evidenceHeight := blockRes.Block.Height - 3
|
||||
|
||||
nValidators := 100
|
||||
valRes, err := client.Validators(context.Background(), &evidenceHeight, nil, &nValidators)
|
||||
valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,12 +74,8 @@ func InjectEvidence(ctx context.Context, testnet *e2e.Testnet, amount int) error
|
||||
return err
|
||||
}
|
||||
|
||||
wctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// wait for the node to reach the height above the forged height so that
|
||||
// it is able to validate the evidence
|
||||
_, err = waitForNode(wctx, targetNode, waitHeight)
|
||||
// request the latest block and validator set from the node
|
||||
blockRes, err = client.Block(ctx, &evidenceHeight)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -104,24 +95,28 @@ func InjectEvidence(ctx context.Context, testnet *e2e.Testnet, amount int) error
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := client.BroadcastEvidence(context.Background(), ev)
|
||||
_, err := client.BroadcastEvidence(ctx, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wctx, cancel = context.WithTimeout(ctx, 30*time.Second)
|
||||
logger.Info("Finished sending evidence",
|
||||
"node", testnet.Name,
|
||||
"amount", amount,
|
||||
"height", evidenceHeight,
|
||||
)
|
||||
|
||||
wctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// wait for the node to reach the height above the forged height so that
|
||||
// it is able to validate the evidence
|
||||
_, err = waitForNode(wctx, targetNode, blockRes.Block.Height+2)
|
||||
// wait for the node to make progress after submitting
|
||||
// evidence (3 (forged height) + 1 (progress))
|
||||
_, err = waitForNode(wctx, targetNode, evidenceHeight+4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Finished sending evidence (height %d)", blockRes.Block.Height+2))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -237,7 +232,7 @@ func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet)
|
||||
}
|
||||
|
||||
func readPrivKey(keyFilePath string) (crypto.PrivKey, error) {
|
||||
keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
|
||||
keyJSONBytes, err := os.ReadFile(keyFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+24
-31
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"container/ring"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
@@ -15,15 +14,15 @@ import (
|
||||
|
||||
// Load generates transactions against the network until the given context is
|
||||
// canceled.
|
||||
func Load(ctx context.Context, testnet *e2e.Testnet) error {
|
||||
func Load(ctx context.Context, r *rand.Rand, testnet *e2e.Testnet) error {
|
||||
// Since transactions are executed across all nodes in the network, we need
|
||||
// to reduce transaction load for larger networks to avoid using too much
|
||||
// CPU. This gives high-throughput small networks and low-throughput large ones.
|
||||
// This also limits the number of TCP connections, since each worker has
|
||||
// a connection to all nodes.
|
||||
concurrency := 64 / len(testnet.Nodes)
|
||||
if concurrency == 0 {
|
||||
concurrency = 1
|
||||
concurrency := len(testnet.Nodes) * 2
|
||||
if concurrency > 32 {
|
||||
concurrency = 32
|
||||
}
|
||||
|
||||
chTx := make(chan types.Tx)
|
||||
@@ -32,10 +31,14 @@ func Load(ctx context.Context, testnet *e2e.Testnet) error {
|
||||
defer cancel()
|
||||
|
||||
// Spawn job generator and processors.
|
||||
logger.Info(fmt.Sprintf("Starting transaction load (%v workers)...", concurrency))
|
||||
logger.Info("starting transaction load",
|
||||
"workers", concurrency,
|
||||
"nodes", len(testnet.Nodes),
|
||||
"tx", testnet.TxSize)
|
||||
|
||||
started := time.Now()
|
||||
|
||||
go loadGenerate(ctx, chTx, testnet.TxSize)
|
||||
go loadGenerate(ctx, r, chTx, testnet.TxSize, len(testnet.Nodes))
|
||||
|
||||
for w := 0; w < concurrency; w++ {
|
||||
go loadProcess(ctx, testnet, chTx, chSuccess)
|
||||
@@ -54,19 +57,9 @@ func Load(ctx context.Context, testnet *e2e.Testnet) error {
|
||||
case numSeen := <-chSuccess:
|
||||
success += numSeen
|
||||
case <-ctx.Done():
|
||||
// if we couldn't submit any transactions,
|
||||
// that's probably a problem and the test
|
||||
// should error; however, for very short tests
|
||||
// we shouldn't abort.
|
||||
//
|
||||
// The 2s cut off, is a rough guess based on
|
||||
// the expected value of
|
||||
// loadGenerateWaitTime. If the implementation
|
||||
// of that function changes, then this might
|
||||
// also need to change without more
|
||||
// refactoring.
|
||||
if success == 0 && time.Since(started) > 2*time.Second {
|
||||
return errors.New("failed to submit any transactions")
|
||||
if success == 0 {
|
||||
return fmt.Errorf("failed to submit transactions in %s by %d workers",
|
||||
time.Since(started), concurrency)
|
||||
}
|
||||
|
||||
// TODO perhaps allow test networks to
|
||||
@@ -78,8 +71,8 @@ func Load(ctx context.Context, testnet *e2e.Testnet) error {
|
||||
logger.Info("ending transaction load",
|
||||
"dur_secs", time.Since(started).Seconds(),
|
||||
"txns", success,
|
||||
"rate", rate,
|
||||
"slow", rate < 1)
|
||||
"workers", concurrency,
|
||||
"rate", rate)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -92,7 +85,7 @@ func Load(ctx context.Context, testnet *e2e.Testnet) error {
|
||||
// generation is primarily the result of backpressure from the
|
||||
// broadcast transaction, though there is still some timer-based
|
||||
// limiting.
|
||||
func loadGenerate(ctx context.Context, chTx chan<- types.Tx, size int64) {
|
||||
func loadGenerate(ctx context.Context, r *rand.Rand, chTx chan<- types.Tx, txSize int64, networkSize int) {
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
defer close(chTx)
|
||||
@@ -108,8 +101,8 @@ func loadGenerate(ctx context.Context, chTx chan<- types.Tx, size int64) {
|
||||
// This gives a reasonable load without putting too much data in the app.
|
||||
id := rand.Int63() % 100 // nolint: gosec
|
||||
|
||||
bz := make([]byte, size)
|
||||
_, err := rand.Read(bz) // nolint: gosec
|
||||
bz := make([]byte, txSize)
|
||||
_, err := r.Read(bz)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to read random bytes: %v", err))
|
||||
}
|
||||
@@ -121,22 +114,22 @@ func loadGenerate(ctx context.Context, chTx chan<- types.Tx, size int64) {
|
||||
case chTx <- tx:
|
||||
// sleep for a bit before sending the
|
||||
// next transaction.
|
||||
timer.Reset(loadGenerateWaitTime(size))
|
||||
timer.Reset(loadGenerateWaitTime(r, networkSize))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func loadGenerateWaitTime(size int64) time.Duration {
|
||||
func loadGenerateWaitTime(r *rand.Rand, size int) time.Duration {
|
||||
const (
|
||||
min = int64(100 * time.Millisecond)
|
||||
min = int64(250 * time.Millisecond)
|
||||
max = int64(time.Second)
|
||||
)
|
||||
|
||||
var (
|
||||
baseJitter = rand.Int63n(max-min+1) + min // nolint: gosec
|
||||
sizeFactor = size * int64(time.Millisecond)
|
||||
sizeJitter = rand.Int63n(sizeFactor-min+1) + min // nolint: gosec
|
||||
baseJitter = r.Int63n(max-min+1) + min
|
||||
sizeFactor = int64(size) * min
|
||||
sizeJitter = r.Int63n(sizeFactor-min+1) + min
|
||||
)
|
||||
|
||||
return time.Duration(baseJitter + sizeJitter)
|
||||
|
||||
+63
-28
@@ -3,8 +3,10 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -12,9 +14,9 @@ import (
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
|
||||
)
|
||||
const randomSeed = 2308084734268
|
||||
|
||||
var logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
|
||||
|
||||
func main() {
|
||||
NewCLI().Run()
|
||||
@@ -48,14 +50,26 @@ func NewCLI() *CLI {
|
||||
cli.testnet = testnet
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
if err = Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Setup(cli.testnet); err != nil {
|
||||
defer func() {
|
||||
if cli.preserve {
|
||||
logger.Info("Preserving testnet contents because -preserve=true")
|
||||
} else if err != nil {
|
||||
logger.Info("Preserving testnet that encountered error",
|
||||
"err", err)
|
||||
} else if err := Cleanup(cli.testnet); err != nil {
|
||||
logger.Error("Error cleaning up testnet contents", "err", err)
|
||||
}
|
||||
}()
|
||||
if err = Setup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec
|
||||
|
||||
chLoadResult := make(chan error)
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
@@ -63,51 +77,59 @@ func NewCLI() *CLI {
|
||||
lctx, loadCancel := context.WithCancel(ctx)
|
||||
defer loadCancel()
|
||||
go func() {
|
||||
chLoadResult <- Load(lctx, cli.testnet)
|
||||
chLoadResult <- Load(lctx, r, cli.testnet)
|
||||
}()
|
||||
|
||||
if err := Start(ctx, cli.testnet); err != nil {
|
||||
startAt := time.Now()
|
||||
if err = Start(ctx, cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
|
||||
if err = Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
|
||||
return err
|
||||
}
|
||||
|
||||
if cli.testnet.HasPerturbations() {
|
||||
if err := Perturb(ctx, cli.testnet); err != nil {
|
||||
if err = Perturb(ctx, cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
|
||||
if err = Wait(ctx, cli.testnet, 5); err != nil { // allow some txs to go through
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cli.testnet.Evidence > 0 {
|
||||
if err := InjectEvidence(ctx, cli.testnet, cli.testnet.Evidence); err != nil {
|
||||
if err = InjectEvidence(ctx, r, cli.testnet, cli.testnet.Evidence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Wait(ctx, cli.testnet, 5); err != nil { // ensure chain progress
|
||||
if err = Wait(ctx, cli.testnet, 5); err != nil { // ensure chain progress
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// to help make sure that we don't run into
|
||||
// situations where 0 transactions have
|
||||
// happened on quick cases, we make sure that
|
||||
// it's been at least 10s before canceling the
|
||||
// load generator.
|
||||
//
|
||||
// TODO allow the load generator to report
|
||||
// successful transactions to avoid needing
|
||||
// this sleep.
|
||||
if rest := time.Since(startAt); rest < 15*time.Second {
|
||||
time.Sleep(15*time.Second - rest)
|
||||
}
|
||||
|
||||
loadCancel()
|
||||
|
||||
if err := <-chLoadResult; err != nil {
|
||||
if err = <-chLoadResult; err != nil {
|
||||
return fmt.Errorf("transaction load failed: %w", err)
|
||||
}
|
||||
if err := Wait(ctx, cli.testnet, 5); err != nil { // wait for network to settle before tests
|
||||
if err = Wait(ctx, cli.testnet, 5); err != nil { // wait for network to settle before tests
|
||||
return err
|
||||
}
|
||||
if err := Test(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
if !cli.preserve {
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -193,7 +215,11 @@ func NewCLI() *CLI {
|
||||
Use: "load",
|
||||
Short: "Generates transaction load until the command is canceled",
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
return Load(context.Background(), cli.testnet)
|
||||
return Load(
|
||||
cmd.Context(),
|
||||
rand.New(rand.NewSource(randomSeed)), // nolint: gosec
|
||||
cli.testnet,
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -211,7 +237,12 @@ func NewCLI() *CLI {
|
||||
}
|
||||
}
|
||||
|
||||
return InjectEvidence(cmd.Context(), cli.testnet, amount)
|
||||
return InjectEvidence(
|
||||
cmd.Context(),
|
||||
rand.New(rand.NewSource(randomSeed)), // nolint: gosec
|
||||
cli.testnet,
|
||||
amount,
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -269,6 +300,12 @@ Does not run any perbutations.
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
logger.Error("Error cleaning up testnet contents", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := Setup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -277,10 +314,12 @@ Does not run any perbutations.
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
defer cancel()
|
||||
|
||||
r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec
|
||||
|
||||
lctx, loadCancel := context.WithCancel(ctx)
|
||||
defer loadCancel()
|
||||
go func() {
|
||||
err := Load(lctx, cli.testnet)
|
||||
err := Load(lctx, r, cli.testnet)
|
||||
chLoadResult <- err
|
||||
}()
|
||||
|
||||
@@ -302,10 +341,6 @@ Does not run any perbutations.
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/coretypes"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/coretypes"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
@@ -70,9 +70,7 @@ func waitForHeight(ctx context.Context, testnet *e2e.Testnet, height int64) (*ty
|
||||
clients[node.Name] = client
|
||||
}
|
||||
|
||||
wctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
result, err := client.Status(wctx)
|
||||
result, err := client.Status(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -171,18 +169,18 @@ func waitForNode(ctx context.Context, node *e2e.Node, height int64) (*rpctypes.R
|
||||
return nil, err
|
||||
case err == nil && status.SyncInfo.LatestBlockHeight >= height:
|
||||
return status, nil
|
||||
case counter%100 == 0:
|
||||
case counter%500 == 0:
|
||||
switch {
|
||||
case err != nil:
|
||||
lastFailed = true
|
||||
logger.Error("node not yet ready",
|
||||
"iter", counter,
|
||||
"node", node.Name,
|
||||
"err", err,
|
||||
"target", height,
|
||||
"err", err,
|
||||
)
|
||||
case status != nil:
|
||||
logger.Error("node not yet ready",
|
||||
logger.Info("node not yet ready",
|
||||
"iter", counter,
|
||||
"node", node.Name,
|
||||
"height", status.SyncInfo.LatestBlockHeight,
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -51,7 +50,7 @@ func Setup(testnet *e2e.Testnet) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0644)
|
||||
err = os.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -84,13 +83,15 @@ func Setup(testnet *e2e.Testnet) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.WriteConfigFile(nodeDir, cfg) // panics
|
||||
if err := config.WriteConfigFile(nodeDir, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appCfg, err := MakeAppConfig(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644)
|
||||
err = os.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,8 +238,6 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) {
|
||||
cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
|
||||
cfg.RPC.PprofListenAddress = ":6060"
|
||||
cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
|
||||
cfg.P2P.AddrBookStrict = false
|
||||
cfg.P2P.UseLegacy = node.UseLegacyP2P
|
||||
cfg.P2P.QueueType = node.QueueType
|
||||
cfg.DBBackend = node.Database
|
||||
cfg.StateSync.DiscoveryTime = 5 * time.Second
|
||||
@@ -292,16 +291,6 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) {
|
||||
return nil, fmt.Errorf("unexpected mode %q", node.Mode)
|
||||
}
|
||||
|
||||
if node.Mempool != "" {
|
||||
cfg.Mempool.Version = node.Mempool
|
||||
}
|
||||
|
||||
if node.BlockSync == "" {
|
||||
cfg.BlockSync.Enable = false
|
||||
} else {
|
||||
cfg.BlockSync.Version = node.BlockSync
|
||||
}
|
||||
|
||||
switch node.StateSync {
|
||||
case e2e.StateSyncP2P:
|
||||
cfg.StateSync.Enable = true
|
||||
@@ -355,7 +344,6 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) {
|
||||
"snapshot_interval": node.SnapshotInterval,
|
||||
"retain_blocks": node.RetainBlocks,
|
||||
"key_type": node.PrivvalKey.Type(),
|
||||
"use_legacy_p2p": node.UseLegacyP2P,
|
||||
}
|
||||
switch node.ABCIProtocol {
|
||||
case e2e.ProtocolUNIX:
|
||||
@@ -417,11 +405,11 @@ func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error {
|
||||
|
||||
// FIXME Apparently there's no function to simply load a config file without
|
||||
// involving the entire Viper apparatus, so we'll just resort to regexps.
|
||||
bz, err := ioutil.ReadFile(cfgPath)
|
||||
bz, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bz = regexp.MustCompile(`(?m)^trust-height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust-height = %v`, height)))
|
||||
bz = regexp.MustCompile(`(?m)^trust-hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust-hash = "%X"`, hash)))
|
||||
return ioutil.WriteFile(cfgPath, bz, 0644)
|
||||
return os.WriteFile(cfgPath, bz, 0644)
|
||||
}
|
||||
|
||||
@@ -44,15 +44,19 @@ func TestApp_Hash(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, info.Response.LastBlockAppHash, "expected app to return app hash")
|
||||
|
||||
block, err := client.Block(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, info.Response.LastBlockAppHash, block.Block.AppHash.Bytes(),
|
||||
"app hash does not match last block's app hash")
|
||||
|
||||
status, err := client.Status(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, info.Response.LastBlockAppHash, status.SyncInfo.LatestAppHash,
|
||||
"app hash does not match node status")
|
||||
|
||||
block, err := client.Block(ctx, &info.Response.LastBlockHeight)
|
||||
require.NoError(t, err)
|
||||
|
||||
if info.Response.LastBlockHeight == block.Block.Height {
|
||||
require.EqualValues(t, info.Response.LastBlockAppHash, block.Block.AppHash.Bytes(),
|
||||
"app hash does not match last block's app hash")
|
||||
}
|
||||
|
||||
require.True(t, status.SyncInfo.LatestBlockHeight >= info.Response.LastBlockHeight,
|
||||
"status out of sync with application")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
rpctypes "github.com/tendermint/tendermint/rpc/coretypes"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -20,6 +20,14 @@ func TestValidator_Sets(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
first := status.SyncInfo.EarliestBlockHeight
|
||||
|
||||
// for nodes that have to catch up, we should only
|
||||
// check the validator sets for nodes after this
|
||||
// point, to avoid inconsistencies with backfill.
|
||||
if node.StartAt > first {
|
||||
first = node.StartAt
|
||||
}
|
||||
|
||||
last := status.SyncInfo.LatestBlockHeight
|
||||
|
||||
// skip first block if node is pruning blocks, to avoid race conditions
|
||||
|
||||
+4
-27
@@ -1,38 +1,15 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
.PHONY: fuzz-mempool-v1
|
||||
fuzz-mempool-v1:
|
||||
cd mempool/v1 && \
|
||||
.PHONY: fuzz-mempool
|
||||
fuzz-mempool:
|
||||
cd mempool && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-mempool-v0
|
||||
fuzz-mempool-v0:
|
||||
cd mempool/v0 && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-p2p-addrbook
|
||||
fuzz-p2p-addrbook:
|
||||
cd p2p/addrbook && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go run ./init-corpus/main.go && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-p2p-pex
|
||||
fuzz-p2p-pex:
|
||||
cd p2p/pex && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go run ./init-corpus/main.go && \
|
||||
go-fuzz-build && \
|
||||
go-fuzz
|
||||
|
||||
.PHONY: fuzz-p2p-sc
|
||||
fuzz-p2p-sc:
|
||||
cd p2p/secret_connection && \
|
||||
cd p2p/secretconnection && \
|
||||
rm -f *-fuzz.zip && \
|
||||
go run ./init-corpus/main.go && \
|
||||
go-fuzz-build && \
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package mempool
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
abciclient "github.com/tendermint/tendermint/abci/client"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
var mp *mempool.TxMempool
|
||||
var getMp func() mempool.Mempool
|
||||
|
||||
func init() {
|
||||
app := kvstore.NewApplication()
|
||||
cc := abciclient.NewLocalCreator(app)
|
||||
appConnMem, _ := cc(log.NewNopLogger())
|
||||
err := appConnMem.Start(context.TODO())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg := config.DefaultMempoolConfig()
|
||||
cfg.Broadcast = false
|
||||
|
||||
getMp = func() mempool.Mempool {
|
||||
if mp == nil {
|
||||
mp = mempool.NewTxMempool(
|
||||
log.NewNopLogger(),
|
||||
cfg,
|
||||
appConnMem,
|
||||
0,
|
||||
)
|
||||
|
||||
}
|
||||
return mp
|
||||
}
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package v1_test
|
||||
package mempool_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
mempoolv1 "github.com/tendermint/tendermint/test/fuzz/mempool/v1"
|
||||
mempool "github.com/tendermint/tendermint/test/fuzz/mempool"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
@@ -25,9 +25,9 @@ func TestMempoolTestdataCases(t *testing.T) {
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
mempoolv1.Fuzz(input)
|
||||
mempool.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package v0
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
mempoolv0 "github.com/tendermint/tendermint/internal/mempool/v0"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
)
|
||||
|
||||
var mp mempool.Mempool
|
||||
|
||||
func init() {
|
||||
app := kvstore.NewApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
appConnMem, _ := cc.NewABCIClient()
|
||||
err := appConnMem.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg := config.DefaultMempoolConfig()
|
||||
cfg.Broadcast = false
|
||||
|
||||
mp = mempoolv0.NewCListMempool(cfg, appConnMem, 0)
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
err := mp.CheckTx(context.Background(), data, nil, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package v0_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
mempoolv0 "github.com/tendermint/tendermint/test/fuzz/mempool/v0"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestMempoolTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, e := range entries {
|
||||
entry := e
|
||||
t.Run(entry.Name(), func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
require.Nilf(t, r, "testdata/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
mempoolv0.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/internal/mempool"
|
||||
mempoolv1 "github.com/tendermint/tendermint/internal/mempool/v0"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
)
|
||||
|
||||
var mp mempool.Mempool
|
||||
|
||||
func init() {
|
||||
app := kvstore.NewApplication()
|
||||
cc := proxy.NewLocalClientCreator(app)
|
||||
appConnMem, _ := cc.NewABCIClient()
|
||||
err := appConnMem.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg := config.DefaultMempoolConfig()
|
||||
cfg.Broadcast = false
|
||||
|
||||
mp = mempoolv1.NewCListMempool(cfg, appConnMem, 0)
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
err := mp.CheckTx(context.Background(), data, nil, mempool.TxInfo{})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// nolint: gosec
|
||||
package addrbook
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/p2p/pex"
|
||||
)
|
||||
|
||||
var addrBook = pex.NewAddrBook("./testdata/addrbook.json", true)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
addr := new(p2p.NetAddress)
|
||||
if err := json.Unmarshal(data, addr); err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Fuzz AddAddress.
|
||||
err := addrBook.AddAddress(addr, addr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Also, make sure PickAddress always returns a non-nil address.
|
||||
bias := rand.Intn(100)
|
||||
if p := addrBook.PickAddress(bias); p == nil {
|
||||
panic(fmt.Sprintf("picked a nil address (bias: %d, addrBook size: %v)",
|
||||
bias, addrBook.Size()))
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package addrbook_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/test/fuzz/p2p/addrbook"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestAddrbookTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, e := range entries {
|
||||
entry := e
|
||||
t.Run(entry.Name(), func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
require.Nilf(t, r, "testdata/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
addrbook.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// nolint: gosec
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
baseDir := flag.String("base", ".", `where the "corpus" directory will live`)
|
||||
flag.Parse()
|
||||
|
||||
initCorpus(*baseDir)
|
||||
}
|
||||
|
||||
func initCorpus(baseDir string) {
|
||||
log.SetFlags(0)
|
||||
|
||||
// create "corpus" directory
|
||||
corpusDir := filepath.Join(baseDir, "corpus")
|
||||
if err := os.MkdirAll(corpusDir, 0755); err != nil {
|
||||
log.Fatalf("Creating %q err: %v", corpusDir, err)
|
||||
}
|
||||
|
||||
// create corpus
|
||||
privKey := ed25519.GenPrivKey()
|
||||
addrs := []*p2p.NetAddress{
|
||||
{ID: types.NodeIDFromPubKey(privKey.PubKey()), IP: net.IPv4(0, 0, 0, 0), Port: 0},
|
||||
{ID: types.NodeIDFromPubKey(privKey.PubKey()), IP: net.IPv4(127, 0, 0, 0), Port: 80},
|
||||
{ID: types.NodeIDFromPubKey(privKey.PubKey()), IP: net.IPv4(213, 87, 10, 200), Port: 8808},
|
||||
{ID: types.NodeIDFromPubKey(privKey.PubKey()), IP: net.IPv4(111, 111, 111, 111), Port: 26656},
|
||||
{ID: types.NodeIDFromPubKey(privKey.PubKey()), IP: net.ParseIP("2001:db8::68"), Port: 26656},
|
||||
}
|
||||
|
||||
for i, addr := range addrs {
|
||||
filename := filepath.Join(corpusDir, fmt.Sprintf("%d.json", i))
|
||||
|
||||
bz, err := json.Marshal(addr)
|
||||
if err != nil {
|
||||
log.Fatalf("can't marshal %v: %v", addr, err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, bz, 0644); err != nil {
|
||||
log.Fatalf("can't write %v to %q: %v", addr, filename, err)
|
||||
}
|
||||
|
||||
log.Printf("wrote %q", filename)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package pex_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/test/fuzz/p2p/pex"
|
||||
)
|
||||
|
||||
const testdataCasesDir = "testdata/cases"
|
||||
|
||||
func TestPexTestdataCases(t *testing.T) {
|
||||
entries, err := os.ReadDir(testdataCasesDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, e := range entries {
|
||||
entry := e
|
||||
t.Run(entry.Name(), func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
require.Nilf(t, r, "testdata/cases test panic")
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
pex.Fuzz(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// nolint: gosec
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/p2p/pex"
|
||||
tmp2p "github.com/tendermint/tendermint/proto/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
baseDir := flag.String("base", ".", `where the "corpus" directory will live`)
|
||||
flag.Parse()
|
||||
|
||||
initCorpus(*baseDir)
|
||||
}
|
||||
|
||||
func initCorpus(rootDir string) {
|
||||
log.SetFlags(0)
|
||||
|
||||
corpusDir := filepath.Join(rootDir, "corpus")
|
||||
if err := os.MkdirAll(corpusDir, 0755); err != nil {
|
||||
log.Fatalf("Creating %q err: %v", corpusDir, err)
|
||||
}
|
||||
sizes := []int{0, 1, 2, 17, 5, 31}
|
||||
|
||||
// Make the PRNG predictable
|
||||
rand.Seed(10)
|
||||
|
||||
for _, n := range sizes {
|
||||
var addrs []*p2p.NetAddress
|
||||
|
||||
// IPv4 addresses
|
||||
for i := 0; i < n; i++ {
|
||||
privKey := ed25519.GenPrivKey()
|
||||
addr := fmt.Sprintf(
|
||||
"%s@%v.%v.%v.%v:26656",
|
||||
types.NodeIDFromPubKey(privKey.PubKey()),
|
||||
rand.Int()%256,
|
||||
rand.Int()%256,
|
||||
rand.Int()%256,
|
||||
rand.Int()%256,
|
||||
)
|
||||
netAddr, _ := types.NewNetAddressString(addr)
|
||||
addrs = append(addrs, netAddr)
|
||||
}
|
||||
|
||||
// IPv6 addresses
|
||||
privKey := ed25519.GenPrivKey()
|
||||
ipv6a, err := types.NewNetAddressString(
|
||||
fmt.Sprintf("%s@[ff02::1:114]:26656", types.NodeIDFromPubKey(privKey.PubKey())))
|
||||
if err != nil {
|
||||
log.Fatalf("can't create a new netaddress: %v", err)
|
||||
}
|
||||
addrs = append(addrs, ipv6a)
|
||||
|
||||
msg := tmp2p.PexMessage{
|
||||
Sum: &tmp2p.PexMessage_PexResponse{
|
||||
PexResponse: &tmp2p.PexResponse{Addresses: pex.NetAddressesToProto(addrs)},
|
||||
},
|
||||
}
|
||||
bz, err := msg.Marshal()
|
||||
if err != nil {
|
||||
log.Fatalf("unable to marshal: %v", err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(rootDir, "corpus", fmt.Sprintf("%d", n))
|
||||
|
||||
if err := ioutil.WriteFile(filename, bz, 0644); err != nil {
|
||||
log.Fatalf("can't write %X to %q: %v", bz, filename, err)
|
||||
}
|
||||
|
||||
log.Printf("wrote %q", filename)
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package pex
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/internal/p2p"
|
||||
"github.com/tendermint/tendermint/internal/p2p/pex"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/libs/service"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
var (
|
||||
pexR *pex.Reactor
|
||||
peer p2p.Peer
|
||||
logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
|
||||
)
|
||||
|
||||
func init() {
|
||||
addrB := pex.NewAddrBook("./testdata/addrbook1", false)
|
||||
pexR = pex.NewReactor(addrB, &pex.ReactorConfig{SeedMode: false})
|
||||
pexR.SetLogger(logger)
|
||||
peer = newFuzzPeer()
|
||||
pexR.AddPeer(peer)
|
||||
|
||||
cfg := config.DefaultP2PConfig()
|
||||
cfg.PexReactor = true
|
||||
sw := p2p.MakeSwitch(cfg, 0, "127.0.0.1", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||
return sw
|
||||
}, logger)
|
||||
pexR.SetSwitch(sw)
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
if len(data) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
pexR.Receive(pex.PexChannel, peer, data)
|
||||
|
||||
if !peer.IsRunning() {
|
||||
// do not increase priority for msgs which lead to peer being stopped
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
type fuzzPeer struct {
|
||||
*service.BaseService
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
var _ p2p.Peer = (*fuzzPeer)(nil)
|
||||
|
||||
func newFuzzPeer() *fuzzPeer {
|
||||
fp := &fuzzPeer{m: make(map[string]interface{})}
|
||||
fp.BaseService = service.NewBaseService(nil, "fuzzPeer", fp)
|
||||
return fp
|
||||
}
|
||||
|
||||
var privKey = ed25519.GenPrivKey()
|
||||
var nodeID = types.NodeIDFromPubKey(privKey.PubKey())
|
||||
var defaultNodeInfo = types.NodeInfo{
|
||||
ProtocolVersion: types.ProtocolVersion{
|
||||
P2P: version.P2PProtocol,
|
||||
Block: version.BlockProtocol,
|
||||
App: 0,
|
||||
},
|
||||
NodeID: nodeID,
|
||||
ListenAddr: "127.0.0.1:0",
|
||||
Moniker: "foo1",
|
||||
}
|
||||
|
||||
func (fp *fuzzPeer) FlushStop() {}
|
||||
func (fp *fuzzPeer) ID() types.NodeID { return nodeID }
|
||||
func (fp *fuzzPeer) RemoteIP() net.IP { return net.IPv4(198, 163, 190, 214) }
|
||||
func (fp *fuzzPeer) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{IP: fp.RemoteIP(), Port: 26656, Zone: ""}
|
||||
}
|
||||
func (fp *fuzzPeer) IsOutbound() bool { return false }
|
||||
func (fp *fuzzPeer) IsPersistent() bool { return false }
|
||||
func (fp *fuzzPeer) CloseConn() error { return nil }
|
||||
func (fp *fuzzPeer) NodeInfo() types.NodeInfo { return defaultNodeInfo }
|
||||
func (fp *fuzzPeer) Status() p2p.ConnectionStatus { var cs p2p.ConnectionStatus; return cs }
|
||||
func (fp *fuzzPeer) SocketAddr() *p2p.NetAddress {
|
||||
return types.NewNetAddress(fp.ID(), fp.RemoteAddr())
|
||||
}
|
||||
func (fp *fuzzPeer) Send(byte, []byte) bool { return true }
|
||||
func (fp *fuzzPeer) TrySend(byte, []byte) bool { return true }
|
||||
func (fp *fuzzPeer) Set(key string, value interface{}) { fp.m[key] = value }
|
||||
func (fp *fuzzPeer) Get(key string) interface{} { return fp.m[key] }
|
||||
Vendored
-1705
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
package secretconnection_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -25,7 +25,7 @@ func TestSecretConnectionTestdataCases(t *testing.T) {
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
secretconnection.Fuzz(input)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -39,7 +38,7 @@ func initCorpus(baseDir string) {
|
||||
for i, datum := range data {
|
||||
filename := filepath.Join(corpusDir, fmt.Sprintf("%d", i))
|
||||
|
||||
if err := ioutil.WriteFile(filename, []byte(datum), 0644); err != nil {
|
||||
if err := os.WriteFile(filename, []byte(datum), 0644); err != nil {
|
||||
log.Fatalf("can't write %v to %q: %v", datum, filename, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -25,7 +25,7 @@ func TestServerTestdataCases(t *testing.T) {
|
||||
}()
|
||||
f, err := os.Open(filepath.Join(testdataCasesDir, entry.Name()))
|
||||
require.NoError(t, err)
|
||||
input, err := ioutil.ReadAll(f)
|
||||
input, err := io.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
server.Fuzz(input)
|
||||
})
|
||||
|
||||
@@ -3,13 +3,13 @@ package server
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
rs "github.com/tendermint/tendermint/rpc/jsonrpc/server"
|
||||
types "github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
||||
"github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
||||
)
|
||||
|
||||
var rpcFuncMap = map[string]*rs.RPCFunc{
|
||||
@@ -32,7 +32,7 @@ func Fuzz(data []byte) int {
|
||||
rec := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rec, req)
|
||||
res := rec.Result()
|
||||
blob, err := ioutil.ReadAll(res.Body)
|
||||
blob, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ set -e
|
||||
|
||||
echo "mode: atomic" > coverage.txt
|
||||
for pkg in ${PKGS[@]}; do
|
||||
go test -timeout 5m -race -coverprofile=profile.out -covermode=atomic "$pkg"
|
||||
go test -timeout 5m -race -coverprofile=profile.out "$pkg"
|
||||
if [ -f profile.out ]; then
|
||||
tail -n +2 profile.out >> coverage.txt;
|
||||
rm profile.out
|
||||
|
||||
Reference in New Issue
Block a user