- package types
-
- import (
- "context"
- "testing"
- "time"
-
- "github.com/gogo/protobuf/proto"
- "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"
- "github.com/tendermint/tendermint/internal/libs/protoio"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
- )
-
- func examplePrevote(t *testing.T) *Vote {
- t.Helper()
- return exampleVote(t, byte(tmproto.PrevoteType))
- }
-
- func examplePrecommit(t testing.TB) *Vote {
- t.Helper()
- return exampleVote(t, byte(tmproto.PrecommitType))
- }
-
- func exampleVote(tb testing.TB, t byte) *Vote {
- tb.Helper()
- var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z")
- require.NoError(tb, err)
-
- return &Vote{
- Type: tmproto.SignedMsgType(t),
- Height: 12345,
- Round: 2,
- Timestamp: stamp,
- BlockID: BlockID{
- Hash: tmhash.Sum([]byte("blockID_hash")),
- PartSetHeader: PartSetHeader{
- Total: 1000000,
- Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")),
- },
- },
- ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
- ValidatorIndex: 56789,
- }
- }
- func TestVoteSignable(t *testing.T) {
- vote := examplePrecommit(t)
- v := vote.ToProto()
- signBytes := VoteSignBytes("test_chain_id", v)
- pb := CanonicalizeVote("test_chain_id", v)
- expected, err := protoio.MarshalDelimited(&pb)
- require.NoError(t, err)
-
- require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.")
- }
-
- func TestVoteSignBytesTestVectors(t *testing.T) {
-
- tests := []struct {
- chainID string
- vote *Vote
- want []byte
- }{
- 0: {
- "", &Vote{},
- // NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
- []byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
- },
- // with proper (fixed size) height and round (PreCommit):
- 1: {
- "", &Vote{Height: 1, Round: 1, Type: tmproto.PrecommitType},
- []byte{
- 0x21, // length
- 0x8, // (field_number << 3) | wire_type
- 0x2, // PrecommitType
- 0x11, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
- 0x19, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
- 0x2a, // (field_number << 3) | wire_type
- // remaining fields (timestamp):
- 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
- },
- // with proper (fixed size) height and round (PreVote):
- 2: {
- "", &Vote{Height: 1, Round: 1, Type: tmproto.PrevoteType},
- []byte{
- 0x21, // length
- 0x8, // (field_number << 3) | wire_type
- 0x1, // PrevoteType
- 0x11, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
- 0x19, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
- 0x2a, // (field_number << 3) | wire_type
- // remaining fields (timestamp):
- 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
- },
- 3: {
- "", &Vote{Height: 1, Round: 1},
- []byte{
- 0x1f, // length
- 0x11, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
- 0x19, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
- // remaining fields (timestamp):
- 0x2a,
- 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
- },
- // containing non-empty chain_id:
- 4: {
- "test_chain_id", &Vote{Height: 1, Round: 1},
- []byte{
- 0x2e, // length
- 0x11, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
- 0x19, // (field_number << 3) | wire_type
- 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
- // remaining fields:
- 0x2a, // (field_number << 3) | wire_type
- 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
- // (field_number << 3) | wire_type
- 0x32,
- 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
- },
- }
- for i, tc := range tests {
- v := tc.vote.ToProto()
- got := VoteSignBytes(tc.chainID, v)
- assert.Equal(t, len(tc.want), len(got), "test case #%v: got unexpected sign bytes length for Vote.", i)
- assert.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i)
- }
- }
-
- func TestVoteProposalNotEq(t *testing.T) {
- cv := CanonicalizeVote("", &tmproto.Vote{Height: 1, Round: 1})
- p := CanonicalizeProposal("", &tmproto.Proposal{Height: 1, Round: 1})
- vb, err := proto.Marshal(&cv)
- require.NoError(t, err)
- pb, err := proto.Marshal(&p)
- require.NoError(t, err)
- require.NotEqual(t, vb, pb)
- }
-
- func TestVoteVerifySignature(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
- pubkey, err := privVal.GetPubKey(ctx)
- require.NoError(t, err)
-
- vote := examplePrecommit(t)
- v := vote.ToProto()
- signBytes := VoteSignBytes("test_chain_id", v)
-
- // sign it
- err = privVal.SignVote(ctx, "test_chain_id", v)
- require.NoError(t, err)
-
- // verify the same vote
- valid := pubkey.VerifySignature(VoteSignBytes("test_chain_id", v), v.Signature)
- require.True(t, valid)
-
- // serialize, deserialize and verify again....
- precommit := new(tmproto.Vote)
- bs, err := proto.Marshal(v)
- require.NoError(t, err)
- err = proto.Unmarshal(bs, precommit)
- require.NoError(t, err)
-
- // verify the transmitted vote
- newSignBytes := VoteSignBytes("test_chain_id", precommit)
- require.Equal(t, string(signBytes), string(newSignBytes))
- valid = pubkey.VerifySignature(newSignBytes, precommit.Signature)
- require.True(t, valid)
- }
-
- func TestIsVoteTypeValid(t *testing.T) {
- tc := []struct {
- name string
- in tmproto.SignedMsgType
- out bool
- }{
- {"Prevote", tmproto.PrevoteType, true},
- {"Precommit", tmproto.PrecommitType, true},
- {"InvalidType", tmproto.SignedMsgType(0x3), false},
- }
-
- for _, tt := range tc {
- tt := tt
- t.Run(tt.name, func(st *testing.T) {
- if rs := IsVoteTypeValid(tt.in); rs != tt.out {
- t.Errorf("got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out)
- }
- })
- }
- }
-
- func TestVoteVerify(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
- pubkey, err := privVal.GetPubKey(ctx)
- require.NoError(t, err)
-
- vote := examplePrevote(t)
- vote.ValidatorAddress = pubkey.Address()
-
- err = vote.Verify("test_chain_id", ed25519.GenPrivKey().PubKey())
- if assert.Error(t, err) {
- assert.Equal(t, ErrVoteInvalidValidatorAddress, err)
- }
-
- err = vote.Verify("test_chain_id", pubkey)
- if assert.Error(t, err) {
- assert.Equal(t, ErrVoteInvalidSignature, err)
- }
- }
-
- func TestVoteString(t *testing.T) {
- str := examplePrecommit(t).String()
- expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}`
- if str != expected {
- t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str)
- }
-
- str2 := examplePrevote(t).String()
- expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}`
- if str2 != expected {
- t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2)
- }
- }
-
- func TestVoteValidateBasic(t *testing.T) {
- privVal := NewMockPV()
-
- testCases := []struct {
- testName string
- malleateVote func(*Vote)
- expectErr bool
- }{
- {"Good Vote", func(v *Vote) {}, false},
- {"Negative Height", func(v *Vote) { v.Height = -1 }, true},
- {"Negative Round", func(v *Vote) { v.Round = -1 }, true},
- {"Invalid BlockID", func(v *Vote) {
- v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}
- }, true},
- {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true},
- {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true},
- {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true},
- {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true},
- }
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- vote := examplePrecommit(t)
- v := vote.ToProto()
- err := privVal.SignVote(ctx, "test_chain_id", v)
- vote.Signature = v.Signature
- require.NoError(t, err)
- tc.malleateVote(vote)
- assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result")
- })
- }
- }
-
- func TestVoteProtobuf(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
- vote := examplePrecommit(t)
- v := vote.ToProto()
- err := privVal.SignVote(ctx, "test_chain_id", v)
- vote.Signature = v.Signature
- require.NoError(t, err)
-
- testCases := []struct {
- msg string
- v1 *Vote
- expPass bool
- }{
- {"success", vote, true},
- {"fail vote validate basic", &Vote{}, false},
- {"failure nil", nil, false},
- }
- for _, tc := range testCases {
- protoProposal := tc.v1.ToProto()
-
- v, err := VoteFromProto(protoProposal)
- if tc.expPass {
- require.NoError(t, err)
- require.Equal(t, tc.v1, v, tc.msg)
- } else {
- require.Error(t, err)
- }
- }
- }
-
- var sink interface{}
-
- func getSampleCommit(ctx context.Context, t testing.TB) *Commit {
- t.Helper()
-
- lastID := makeBlockIDRandom()
- voteSet, _, vals := randVoteSet(ctx, t, 2, 1, tmproto.PrecommitType, 10, 1)
- commit, err := makeCommit(ctx, lastID, 2, 1, voteSet, vals, time.Now())
-
- require.NoError(t, err)
-
- return commit
- }
-
- func BenchmarkVoteSignBytes(b *testing.B) {
- protoVote := examplePrecommit(b).ToProto()
-
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- sink = VoteSignBytes("test_chain_id", protoVote)
- }
-
- if sink == nil {
- b.Fatal("Benchmark did not run")
- }
-
- // Reset the sink.
- sink = (interface{})(nil)
- }
-
- func BenchmarkCommitVoteSignBytes(b *testing.B) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- sampleCommit := getSampleCommit(ctx, b)
-
- b.ReportAllocs()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- for index := range sampleCommit.Signatures {
- sink = sampleCommit.VoteSignBytes("test_chain_id", int32(index))
- }
- }
-
- if sink == nil {
- b.Fatal("Benchmark did not run")
- }
-
- // Reset the sink.
- sink = (interface{})(nil)
- }
|