mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-28 02:52:48 +00:00
e2e: add evidence tests
This commit is contained in:
@@ -188,7 +188,8 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
// mock the evidence pool
|
||||
// everyone includes evidence of another double signing
|
||||
vIdx := (i + 1) % nValidators
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID())
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultTestTime, privVals[vIdx], config.ChainID())
|
||||
require.NoError(t, err)
|
||||
evpool := &statemocks.EvidencePool{}
|
||||
evpool.On("CheckEvidence", mock.AnythingOfType("types.EvidenceList")).Return(nil)
|
||||
evpool.On("PendingEvidence", mock.AnythingOfType("int64")).Return([]types.Evidence{
|
||||
@@ -205,7 +206,7 @@ func TestReactorWithEvidence(t *testing.T) {
|
||||
|
||||
eventBus := types.NewEventBus()
|
||||
eventBus.SetLogger(log.TestingLogger().With("module", "events"))
|
||||
err := eventBus.Start()
|
||||
err = eventBus.Start()
|
||||
require.NoError(t, err)
|
||||
cs.SetEventBus(eventBus)
|
||||
|
||||
|
||||
+10
-3
@@ -463,10 +463,13 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) {
|
||||
|
||||
// Check the height of the conflicting votes and fetch the corresponding time and validator set
|
||||
// to produce the valid evidence
|
||||
var dve *types.DuplicateVoteEvidence
|
||||
var (
|
||||
dve *types.DuplicateVoteEvidence
|
||||
err error
|
||||
)
|
||||
switch {
|
||||
case voteSet.VoteA.Height == state.LastBlockHeight:
|
||||
dve = types.NewDuplicateVoteEvidence(
|
||||
dve, err = types.NewDuplicateVoteEvidence(
|
||||
voteSet.VoteA,
|
||||
voteSet.VoteB,
|
||||
state.LastBlockTime,
|
||||
@@ -486,7 +489,7 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) {
|
||||
evpool.logger.Error("failed to load block time for conflicting votes", "height", voteSet.VoteA.Height)
|
||||
continue
|
||||
}
|
||||
dve = types.NewDuplicateVoteEvidence(
|
||||
dve, err = types.NewDuplicateVoteEvidence(
|
||||
voteSet.VoteA,
|
||||
voteSet.VoteB,
|
||||
blockMeta.Header.Time,
|
||||
@@ -502,6 +505,10 @@ func (evpool *Pool) processConsensusBuffer(state sm.State) {
|
||||
"state.LastBlockHeight", state.LastBlockHeight)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
evpool.logger.Error("error in generating evidence from votes", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// check if we already have this evidence
|
||||
if evpool.isPending(dve) {
|
||||
|
||||
+22
-13
@@ -61,7 +61,8 @@ func TestEvidencePoolBasic(t *testing.T) {
|
||||
assert.Equal(t, 0, len(evs))
|
||||
assert.Zero(t, size)
|
||||
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID)
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime, privVals[0], evidenceChainID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// good evidence
|
||||
evAdded := make(chan struct{})
|
||||
@@ -133,8 +134,9 @@ func TestAddExpiredEvidence(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.evDescription, func(t *testing.T) {
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID)
|
||||
err := pool.AddEvidence(ev)
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(tc.evHeight, tc.evTime, val, evidenceChainID)
|
||||
require.NoError(t, err)
|
||||
err = pool.AddEvidence(ev)
|
||||
if tc.expErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
@@ -149,7 +151,8 @@ func TestReportConflictingVotes(t *testing.T) {
|
||||
|
||||
pool, pv := defaultTestPool(height)
|
||||
val := types.NewValidator(pv.PrivKey.PubKey(), 10)
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID)
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height+1, defaultEvidenceTime, pv, evidenceChainID)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool.ReportConflictingVotes(ev.VoteA, ev.VoteB)
|
||||
|
||||
@@ -185,12 +188,14 @@ func TestEvidencePoolUpdate(t *testing.T) {
|
||||
state := pool.State()
|
||||
|
||||
// create new block (no need to save it to blockStore)
|
||||
prunedEv := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute),
|
||||
prunedEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(1, defaultEvidenceTime.Add(1*time.Minute),
|
||||
val, evidenceChainID)
|
||||
err := pool.AddEvidence(prunedEv)
|
||||
require.NoError(t, err)
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute),
|
||||
err = pool.AddEvidence(prunedEv)
|
||||
require.NoError(t, err)
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(21*time.Minute),
|
||||
val, evidenceChainID)
|
||||
require.NoError(t, err)
|
||||
lastCommit := makeCommit(height, val.PrivKey.PubKey().Address())
|
||||
block := types.MakeBlock(height+1, []types.Tx{}, lastCommit, []types.Evidence{ev})
|
||||
// update state (partially)
|
||||
@@ -215,9 +220,10 @@ func TestEvidencePoolUpdate(t *testing.T) {
|
||||
func TestVerifyPendingEvidencePasses(t *testing.T) {
|
||||
var height int64 = 1
|
||||
pool, val := defaultTestPool(height)
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
|
||||
val, evidenceChainID)
|
||||
err := pool.AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
err = pool.AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pool.CheckEvidence(types.EvidenceList{ev})
|
||||
@@ -227,9 +233,10 @@ func TestVerifyPendingEvidencePasses(t *testing.T) {
|
||||
func TestVerifyDuplicatedEvidenceFails(t *testing.T) {
|
||||
var height int64 = 1
|
||||
pool, val := defaultTestPool(height)
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime.Add(1*time.Minute),
|
||||
val, evidenceChainID)
|
||||
err := pool.CheckEvidence(types.EvidenceList{ev, ev})
|
||||
require.NoError(t, err)
|
||||
err = pool.CheckEvidence(types.EvidenceList{ev, ev})
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "duplicate evidence", err.(*types.ErrInvalidEvidence).Reason.Error())
|
||||
}
|
||||
@@ -311,10 +318,12 @@ func TestRecoverPendingEvidence(t *testing.T) {
|
||||
pool, err := evidence.NewPool(evidenceDB, stateStore, blockStore)
|
||||
require.NoError(t, err)
|
||||
pool.SetLogger(log.TestingLogger())
|
||||
goodEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(height,
|
||||
goodEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(height,
|
||||
defaultEvidenceTime.Add(10*time.Minute), val, evidenceChainID)
|
||||
expiredEvidence := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1),
|
||||
require.NoError(t, err)
|
||||
expiredEvidence, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(1),
|
||||
defaultEvidenceTime.Add(1*time.Minute), val, evidenceChainID)
|
||||
require.NoError(t, err)
|
||||
err = pool.AddEvidence(goodEvidence)
|
||||
require.NoError(t, err)
|
||||
err = pool.AddEvidence(expiredEvidence)
|
||||
|
||||
@@ -152,9 +152,10 @@ func TestReactorsGossipNoCommittedEvidence(t *testing.T) {
|
||||
// the first reactor receives three more evidence
|
||||
evList = make([]types.Evidence, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i),
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height-3+int64(i),
|
||||
time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, state.ChainID)
|
||||
err := pools[0].AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
err = pools[0].AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
evList[i] = ev
|
||||
}
|
||||
@@ -327,9 +328,10 @@ func _waitForEvidence(
|
||||
func sendEvidence(t *testing.T, evpool *evidence.Pool, val types.PrivValidator, n int) types.EvidenceList {
|
||||
evList := make([]types.Evidence, n)
|
||||
for i := 0; i < n; i++ {
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1),
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(int64(i+1),
|
||||
time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), val, evidenceChainID)
|
||||
err := evpool.AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
err = evpool.AddEvidence(ev)
|
||||
require.NoError(t, err)
|
||||
evList[i] = ev
|
||||
}
|
||||
@@ -377,12 +379,13 @@ func TestEvidenceVectors(t *testing.T) {
|
||||
|
||||
valSet := types.NewValidatorSet([]*types.Validator{val})
|
||||
|
||||
dupl := types.NewDuplicateVoteEvidence(
|
||||
dupl, err := types.NewDuplicateVoteEvidence(
|
||||
exampleVote(1),
|
||||
exampleVote(2),
|
||||
defaultEvidenceTime,
|
||||
valSet,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
testName string
|
||||
|
||||
@@ -412,11 +412,14 @@ func TestVerifyDuplicateVoteEvidence(t *testing.T) {
|
||||
}
|
||||
|
||||
// create good evidence and correct validator power
|
||||
goodEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
|
||||
goodEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
|
||||
require.NoError(t, err)
|
||||
goodEv.ValidatorPower = 1
|
||||
goodEv.TotalVotingPower = 1
|
||||
badEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
|
||||
badTimeEv := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID)
|
||||
badEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime, val, chainID)
|
||||
require.NoError(t, err)
|
||||
badTimeEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(10, defaultEvidenceTime.Add(1*time.Minute), val, chainID)
|
||||
require.NoError(t, err)
|
||||
badTimeEv.ValidatorPower = 1
|
||||
badTimeEv.TotalVotingPower = 1
|
||||
state := sm.State{
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/tmhash"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tendermint/version"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultTestChainID = "test-chain"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultTestTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
func RandomAddress() []byte {
|
||||
return crypto.CRandBytes(crypto.AddressSize)
|
||||
}
|
||||
|
||||
func RandomHash() []byte {
|
||||
return crypto.CRandBytes(tmhash.Size)
|
||||
}
|
||||
|
||||
func MakeBlockID() types.BlockID {
|
||||
return MakeBlockIDWithHash(RandomHash())
|
||||
}
|
||||
|
||||
func MakeBlockIDWithHash(hash []byte) types.BlockID {
|
||||
return types.BlockID{
|
||||
Hash: hash,
|
||||
PartSetHeader: types.PartSetHeader{
|
||||
Total: 100,
|
||||
Hash: RandomHash(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MakeHeader fills the rest of the contents of the header such that it passes
|
||||
// validate basic
|
||||
func MakeHeader(t *testing.T, h *types.Header) *types.Header {
|
||||
t.Helper()
|
||||
if h.Version.Block == 0 {
|
||||
h.Version.Block = version.BlockProtocol
|
||||
}
|
||||
if h.Height == 0 {
|
||||
h.Height = 1
|
||||
}
|
||||
if h.LastBlockID.IsZero() {
|
||||
h.LastBlockID = MakeBlockID()
|
||||
}
|
||||
if h.ChainID == "" {
|
||||
h.ChainID = DefaultTestChainID
|
||||
}
|
||||
if len(h.LastCommitHash) == 0 {
|
||||
h.LastCommitHash = RandomHash()
|
||||
}
|
||||
if len(h.DataHash) == 0 {
|
||||
h.DataHash = RandomHash()
|
||||
}
|
||||
if len(h.ValidatorsHash) == 0 {
|
||||
h.ValidatorsHash = RandomHash()
|
||||
}
|
||||
if len(h.NextValidatorsHash) == 0 {
|
||||
h.NextValidatorsHash = RandomHash()
|
||||
}
|
||||
if len(h.ConsensusHash) == 0 {
|
||||
h.ConsensusHash = RandomHash()
|
||||
}
|
||||
if len(h.AppHash) == 0 {
|
||||
h.AppHash = RandomHash()
|
||||
}
|
||||
if len(h.LastResultsHash) == 0 {
|
||||
h.LastResultsHash = RandomHash()
|
||||
}
|
||||
if len(h.EvidenceHash) == 0 {
|
||||
h.EvidenceHash = RandomHash()
|
||||
}
|
||||
if len(h.ProposerAddress) == 0 {
|
||||
h.ProposerAddress = RandomAddress()
|
||||
}
|
||||
|
||||
require.NoError(t, h.ValidateBasic())
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func MakeCommit(ctx context.Context, blockID types.BlockID, height int64, round int32, voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time) (*types.Commit, error) {
|
||||
// all sign
|
||||
for i := 0; i < len(validators); i++ {
|
||||
pubKey, err := validators[i].GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
ValidatorIndex: int32(i),
|
||||
Height: height,
|
||||
Round: round,
|
||||
Type: tmproto.PrecommitType,
|
||||
BlockID: blockID,
|
||||
Timestamp: now,
|
||||
}
|
||||
|
||||
v := vote.ToProto()
|
||||
|
||||
if err := validators[i].SignVote(voteSet.ChainID(), v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vote.Signature = v.Signature
|
||||
if _, err := voteSet.AddVote(vote); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return voteSet.MakeCommit(), nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
Package factory provides generation code for common structs in Tendermint.
|
||||
It is used primarily for the testing of internal components such as statesync,
|
||||
consensus, blocksync etc..
|
||||
*/
|
||||
package test
|
||||
@@ -0,0 +1,15 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestMakeHeader(t *testing.T) {
|
||||
header := MakeHeader(t, &types.Header{})
|
||||
require.NotNil(t, header)
|
||||
|
||||
require.NoError(t, header.ValidateBasic())
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func GenesisDoc(
|
||||
config *cfg.Config,
|
||||
time time.Time,
|
||||
validators []*types.Validator,
|
||||
consensusParams *tmproto.ConsensusParams,
|
||||
) *types.GenesisDoc {
|
||||
|
||||
genesisValidators := make([]types.GenesisValidator, len(validators))
|
||||
|
||||
for i := range validators {
|
||||
genesisValidators[i] = types.GenesisValidator{
|
||||
Power: validators[i].VotingPower,
|
||||
PubKey: validators[i].PubKey,
|
||||
}
|
||||
}
|
||||
|
||||
return &types.GenesisDoc{
|
||||
GenesisTime: time,
|
||||
InitialHeight: 1,
|
||||
ChainID: config.ChainID(),
|
||||
Validators: genesisValidators,
|
||||
ConsensusParams: consensusParams,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package test
|
||||
|
||||
import "github.com/tendermint/tendermint/types"
|
||||
|
||||
func MakeNTxs(height, n int64) []types.Tx {
|
||||
txs := make([]types.Tx, n)
|
||||
for i := range txs {
|
||||
txs[i] = types.Tx([]byte{byte(height), byte(i / 256), byte(i % 256)})
|
||||
}
|
||||
return txs
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func Validator(ctx context.Context, votingPower int64) (*types.Validator, types.PrivValidator, error) {
|
||||
privVal := types.NewMockPV()
|
||||
pubKey, err := privVal.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
val := types.NewValidator(pubKey, votingPower)
|
||||
return val, privVal, nil
|
||||
}
|
||||
|
||||
func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) {
|
||||
var (
|
||||
valz = make([]*types.Validator, numValidators)
|
||||
privValidators = make([]types.PrivValidator, numValidators)
|
||||
)
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < numValidators; i++ {
|
||||
val, privValidator, err := Validator(ctx, votingPower)
|
||||
require.NoError(t, err)
|
||||
valz[i] = val
|
||||
privValidators[i] = privValidator
|
||||
}
|
||||
|
||||
sort.Sort(types.PrivValidatorsByAddress(privValidators))
|
||||
|
||||
return types.NewValidatorSet(valz), privValidators
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func MakeVote(
|
||||
ctx context.Context,
|
||||
val types.PrivValidator,
|
||||
chainID string,
|
||||
valIndex int32,
|
||||
height int64,
|
||||
round int32,
|
||||
step int,
|
||||
blockID types.BlockID,
|
||||
time time.Time,
|
||||
) (*types.Vote, error) {
|
||||
pubKey, err := val.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &types.Vote{
|
||||
ValidatorAddress: pubKey.Address(),
|
||||
ValidatorIndex: valIndex,
|
||||
Height: height,
|
||||
Round: round,
|
||||
Type: tmproto.SignedMsgType(step),
|
||||
BlockID: blockID,
|
||||
Timestamp: time,
|
||||
}
|
||||
|
||||
vpb := v.ToProto()
|
||||
if err := val.SignVote(chainID, vpb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.Signature = vpb.Signature
|
||||
return v, nil
|
||||
}
|
||||
+2
-1
@@ -277,7 +277,8 @@ func TestCreateProposalBlock(t *testing.T) {
|
||||
// than can fit in a block
|
||||
var currentBytes int64
|
||||
for currentBytes <= maxEvidenceBytes {
|
||||
ev := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain")
|
||||
ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain")
|
||||
require.NoError(t, err)
|
||||
currentBytes += int64(len(ev.Bytes()))
|
||||
evidencePool.ReportConflictingVotes(ev.VoteA, ev.VoteB)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,9 @@ func newEvidence(t *testing.T, val *privval.FilePV,
|
||||
validator := types.NewValidator(val.Key.PubKey, 10)
|
||||
valSet := types.NewValidatorSet([]*types.Validator{validator})
|
||||
|
||||
return types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet)
|
||||
ev, err := types.NewDuplicateVoteEvidence(vote, vote2, defaultTestTime, valSet)
|
||||
require.NoError(t, err)
|
||||
return ev
|
||||
}
|
||||
|
||||
func makeEvidences(
|
||||
|
||||
@@ -153,7 +153,8 @@ func TestBeginBlockByzantineValidators(t *testing.T) {
|
||||
}
|
||||
|
||||
// we don't need to worry about validating the evidence as long as they pass validate basic
|
||||
dve := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID)
|
||||
dve, err := types.NewMockDuplicateVoteEvidenceWithValidator(3, defaultEvidenceTime, privVal, state.ChainID)
|
||||
require.NoError(t, err)
|
||||
dve.ValidatorPower = 1000
|
||||
lcae := &types.LightClientAttackEvidence{
|
||||
ConflictingBlock: &types.LightBlock{
|
||||
|
||||
@@ -243,8 +243,9 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
var currentBytes int64
|
||||
// more bytes than the maximum allowed for evidence
|
||||
for currentBytes <= maxBytesEvidence {
|
||||
newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(),
|
||||
newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(),
|
||||
privVals[proposerAddr.String()], chainID)
|
||||
require.NoError(t, err)
|
||||
evidence = append(evidence, newEv)
|
||||
currentBytes += int64(len(newEv.Bytes()))
|
||||
}
|
||||
@@ -263,8 +264,9 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
var currentBytes int64
|
||||
// precisely the amount of allowed evidence
|
||||
for {
|
||||
newEv := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime,
|
||||
newEv, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, defaultEvidenceTime,
|
||||
privVals[proposerAddr.String()], chainID)
|
||||
require.NoError(t, err)
|
||||
currentBytes += int64(len(newEv.Bytes()))
|
||||
if currentBytes >= maxBytesEvidence {
|
||||
break
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
ipv6 = true
|
||||
initial_height = 1000
|
||||
evidence = 5
|
||||
initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" }
|
||||
|
||||
[validators]
|
||||
@@ -27,11 +28,7 @@ validator05 = 50
|
||||
|
||||
[node.seed01]
|
||||
mode = "seed"
|
||||
seeds = ["seed02"]
|
||||
|
||||
[node.seed02]
|
||||
mode = "seed"
|
||||
seeds = ["seed01"]
|
||||
perturb = ["restart"]
|
||||
|
||||
[node.validator01]
|
||||
seeds = ["seed01"]
|
||||
@@ -39,7 +36,7 @@ snapshot_interval = 5
|
||||
perturb = ["disconnect"]
|
||||
|
||||
[node.validator02]
|
||||
seeds = ["seed02"]
|
||||
seeds = ["seed01"]
|
||||
database = "boltdb"
|
||||
abci_protocol = "tcp"
|
||||
privval_protocol = "tcp"
|
||||
@@ -53,7 +50,7 @@ database = "badgerdb"
|
||||
#abci_protocol = "grpc"
|
||||
privval_protocol = "unix"
|
||||
persist_interval = 3
|
||||
retain_blocks = 3
|
||||
retain_blocks = 10
|
||||
perturb = ["kill"]
|
||||
|
||||
[node.validator04]
|
||||
@@ -64,7 +61,7 @@ perturb = ["pause"]
|
||||
|
||||
[node.validator05]
|
||||
start_at = 1005 # Becomes part of the validator set at 1010
|
||||
seeds = ["seed02"]
|
||||
persistent_peers = ["validator01", "full01"]
|
||||
database = "cleveldb"
|
||||
fast_sync = true
|
||||
mempool_version = "v1"
|
||||
@@ -78,7 +75,7 @@ start_at = 1010
|
||||
mode = "full"
|
||||
fast_sync = true
|
||||
persistent_peers = ["validator01", "validator02", "validator03", "validator04", "validator05"]
|
||||
retain_blocks = 1
|
||||
retain_blocks = 10
|
||||
perturb = ["restart"]
|
||||
|
||||
[node.full02]
|
||||
|
||||
@@ -51,6 +51,10 @@ 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
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
@@ -45,6 +46,9 @@ const (
|
||||
PerturbationKill Perturbation = "kill"
|
||||
PerturbationPause Perturbation = "pause"
|
||||
PerturbationRestart Perturbation = "restart"
|
||||
|
||||
EvidenceAgeHeight int64 = 7
|
||||
EvidenceAgeTime time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// Testnet represents a single testnet.
|
||||
@@ -59,6 +63,7 @@ type Testnet struct {
|
||||
ValidatorUpdates map[int64]map[*Node]int64
|
||||
Nodes []*Node
|
||||
KeyType string
|
||||
Evidence int
|
||||
ABCIProtocol string
|
||||
}
|
||||
|
||||
@@ -122,6 +127,7 @@ func LoadTestnet(file string) (*Testnet, error) {
|
||||
Validators: map[*Node]int64{},
|
||||
ValidatorUpdates: map[int64]map[*Node]int64{},
|
||||
Nodes: []*Node{},
|
||||
Evidence: manifest.Evidence,
|
||||
ABCIProtocol: manifest.ABCIProtocol,
|
||||
}
|
||||
if len(manifest.KeyType) != 0 {
|
||||
@@ -328,6 +334,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")
|
||||
}
|
||||
|
||||
@@ -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.MakeCommit(ctx, blockID, forgedHeight, 0, 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(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
voteB, err := test.MakeVote(ctx, 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
|
||||
}
|
||||
+38
-3
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
+12
-6
@@ -41,7 +41,8 @@ func TestBlockAddEvidence(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
block := MakeBlock(h, txs, commit, evList)
|
||||
@@ -61,7 +62,8 @@ func TestBlockValidateBasic(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -127,7 +129,8 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(512)
|
||||
@@ -144,7 +147,8 @@ func TestBlockHashesTo(t *testing.T) {
|
||||
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals, time.Now())
|
||||
require.NoError(t, err)
|
||||
|
||||
ev := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
ev, err := NewMockDuplicateVoteEvidenceWithValidator(h, time.Now(), vals[0], "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
evList := []Evidence{ev}
|
||||
|
||||
block := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList)
|
||||
@@ -635,7 +639,8 @@ func TestBlockProtoBuf(t *testing.T) {
|
||||
b2 := MakeBlock(h, []Tx{Tx([]byte{1})}, c1, []Evidence{})
|
||||
b2.ProposerAddress = tmrand.Bytes(crypto.AddressSize)
|
||||
evidenceTime := time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
evi := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain")
|
||||
evi, err := NewMockDuplicateVoteEvidence(h, evidenceTime, "block-test-chain")
|
||||
require.NoError(t, err)
|
||||
b2.Evidence = EvidenceData{Evidence: EvidenceList{evi}}
|
||||
b2.EvidenceHash = b2.Evidence.Hash()
|
||||
|
||||
@@ -699,7 +704,8 @@ func TestDataProtoBuf(t *testing.T) {
|
||||
// TestEvidenceDataProtoBuf ensures parity in converting to and from proto.
|
||||
func TestEvidenceDataProtoBuf(t *testing.T) {
|
||||
const chainID = "mychain"
|
||||
ev := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID)
|
||||
ev, err := NewMockDuplicateVoteEvidence(math.MaxInt64, time.Now(), chainID)
|
||||
require.NoError(t, err)
|
||||
data := &EvidenceData{Evidence: EvidenceList{ev}}
|
||||
_ = data.ByteSize()
|
||||
testCases := []struct {
|
||||
|
||||
@@ -285,7 +285,8 @@ func TestEventBusPublishEventNewEvidence(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
ev := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id")
|
||||
ev, err := NewMockDuplicateVoteEvidence(1, time.Now(), "test-chain-id")
|
||||
require.NoError(t, err)
|
||||
|
||||
query := "tm.event='NewEvidence'"
|
||||
evSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query))
|
||||
|
||||
+25
-11
@@ -45,15 +45,20 @@ type DuplicateVoteEvidence struct {
|
||||
var _ Evidence = &DuplicateVoteEvidence{}
|
||||
|
||||
// NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given
|
||||
// two conflicting votes. If one of the votes is nil, evidence returned is nil as well
|
||||
func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet) *DuplicateVoteEvidence {
|
||||
// two conflicting votes. If either of the votes is nil, the val set is nil or the voter is
|
||||
// not in the val set, an error is returned
|
||||
func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *ValidatorSet,
|
||||
) (*DuplicateVoteEvidence, error) {
|
||||
var voteA, voteB *Vote
|
||||
if vote1 == nil || vote2 == nil || valSet == nil {
|
||||
return nil
|
||||
if vote1 == nil || vote2 == nil {
|
||||
return nil, errors.New("missing vote")
|
||||
}
|
||||
if valSet == nil {
|
||||
return nil, errors.New("missing validator set")
|
||||
}
|
||||
idx, val := valSet.GetByAddress(vote1.ValidatorAddress)
|
||||
if idx == -1 {
|
||||
return nil
|
||||
return nil, errors.New("validator not in validator set")
|
||||
}
|
||||
|
||||
if strings.Compare(vote1.BlockID.Key(), vote2.BlockID.Key()) == -1 {
|
||||
@@ -69,7 +74,7 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote, blockTime time.Time, valSet *V
|
||||
TotalVotingPower: valSet.TotalVotingPower(),
|
||||
ValidatorPower: val.VotingPower,
|
||||
Timestamp: blockTime,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ABCI returns the application relevant representation of the evidence
|
||||
@@ -554,23 +559,32 @@ func (err *ErrEvidenceOverflow) Error() string {
|
||||
// unstable - use only for testing
|
||||
|
||||
// assumes the round to be 0 and the validator index to be 0
|
||||
func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) *DuplicateVoteEvidence {
|
||||
func NewMockDuplicateVoteEvidence(height int64, time time.Time, chainID string) (*DuplicateVoteEvidence, error) {
|
||||
val := NewMockPV()
|
||||
return NewMockDuplicateVoteEvidenceWithValidator(height, time, val, chainID)
|
||||
}
|
||||
|
||||
// assumes voting power to be 10 and validator to be the only one in the set
|
||||
func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time,
|
||||
pv PrivValidator, chainID string) *DuplicateVoteEvidence {
|
||||
pubKey, _ := pv.GetPubKey()
|
||||
pv PrivValidator, chainID string) (*DuplicateVoteEvidence, error) {
|
||||
pubKey, err := pv.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val := NewValidator(pubKey, 10)
|
||||
voteA := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time)
|
||||
vA := voteA.ToProto()
|
||||
_ = pv.SignVote(chainID, vA)
|
||||
err = pv.SignVote(chainID, vA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
voteA.Signature = vA.Signature
|
||||
voteB := makeMockVote(height, 0, 0, pubKey.Address(), randBlockID(), time)
|
||||
vB := voteB.ToProto()
|
||||
_ = pv.SignVote(chainID, vB)
|
||||
err = pv.SignVote(chainID, vB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
voteB.Signature = vB.Signature
|
||||
return NewDuplicateVoteEvidence(voteA, voteB, time, NewValidatorSet([]*Validator{val}))
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence {
|
||||
|
||||
func TestDuplicateVoteEvidence(t *testing.T) {
|
||||
const height = int64(13)
|
||||
ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id")
|
||||
ev, err := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes()))
|
||||
assert.NotNil(t, ev.String())
|
||||
assert.Equal(t, ev.Height(), height)
|
||||
@@ -82,7 +83,8 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) {
|
||||
vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime)
|
||||
vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime)
|
||||
valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(10)})
|
||||
ev := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet)
|
||||
ev, err := NewDuplicateVoteEvidence(vote1, vote2, defaultVoteTime, valSet)
|
||||
require.NoError(t, err)
|
||||
tc.malleateEvidence(ev)
|
||||
assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result")
|
||||
})
|
||||
@@ -224,7 +226,8 @@ func TestLightClientAttackEvidenceValidation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockEvidenceValidateBasic(t *testing.T) {
|
||||
goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id")
|
||||
goodEvidence, err := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, goodEvidence.ValidateBasic())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user