Merge remote-tracking branch 'origin/master' into wb/proposer-based-timestamps

This commit is contained in:
William Banfield
2021-11-24 11:54:43 -05:00
583 changed files with 16589 additions and 48821 deletions
-42
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+29 -6
View File
@@ -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
}
+3 -4
View File
@@ -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)
}
+1 -1
View File
@@ -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.
+74 -91
View File
@@ -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,
}
+49
View File
@@ -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
View File
@@ -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
}
+9 -3
View File
@@ -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 {
+28
View File
@@ -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 -1
View File
@@ -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"
+4
View File
@@ -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{
+31 -24
View File
@@ -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
}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
},
})
+1 -1
View File
@@ -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"
)
+5 -7
View File
@@ -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 -19
View File
@@ -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)
}
+11 -7
View File
@@ -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")
})
}
+1 -1
View File
@@ -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"
)
+8
View File
@@ -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
View File
@@ -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 && \
+49
View File
@@ -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)
})
}
}
-37
View File
@@ -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
}
-33
View File
@@ -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)
})
}
}
-37
View File
@@ -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
}
View File
-35
View File
@@ -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
}
-33
View File
@@ -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)
}
}
View File
-33
View File
@@ -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)
})
}
}
-84
View File
@@ -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)
}
}
-95
View File
@@ -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] }
File diff suppressed because it is too large Load Diff
View File
+2 -2
View File
@@ -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)
}
+2 -2
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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