You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

424 lines
15 KiB

package types
import (
"context"
"encoding/hex"
"math"
mrand "math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/tmhash"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/version"
)
var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
func TestEvidenceList(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ev := randomDuplicateVoteEvidence(ctx, t)
evl := EvidenceList([]Evidence{ev})
assert.NotNil(t, evl.Hash())
assert.True(t, evl.Has(ev))
assert.False(t, evl.Has(&DuplicateVoteEvidence{}))
}
func randomDuplicateVoteEvidence(ctx context.Context, t *testing.T) *DuplicateVoteEvidence {
t.Helper()
val := NewMockPV()
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
const chainID = "mychain"
return &DuplicateVoteEvidence{
VoteA: makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime),
VoteB: makeVote(ctx, t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime.Add(1*time.Minute)),
TotalVotingPower: 30,
ValidatorPower: 10,
Timestamp: defaultVoteTime,
}
}
func TestDuplicateVoteEvidence(t *testing.T) {
const height = int64(13)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ev, err := NewMockDuplicateVoteEvidence(ctx, 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)
}
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"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
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(ctx, 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(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime)
vote2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime)
valSet := NewValidatorSet([]*Validator{val.ExtractIntoValidator(ctx, 10)})
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")
})
}
}
func TestLightClientAttackEvidenceBasic(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
height := int64(5)
commonHeight := height - 1
nValidators := 10
voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1)
header := makeHeaderRandom()
header.Height = height
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, defaultVoteTime)
require.NoError(t, err)
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: commonHeight,
TotalVotingPower: valSet.TotalVotingPower(),
Timestamp: header.Time,
ByzantineValidators: valSet.Validators[:nValidators/2],
}
assert.NotNil(t, lcae.String())
assert.NotNil(t, lcae.Hash())
assert.Equal(t, lcae.Height(), commonHeight) // Height should be the common Height
assert.NotNil(t, lcae.Bytes())
// maleate evidence to test hash uniqueness
testCases := []struct {
testName string
malleateEvidence func(*LightClientAttackEvidence)
}{
{"Different header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = makeHeaderRandom() }},
{"Different common height", func(ev *LightClientAttackEvidence) {
ev.CommonHeight = height + 1
}},
}
for _, tc := range testCases {
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: commonHeight,
TotalVotingPower: valSet.TotalVotingPower(),
Timestamp: header.Time,
ByzantineValidators: valSet.Validators[:nValidators/2],
}
hash := lcae.Hash()
tc.malleateEvidence(lcae)
assert.NotEqual(t, hash, lcae.Hash(), tc.testName)
}
}
func TestLightClientAttackEvidenceValidation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
height := int64(5)
commonHeight := height - 1
nValidators := 10
voteSet, valSet, privVals := randVoteSet(ctx, t, height, 1, tmproto.PrecommitType, nValidators, 1)
header := makeHeaderRandom()
header.Height = height
header.ValidatorsHash = valSet.Hash()
blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash")))
commit, err := makeCommit(ctx, blockID, height, 1, voteSet, privVals, time.Now())
require.NoError(t, err)
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: commonHeight,
TotalVotingPower: valSet.TotalVotingPower(),
Timestamp: header.Time,
ByzantineValidators: valSet.Validators[:nValidators/2],
}
assert.NoError(t, lcae.ValidateBasic())
testCases := []struct {
testName string
malleateEvidence func(*LightClientAttackEvidence)
expectErr bool
}{
{"Good LightClientAttackEvidence", func(ev *LightClientAttackEvidence) {}, false},
{"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true},
{"Height is greater than divergent block", func(ev *LightClientAttackEvidence) {
ev.CommonHeight = height + 1
}, true},
{"Height is equal to the divergent block", func(ev *LightClientAttackEvidence) {
ev.CommonHeight = height
}, false},
{"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true},
{"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true},
{"Nil validator set", func(ev *LightClientAttackEvidence) {
ev.ConflictingBlock.ValidatorSet = &ValidatorSet{}
}, true},
{"Negative total voting power", func(ev *LightClientAttackEvidence) {
ev.TotalVotingPower = -1
}, true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: commonHeight,
TotalVotingPower: valSet.TotalVotingPower(),
Timestamp: header.Time,
ByzantineValidators: valSet.Validators[:nValidators/2],
}
tc.malleateEvidence(lcae)
if tc.expectErr {
assert.Error(t, lcae.ValidateBasic(), tc.testName)
} else {
assert.NoError(t, lcae.ValidateBasic(), tc.testName)
}
})
}
}
func TestMockEvidenceValidateBasic(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
goodEvidence, err := NewMockDuplicateVoteEvidence(ctx, int64(1), time.Now(), "mock-chain-id")
require.NoError(t, err)
assert.Nil(t, goodEvidence.ValidateBasic())
}
func makeVote(
ctx context.Context,
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(ctx)
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(ctx, chainID, vpb)
require.NoError(t, err)
v.Signature = vpb.Signature
return v
}
func makeHeaderRandom() *Header {
return &Header{
Version: version.Consensus{Block: version.BlockProtocol, App: 1},
ChainID: tmrand.Str(12),
Height: int64(mrand.Uint32() + 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) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// -------- 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(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
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)
})
}
}
func TestEvidenceVectors(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Votes for duplicateEvidence
val := NewMockPV()
val.PrivKey = ed25519.GenPrivKeyFromSecret([]byte("it's a secret")) // deterministic key
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(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, defaultVoteTime)
v2 := makeVote(ctx, t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, defaultVoteTime)
// Data for LightClientAttackEvidence
height := int64(5)
commonHeight := height - 1
nValidators := 10
voteSet, valSet, privVals := deterministicVoteSet(ctx, t, height, 1, tmproto.PrecommitType, 1)
header := &Header{
Version: version.Consensus{Block: 1, App: 1},
ChainID: chainID,
Height: height,
Time: time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC),
LastBlockID: BlockID{},
LastCommitHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
DataHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
ValidatorsHash: valSet.Hash(),
NextValidatorsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
ConsensusHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
AppHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
LastResultsHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
EvidenceHash: []byte("f2564c78071e26643ae9b3e2a19fa0dc10d4d9e873aa0be808660123f11a1e78"),
ProposerAddress: []byte("2915b7b15f979e48ebc61774bb1d86ba3136b7eb"),
}
blockID3 := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash")))
commit, err := makeCommit(ctx, blockID3, height, 1, voteSet, privVals, defaultVoteTime)
require.NoError(t, err)
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: commonHeight,
TotalVotingPower: valSet.TotalVotingPower(),
Timestamp: header.Time,
ByzantineValidators: valSet.Validators[:nValidators/2],
}
// assert.NoError(t, lcae.ValidateBasic())
testCases := []struct {
testName string
evList EvidenceList
expBytes string
}{
{"duplicateVoteEvidence",
EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}},
"a9ce28d13bb31001fc3e5b7927051baf98f86abdbd64377643a304164c826923",
},
{"LightClientAttackEvidence",
EvidenceList{lcae},
"2f8782163c3905b26e65823ababc977fe54e97b94e60c0360b1e4726b668bb8e",
},
{"LightClientAttackEvidence & DuplicateVoteEvidence",
EvidenceList{&DuplicateVoteEvidence{VoteA: v2, VoteB: v}, lcae},
"eedb4b47d6dbc9d43f53da8aa50bb826e8d9fc7d897da777c8af6a04aa74163e",
},
}
for _, tc := range testCases {
tc := tc
hash := tc.evList.Hash()
require.Equal(t, tc.expBytes, hex.EncodeToString(hash), tc.testName)
}
}