mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 05:46:32 +00:00
Merge branch 'feature/abci++ppp'
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/code"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@@ -70,6 +71,13 @@ type Config struct {
|
||||
//
|
||||
// height <-> pubkey <-> voting power
|
||||
ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"`
|
||||
|
||||
// Add artificial delays to each of the main ABCI calls to mimic computation time
|
||||
// of the application
|
||||
PrepareProposalDelay time.Duration `toml:"prepare_proposal_delay"`
|
||||
ProcessProposalDelay time.Duration `toml:"process_proposal_delay"`
|
||||
CheckTxDelay time.Duration `toml:"check_tx_delay"`
|
||||
// TODO: add vote extension and finalize block delays once completed (@cmwaters)
|
||||
}
|
||||
|
||||
func DefaultConfig(dir string) *Config {
|
||||
@@ -136,6 +144,11 @@ func (app *Application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
||||
Log: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
if app.cfg.CheckTxDelay != 0 {
|
||||
time.Sleep(app.cfg.CheckTxDelay)
|
||||
}
|
||||
|
||||
return abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
|
||||
}
|
||||
|
||||
@@ -257,6 +270,42 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a
|
||||
return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
|
||||
}
|
||||
|
||||
func (app *Application) PrepareProposal(
|
||||
req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
|
||||
txs := make([][]byte, 0, len(req.Txs))
|
||||
var totalBytes int64
|
||||
for _, tx := range req.Txs {
|
||||
totalBytes += int64(len(tx))
|
||||
if totalBytes > req.MaxTxBytes {
|
||||
break
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
|
||||
if app.cfg.PrepareProposalDelay != 0 {
|
||||
time.Sleep(app.cfg.PrepareProposalDelay)
|
||||
}
|
||||
|
||||
return abci.ResponsePrepareProposal{Txs: txs}
|
||||
}
|
||||
|
||||
// ProcessProposal implements part of the Application interface.
|
||||
// It accepts any proposal that does not contain a malformed transaction.
|
||||
func (app *Application) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
|
||||
for _, tx := range req.Txs {
|
||||
_, _, err := parseTx(tx)
|
||||
if err != nil {
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
|
||||
}
|
||||
}
|
||||
|
||||
if app.cfg.ProcessProposalDelay != 0 {
|
||||
time.Sleep(app.cfg.ProcessProposalDelay)
|
||||
}
|
||||
|
||||
return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}
|
||||
}
|
||||
|
||||
func (app *Application) Rollback() error {
|
||||
return app.state.Rollback()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
@@ -34,6 +35,7 @@ var (
|
||||
nodePersistIntervals = uniformChoice{0, 1, 5}
|
||||
nodeSnapshotIntervals = uniformChoice{0, 3}
|
||||
nodeRetainBlocks = uniformChoice{0, 1, 5}
|
||||
abciDelays = uniformChoice{"none", "small", "large"}
|
||||
nodePerturbations = probSetChoice{
|
||||
"disconnect": 0.1,
|
||||
"pause": 0.1,
|
||||
@@ -67,6 +69,17 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
Nodes: map[string]*e2e.ManifestNode{},
|
||||
}
|
||||
|
||||
switch abciDelays.Choose(r).(string) {
|
||||
case "none":
|
||||
case "small":
|
||||
manifest.PrepareProposalDelay = 100 * time.Millisecond
|
||||
manifest.ProcessProposalDelay = 100 * time.Millisecond
|
||||
case "large":
|
||||
manifest.PrepareProposalDelay = 200 * time.Millisecond
|
||||
manifest.ProcessProposalDelay = 200 * time.Millisecond
|
||||
manifest.CheckTxDelay = 20 * time.Millisecond
|
||||
}
|
||||
|
||||
var numSeeds, numValidators, numFulls, numLightClients int
|
||||
switch opt["topology"].(string) {
|
||||
case "single":
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
ipv6 = true
|
||||
initial_height = 1000
|
||||
evidence = 5
|
||||
initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" }
|
||||
prepare_proposal_delay = "100ms"
|
||||
process_proposal_delay = "100ms"
|
||||
check_tx_delay = "0ms"
|
||||
|
||||
[validators]
|
||||
validator01 = 100
|
||||
@@ -27,11 +31,7 @@ validator05 = 50
|
||||
|
||||
[node.seed01]
|
||||
mode = "seed"
|
||||
seeds = ["seed02"]
|
||||
|
||||
[node.seed02]
|
||||
mode = "seed"
|
||||
seeds = ["seed01"]
|
||||
perturb = ["restart"]
|
||||
|
||||
[node.validator01]
|
||||
seeds = ["seed01"]
|
||||
@@ -39,7 +39,7 @@ snapshot_interval = 5
|
||||
perturb = ["disconnect"]
|
||||
|
||||
[node.validator02]
|
||||
seeds = ["seed02"]
|
||||
seeds = ["seed01"]
|
||||
database = "boltdb"
|
||||
abci_protocol = "tcp"
|
||||
privval_protocol = "tcp"
|
||||
@@ -53,7 +53,7 @@ database = "badgerdb"
|
||||
#abci_protocol = "grpc"
|
||||
privval_protocol = "unix"
|
||||
persist_interval = 3
|
||||
retain_blocks = 3
|
||||
retain_blocks = 10
|
||||
perturb = ["kill"]
|
||||
|
||||
[node.validator04]
|
||||
@@ -65,7 +65,7 @@ perturb = ["pause"]
|
||||
[node.validator05]
|
||||
block_sync = "v0"
|
||||
start_at = 1005 # Becomes part of the validator set at 1010
|
||||
seeds = ["seed02"]
|
||||
persistent_peers = ["validator01", "full01"]
|
||||
database = "cleveldb"
|
||||
mempool_version = "v1"
|
||||
# FIXME: should be grpc, disabled due to https://github.com/tendermint/tendermint/issues/5439
|
||||
@@ -78,7 +78,7 @@ start_at = 1010
|
||||
mode = "full"
|
||||
block_sync = "v0"
|
||||
persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"]
|
||||
retain_blocks = 1
|
||||
retain_blocks = 10
|
||||
perturb = ["restart"]
|
||||
|
||||
[node.full02]
|
||||
|
||||
@@ -3,6 +3,7 @@ package e2e
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
@@ -51,11 +52,22 @@ type Manifest struct {
|
||||
// Options are ed25519 & secp256k1
|
||||
KeyType string `toml:"key_type"`
|
||||
|
||||
// Evidence indicates the amount of evidence that will be injected into the
|
||||
// testnet via the RPC endpoint of a random node. Default is 0
|
||||
Evidence int `toml:"evidence"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// Add artificial delays to each of the main ABCI calls to mimic computation time
|
||||
// of the application
|
||||
PrepareProposalDelay time.Duration `toml:"prepare_proposal_delay"`
|
||||
ProcessProposalDelay time.Duration `toml:"process_proposal_delay"`
|
||||
CheckTxDelay time.Duration `toml:"check_tx_delay"`
|
||||
// TODO: add vote extension and finalize block delay (@cmwaters)
|
||||
}
|
||||
|
||||
// ManifestNode represents a node in a testnet manifest.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
@@ -45,21 +46,28 @@ const (
|
||||
PerturbationKill Perturbation = "kill"
|
||||
PerturbationPause Perturbation = "pause"
|
||||
PerturbationRestart Perturbation = "restart"
|
||||
|
||||
EvidenceAgeHeight int64 = 7
|
||||
EvidenceAgeTime time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// Testnet represents a single testnet.
|
||||
type Testnet struct {
|
||||
Name string
|
||||
File string
|
||||
Dir string
|
||||
IP *net.IPNet
|
||||
InitialHeight int64
|
||||
InitialState map[string]string
|
||||
Validators map[*Node]int64
|
||||
ValidatorUpdates map[int64]map[*Node]int64
|
||||
Nodes []*Node
|
||||
KeyType string
|
||||
ABCIProtocol string
|
||||
Name string
|
||||
File string
|
||||
Dir string
|
||||
IP *net.IPNet
|
||||
InitialHeight int64
|
||||
InitialState map[string]string
|
||||
Validators map[*Node]int64
|
||||
ValidatorUpdates map[int64]map[*Node]int64
|
||||
Nodes []*Node
|
||||
KeyType string
|
||||
Evidence int
|
||||
ABCIProtocol string
|
||||
PrepareProposalDelay time.Duration
|
||||
ProcessProposalDelay time.Duration
|
||||
CheckTxDelay time.Duration
|
||||
}
|
||||
|
||||
// Node represents a Tendermint node in a testnet.
|
||||
@@ -113,16 +121,20 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
proxyPortGen := newPortGenerator(proxyPortFirst)
|
||||
|
||||
testnet := &Testnet{
|
||||
Name: filepath.Base(dir),
|
||||
File: file,
|
||||
Dir: dir,
|
||||
IP: ipGen.Network(),
|
||||
InitialHeight: 1,
|
||||
InitialState: manifest.InitialState,
|
||||
Validators: map[*Node]int64{},
|
||||
ValidatorUpdates: map[int64]map[*Node]int64{},
|
||||
Nodes: []*Node{},
|
||||
ABCIProtocol: manifest.ABCIProtocol,
|
||||
Name: filepath.Base(dir),
|
||||
File: file,
|
||||
Dir: dir,
|
||||
IP: ipGen.Network(),
|
||||
InitialHeight: 1,
|
||||
InitialState: manifest.InitialState,
|
||||
Validators: map[*Node]int64{},
|
||||
ValidatorUpdates: map[int64]map[*Node]int64{},
|
||||
Nodes: []*Node{},
|
||||
Evidence: manifest.Evidence,
|
||||
ABCIProtocol: manifest.ABCIProtocol,
|
||||
PrepareProposalDelay: manifest.PrepareProposalDelay,
|
||||
ProcessProposalDelay: manifest.ProcessProposalDelay,
|
||||
CheckTxDelay: manifest.CheckTxDelay,
|
||||
}
|
||||
if len(manifest.KeyType) != 0 {
|
||||
testnet.KeyType = manifest.KeyType
|
||||
@@ -333,6 +345,10 @@ func (n Node) Validate(testnet Testnet) error {
|
||||
if n.StateSync && n.StartAt == 0 {
|
||||
return errors.New("state synced nodes cannot start at the initial height")
|
||||
}
|
||||
if n.RetainBlocks != 0 && n.RetainBlocks < uint64(EvidenceAgeHeight) {
|
||||
return fmt.Errorf("retain_blocks must be greater or equal to max evidence age (%d)",
|
||||
EvidenceAgeHeight)
|
||||
}
|
||||
if n.PersistInterval == 0 && n.RetainBlocks > 0 {
|
||||
return errors.New("persist_interval=0 requires retain_blocks=0")
|
||||
}
|
||||
|
||||
320
test/e2e/runner/evidence.go
Normal file
320
test/e2e/runner/evidence.go
Normal file
@@ -0,0 +1,320 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/internal/test"
|
||||
tmjson "github.com/tendermint/tendermint/libs/json"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
// 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence
|
||||
const lightClientEvidenceRatio = 4
|
||||
|
||||
// InjectEvidence takes a running testnet and generates an amount of valid
|
||||
// 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, r *rand.Rand, testnet *e2e.Testnet, amount int) error {
|
||||
// select a random node
|
||||
var targetNode *e2e.Node
|
||||
|
||||
for _, idx := range r.Perm(len(testnet.Nodes)) {
|
||||
targetNode = testnet.Nodes[idx]
|
||||
|
||||
if targetNode.Mode == e2e.ModeSeed || targetNode.Mode == e2e.ModeLight {
|
||||
targetNode = nil
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if targetNode == nil {
|
||||
return errors.New("could not find node to inject evidence into")
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount))
|
||||
|
||||
client, err := targetNode.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// request the latest block and validator set from the node
|
||||
blockRes, err := client.Block(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
evidenceHeight := blockRes.Block.Height
|
||||
waitHeight := blockRes.Block.Height + 3
|
||||
|
||||
nValidators := 100
|
||||
valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the private keys of all the validators in the network
|
||||
privVals, err := getPrivateValidatorKeys(testnet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for the node to reach the height above the forged height so that
|
||||
// it is able to validate the evidence
|
||||
_, err = waitForNode(targetNode, waitHeight, time.Minute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ev types.Evidence
|
||||
for i := 1; i <= amount; i++ {
|
||||
if i%lightClientEvidenceRatio == 0 {
|
||||
ev, err = generateLightClientAttackEvidence(
|
||||
ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time,
|
||||
)
|
||||
} else {
|
||||
ev, err = generateDuplicateVoteEvidence(
|
||||
ctx, privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := client.BroadcastEvidence(ctx, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the node to reach the height above the forged height so that
|
||||
// it is able to validate the evidence
|
||||
_, err = waitForNode(targetNode, blockRes.Block.Height+2, 30*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Finished sending evidence (height %d)", blockRes.Block.Height+2))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) {
|
||||
privVals := []types.MockPV{}
|
||||
|
||||
for _, node := range testnet.Nodes {
|
||||
if node.Mode == e2e.ModeValidator {
|
||||
privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile)
|
||||
privKey, err := readPrivKey(privKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create mock private validators from the validators private key. MockPV is
|
||||
// stateless which means we can double vote and do other funky stuff
|
||||
privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false))
|
||||
}
|
||||
}
|
||||
|
||||
return privVals, nil
|
||||
}
|
||||
|
||||
// creates evidence of a lunatic attack. The height provided is the common height.
|
||||
// The forged height happens 2 blocks later.
|
||||
func generateLightClientAttackEvidence(
|
||||
ctx context.Context,
|
||||
privVals []types.MockPV,
|
||||
height int64,
|
||||
vals *types.ValidatorSet,
|
||||
chainID string,
|
||||
evTime time.Time,
|
||||
) (*types.LightClientAttackEvidence, error) {
|
||||
// forge a random header
|
||||
forgedHeight := height + 2
|
||||
forgedTime := evTime.Add(1 * time.Second)
|
||||
header := makeHeaderRandom(chainID, forgedHeight)
|
||||
header.Time = forgedTime
|
||||
|
||||
// add a new bogus validator and remove an existing one to
|
||||
// vary the validator set slightly
|
||||
pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header.ValidatorsHash = conflictingVals.Hash()
|
||||
|
||||
// create a commit for the forged header
|
||||
blockID := makeBlockID(header.Hash(), 1000, []byte("partshash"))
|
||||
voteSet := types.NewVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals)
|
||||
commit, err := test.MakeCommitFromVoteSet(blockID, voteSet, pv, forgedTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ev := &types.LightClientAttackEvidence{
|
||||
ConflictingBlock: &types.LightBlock{
|
||||
SignedHeader: &types.SignedHeader{
|
||||
Header: header,
|
||||
Commit: commit,
|
||||
},
|
||||
ValidatorSet: conflictingVals,
|
||||
},
|
||||
CommonHeight: height,
|
||||
TotalVotingPower: vals.TotalVotingPower(),
|
||||
Timestamp: evTime,
|
||||
}
|
||||
ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{
|
||||
Header: makeHeaderRandom(chainID, forgedHeight),
|
||||
})
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
// generateDuplicateVoteEvidence picks a random validator from the val set and
|
||||
// returns duplicate vote evidence against the validator
|
||||
func generateDuplicateVoteEvidence(
|
||||
ctx context.Context,
|
||||
privVals []types.MockPV,
|
||||
height int64,
|
||||
vals *types.ValidatorSet,
|
||||
chainID string,
|
||||
time time.Time,
|
||||
) (*types.DuplicateVoteEvidence, error) {
|
||||
privVal, valIdx, err := getRandomValidatorIndex(privVals, vals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
voteA, err := test.MakeVote(privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
voteB, err := test.MakeVote(privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ev, err := types.NewDuplicateVoteEvidence(voteA, voteB, time, vals)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate evidence: %w", err)
|
||||
}
|
||||
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
// getRandomValidatorIndex picks a random validator from a slice of mock PrivVals that's
|
||||
// also part of the validator set, returning the PrivVal and its index in the validator set
|
||||
func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet) (types.MockPV, int32, error) {
|
||||
for _, idx := range rand.Perm(len(privVals)) {
|
||||
pv := privVals[idx]
|
||||
valIdx, _ := vals.GetByAddress(pv.PrivKey.PubKey().Address())
|
||||
if valIdx >= 0 {
|
||||
return pv, valIdx, nil
|
||||
}
|
||||
}
|
||||
return types.MockPV{}, -1, errors.New("no private validator found in validator set")
|
||||
}
|
||||
|
||||
func readPrivKey(keyFilePath string) (crypto.PrivKey, error) {
|
||||
keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pvKey := privval.FilePVKey{}
|
||||
err = tmjson.Unmarshal(keyJSONBytes, &pvKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err)
|
||||
}
|
||||
|
||||
return pvKey.PrivKey, nil
|
||||
}
|
||||
|
||||
func makeHeaderRandom(chainID string, height int64) *types.Header {
|
||||
return &types.Header{
|
||||
Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1},
|
||||
ChainID: chainID,
|
||||
Height: height,
|
||||
Time: time.Now(),
|
||||
LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")),
|
||||
LastCommitHash: crypto.CRandBytes(tmhash.Size),
|
||||
DataHash: crypto.CRandBytes(tmhash.Size),
|
||||
ValidatorsHash: crypto.CRandBytes(tmhash.Size),
|
||||
NextValidatorsHash: crypto.CRandBytes(tmhash.Size),
|
||||
ConsensusHash: crypto.CRandBytes(tmhash.Size),
|
||||
AppHash: crypto.CRandBytes(tmhash.Size),
|
||||
LastResultsHash: crypto.CRandBytes(tmhash.Size),
|
||||
EvidenceHash: crypto.CRandBytes(tmhash.Size),
|
||||
ProposerAddress: crypto.CRandBytes(crypto.AddressSize),
|
||||
}
|
||||
}
|
||||
|
||||
func makeRandomBlockID() types.BlockID {
|
||||
return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size))
|
||||
}
|
||||
|
||||
func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
|
||||
var (
|
||||
h = make([]byte, tmhash.Size)
|
||||
psH = make([]byte, tmhash.Size)
|
||||
)
|
||||
copy(h, hash)
|
||||
copy(psH, partSetHash)
|
||||
return types.BlockID{
|
||||
Hash: h,
|
||||
PartSetHeader: types.PartSetHeader{
|
||||
Total: partSetSize,
|
||||
Hash: psH,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet,
|
||||
) ([]types.PrivValidator, *types.ValidatorSet, error) {
|
||||
newVal, newPrivVal, err := test.Validator(ctx, 10)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var newVals *types.ValidatorSet
|
||||
if vals.Size() > 2 {
|
||||
newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal))
|
||||
} else {
|
||||
newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal))
|
||||
}
|
||||
|
||||
// we need to sort the priv validators with the same index as the validator set
|
||||
pv := make([]types.PrivValidator, newVals.Size())
|
||||
for idx, val := range newVals.Validators {
|
||||
found := false
|
||||
for _, p := range append(privVals, newPrivVal.(types.MockPV)) {
|
||||
if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) {
|
||||
pv[idx] = p
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address)
|
||||
}
|
||||
}
|
||||
|
||||
return pv, newVals, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
@@ -12,9 +13,9 @@ import (
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
)
|
||||
const randomSeed = 2308084734268
|
||||
|
||||
var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
func main() {
|
||||
NewCLI().Run()
|
||||
@@ -56,6 +57,8 @@ func NewCLI() *CLI {
|
||||
return err
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec
|
||||
|
||||
chLoadResult := make(chan error)
|
||||
ctx, loadCancel := context.WithCancel(context.Background())
|
||||
defer loadCancel()
|
||||
@@ -84,6 +87,15 @@ func NewCLI() *CLI {
|
||||
}
|
||||
}
|
||||
|
||||
if cli.testnet.Evidence > 0 {
|
||||
if err := InjectEvidence(ctx, r, cli.testnet, cli.testnet.Evidence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Wait(cli.testnet, 5); err != nil { // ensure chain progress
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
loadCancel()
|
||||
if err := <-chLoadResult; err != nil {
|
||||
return err
|
||||
@@ -175,6 +187,29 @@ func NewCLI() *CLI {
|
||||
},
|
||||
})
|
||||
|
||||
cli.root.AddCommand(&cobra.Command{
|
||||
Use: "evidence [amount]",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Short: "Generates and broadcasts evidence to a random node",
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
amount := 1
|
||||
|
||||
if len(args) == 1 {
|
||||
amount, err = strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return InjectEvidence(
|
||||
cmd.Context(),
|
||||
rand.New(rand.NewSource(randomSeed)), // nolint: gosec
|
||||
cli.testnet,
|
||||
amount,
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
cli.root.AddCommand(&cobra.Command{
|
||||
Use: "test",
|
||||
Short: "Runs test cases against a running testnet",
|
||||
|
||||
@@ -200,7 +200,7 @@ func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
|
||||
InitialHeight: testnet.InitialHeight,
|
||||
}
|
||||
// set the app version to 1
|
||||
genesis.ConsensusParams.Version.AppVersion = 1
|
||||
genesis.ConsensusParams.Version.App = 1
|
||||
for validator, power := range testnet.Validators {
|
||||
genesis.Validators = append(genesis.Validators, types.GenesisValidator{
|
||||
Name: validator.Name,
|
||||
@@ -324,16 +324,19 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) {
|
||||
// MakeAppConfig generates an ABCI application config for a node.
|
||||
func MakeAppConfig(node *e2e.Node) ([]byte, error) {
|
||||
cfg := map[string]interface{}{
|
||||
"chain_id": node.Testnet.Name,
|
||||
"dir": "data/app",
|
||||
"listen": AppAddressUNIX,
|
||||
"mode": node.Mode,
|
||||
"proxy_port": node.ProxyPort,
|
||||
"protocol": "socket",
|
||||
"persist_interval": node.PersistInterval,
|
||||
"snapshot_interval": node.SnapshotInterval,
|
||||
"retain_blocks": node.RetainBlocks,
|
||||
"key_type": node.PrivvalKey.Type(),
|
||||
"chain_id": node.Testnet.Name,
|
||||
"dir": "data/app",
|
||||
"listen": AppAddressUNIX,
|
||||
"mode": node.Mode,
|
||||
"proxy_port": node.ProxyPort,
|
||||
"protocol": "socket",
|
||||
"persist_interval": node.PersistInterval,
|
||||
"snapshot_interval": node.SnapshotInterval,
|
||||
"retain_blocks": node.RetainBlocks,
|
||||
"key_type": node.PrivvalKey.Type(),
|
||||
"prepare_proposal_delay": node.Testnet.PrepareProposalDelay,
|
||||
"process_proposal_delay": node.Testnet.ProcessProposalDelay,
|
||||
"check_tx_delay": node.Testnet.CheckTxDelay,
|
||||
}
|
||||
switch node.ABCIProtocol {
|
||||
case e2e.ProtocolUNIX:
|
||||
|
||||
22
test/e2e/tests/evidence_test.go
Normal file
22
test/e2e/tests/evidence_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// assert that all nodes that have blocks at the height of a misbehavior has evidence
|
||||
// for that misbehavior
|
||||
func TestEvidence_Misbehavior(t *testing.T) {
|
||||
blocks := fetchBlockChain(t)
|
||||
testnet := loadTestnet(t)
|
||||
seenEvidence := 0
|
||||
for _, block := range blocks {
|
||||
if len(block.Evidence.Evidence) != 0 {
|
||||
seenEvidence += len(block.Evidence.Evidence)
|
||||
}
|
||||
}
|
||||
require.Equal(t, testnet.Evidence, seenEvidence,
|
||||
"difference between the amount of evidence produced and committed")
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
mempoolv0 "github.com/tendermint/tendermint/test/fuzz/mempool/v0"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
mempoolv1 "github.com/tendermint/tendermint/test/fuzz/mempool/v1"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user