|
package types
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
tmrand "github.com/tendermint/tendermint/libs/rand"
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
)
|
|
|
|
type voteData struct {
|
|
vote1 *Vote
|
|
vote2 *Vote
|
|
valid bool
|
|
}
|
|
|
|
var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
func TestDuplicateVoteEvidence(t *testing.T) {
|
|
val := NewMockPV()
|
|
val2 := NewMockPV()
|
|
|
|
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
|
|
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
|
|
blockID3 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash"))
|
|
blockID4 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash2"))
|
|
|
|
const chainID = "mychain"
|
|
|
|
vote1 := makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime)
|
|
v1 := vote1.ToProto()
|
|
err := val.SignVote(chainID, v1)
|
|
require.NoError(t, err)
|
|
badVote := makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime)
|
|
bv := badVote.ToProto()
|
|
err = val2.SignVote(chainID, bv)
|
|
require.NoError(t, err)
|
|
|
|
vote1.Signature = v1.Signature
|
|
badVote.Signature = bv.Signature
|
|
|
|
cases := []voteData{
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime), true}, // different block ids
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID3, defaultVoteTime), true},
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID4, defaultVoteTime), true},
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), false}, // wrong block id
|
|
{vote1, makeVote(t, val, "mychain2", 0, 10, 2, 1, blockID2, defaultVoteTime), false}, // wrong chain id
|
|
{vote1, makeVote(t, val, chainID, 0, 11, 2, 1, blockID2, defaultVoteTime), false}, // wrong height
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 3, 1, blockID2, defaultVoteTime), false}, // wrong round
|
|
{vote1, makeVote(t, val, chainID, 0, 10, 2, 2, blockID2, defaultVoteTime), false}, // wrong step
|
|
{vote1, makeVote(t, val2, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), false}, // wrong validator
|
|
{vote1, makeVote(t, val2, chainID, 0, 10, 2, 1, blockID, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), false},
|
|
{vote1, badVote, false}, // signed by wrong key
|
|
}
|
|
|
|
pubKey, err := val.GetPubKey()
|
|
require.NoError(t, err)
|
|
for _, c := range cases {
|
|
ev := &DuplicateVoteEvidence{
|
|
VoteA: c.vote1,
|
|
VoteB: c.vote2,
|
|
|
|
Timestamp: defaultVoteTime,
|
|
}
|
|
if c.valid {
|
|
assert.Nil(t, ev.Verify(chainID, pubKey), "evidence should be valid")
|
|
} else {
|
|
assert.NotNil(t, ev.Verify(chainID, pubKey), "evidence should be invalid")
|
|
}
|
|
}
|
|
|
|
ev := randomDuplicatedVoteEvidence(t)
|
|
|
|
assert.True(t, ev.Equal(ev))
|
|
assert.False(t, ev.Equal(&DuplicateVoteEvidence{}))
|
|
|
|
}
|
|
|
|
func TestEvidenceList(t *testing.T) {
|
|
ev := randomDuplicatedVoteEvidence(t)
|
|
evl := EvidenceList([]Evidence{ev})
|
|
|
|
assert.NotNil(t, evl.Hash())
|
|
assert.True(t, evl.Has(ev))
|
|
assert.False(t, evl.Has(&DuplicateVoteEvidence{}))
|
|
}
|
|
|
|
func TestMaxEvidenceBytes(t *testing.T) {
|
|
val := NewMockPV()
|
|
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
maxTime := time.Date(9999, 0, 0, 0, 0, 0, 0, time.UTC)
|
|
const chainID = "mychain"
|
|
ev := &DuplicateVoteEvidence{
|
|
VoteA: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, math.MaxInt64, blockID, maxTime),
|
|
VoteB: makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, math.MaxInt64, blockID2, maxTime),
|
|
}
|
|
|
|
//TODO: Add other types of evidence to test and set MaxEvidenceBytes accordingly
|
|
|
|
testCases := []struct {
|
|
testName string
|
|
evidence Evidence
|
|
}{
|
|
{"DuplicateVote", ev},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
pb, err := EvidenceToProto(tt.evidence)
|
|
require.NoError(t, err, tt.testName)
|
|
bz, err := pb.Marshal()
|
|
require.NoError(t, err, tt.testName)
|
|
|
|
assert.LessOrEqual(t, int64(len(bz)), MaxEvidenceBytes, tt.testName)
|
|
}
|
|
|
|
}
|
|
|
|
func randomDuplicatedVoteEvidence(t *testing.T) *DuplicateVoteEvidence {
|
|
val := NewMockPV()
|
|
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
|
|
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
|
|
const chainID = "mychain"
|
|
return &DuplicateVoteEvidence{
|
|
VoteA: makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime),
|
|
VoteB: makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)),
|
|
}
|
|
}
|
|
|
|
func TestDuplicateVoteEvidenceValidation(t *testing.T) {
|
|
val := NewMockPV()
|
|
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
const chainID = "mychain"
|
|
|
|
testCases := []struct {
|
|
testName string
|
|
malleateEvidence func(*DuplicateVoteEvidence)
|
|
expectErr bool
|
|
}{
|
|
{"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false},
|
|
{"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true},
|
|
{"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true},
|
|
{"Nil votes", func(ev *DuplicateVoteEvidence) {
|
|
ev.VoteA = nil
|
|
ev.VoteB = nil
|
|
}, true},
|
|
{"Invalid vote type", func(ev *DuplicateVoteEvidence) {
|
|
ev.VoteA = makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0, blockID2, defaultVoteTime)
|
|
}, true},
|
|
{"Invalid vote order", func(ev *DuplicateVoteEvidence) {
|
|
swap := ev.VoteA.Copy()
|
|
ev.VoteA = ev.VoteB.Copy()
|
|
ev.VoteB = swap
|
|
}, true},
|
|
}
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.testName, func(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)
|
|
ev := NewDuplicateVoteEvidence(vote1, vote2, vote1.Timestamp)
|
|
tc.malleateEvidence(ev)
|
|
assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMockEvidenceValidateBasic(t *testing.T) {
|
|
goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id")
|
|
assert.Nil(t, goodEvidence.ValidateBasic())
|
|
}
|
|
|
|
func makeVote(
|
|
t *testing.T, val PrivValidator, chainID string, valIndex int32, height int64, round int32, step int, blockID BlockID,
|
|
time time.Time) *Vote {
|
|
pubKey, err := val.GetPubKey()
|
|
require.NoError(t, err)
|
|
v := &Vote{
|
|
ValidatorAddress: pubKey.Address(),
|
|
ValidatorIndex: valIndex,
|
|
Height: height,
|
|
Round: round,
|
|
Type: tmproto.SignedMsgType(step),
|
|
BlockID: blockID,
|
|
Timestamp: time,
|
|
}
|
|
|
|
vpb := v.ToProto()
|
|
err = val.SignVote(chainID, vpb)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
v.Signature = vpb.Signature
|
|
return v
|
|
}
|
|
|
|
func makeHeaderRandom() *Header {
|
|
return &Header{
|
|
ChainID: tmrand.Str(12),
|
|
Height: int64(tmrand.Uint16()) + 1,
|
|
Time: time.Now(),
|
|
LastBlockID: makeBlockIDRandom(),
|
|
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 TestEvidenceProto(t *testing.T) {
|
|
// -------- Votes --------
|
|
val := NewMockPV()
|
|
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
|
|
const chainID = "mychain"
|
|
v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
|
|
v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
|
|
|
|
// -------- SignedHeaders --------
|
|
const height int64 = 37
|
|
|
|
var (
|
|
header1 = makeHeaderRandom()
|
|
header2 = makeHeaderRandom()
|
|
)
|
|
|
|
header1.Height = height
|
|
header1.LastBlockID = blockID
|
|
header1.ChainID = chainID
|
|
|
|
header2.Height = height
|
|
header2.LastBlockID = blockID
|
|
header2.ChainID = chainID
|
|
|
|
tests := []struct {
|
|
testName string
|
|
evidence Evidence
|
|
toProtoErr bool
|
|
fromProtoErr bool
|
|
}{
|
|
{"nil fail", nil, true, true},
|
|
{"DuplicateVoteEvidence empty fail", &DuplicateVoteEvidence{}, false, true},
|
|
{"DuplicateVoteEvidence nil voteB", &DuplicateVoteEvidence{VoteA: v, VoteB: nil}, false, true},
|
|
{"DuplicateVoteEvidence nil voteA", &DuplicateVoteEvidence{VoteA: nil, VoteB: v}, false, true},
|
|
{"DuplicateVoteEvidence success", &DuplicateVoteEvidence{VoteA: v2, VoteB: v}, false, false},
|
|
}
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.testName, func(t *testing.T) {
|
|
pb, err := EvidenceToProto(tt.evidence)
|
|
if tt.toProtoErr {
|
|
assert.Error(t, err, tt.testName)
|
|
return
|
|
}
|
|
assert.NoError(t, err, tt.testName)
|
|
|
|
evi, err := EvidenceFromProto(pb)
|
|
if tt.fromProtoErr {
|
|
assert.Error(t, err, tt.testName)
|
|
return
|
|
}
|
|
require.Equal(t, tt.evidence, evi, tt.testName)
|
|
})
|
|
}
|
|
}
|