package types import ( "math" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" tmtime "github.com/tendermint/tendermint/types/time" ) func examplePrevote() *Vote { return exampleVote(byte(PrevoteType)) } func examplePrecommit() *Vote { return exampleVote(byte(PrecommitType)) } func exampleVote(t byte) *Vote { var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z") if err != nil { panic(err) } return &Vote{ ValidatorAddress: tmhash.Sum([]byte("validator_address")), ValidatorIndex: 56789, Height: 12345, Round: 2, Timestamp: stamp, Type: SignedMsgType(t), BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), PartsHeader: PartSetHeader{ Total: 1000000, Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, }, } } func TestVoteSignable(t *testing.T) { vote := examplePrecommit() signBytes := vote.SignBytes("test_chain_id") expected, err := cdc.MarshalBinary(CanonicalizeVote("test_chain_id", vote)) require.NoError(t, err) require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.") } func TestVoteSignableTestVectors(t *testing.T) { voteWithVersion := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) voteWithVersion.Version = 123 tests := []struct { canonicalVote CanonicalVote want []byte }{ { CanonicalizeVote("", &Vote{}), // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. []byte{0xb, 0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // with proper (fixed size) height and round (PreCommit): { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}), []byte{ 0x1f, // total length 0x11, // (field_number << 3) | wire_type (version is missing) 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 0x20, // (field_number << 3) | wire_type 0x2, // PrecommitType 0x2a, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // with proper (fixed size) height and round (PreVote): { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}), []byte{ 0x1f, // total length 0x11, // (field_number << 3) | wire_type (version is missing) 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 0x20, // (field_number << 3) | wire_type 0x1, // PrevoteType 0x2a, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // containing version (empty type) { voteWithVersion, []byte{ 0x26, // total length 0x9, // (field_number << 3) | wire_type 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // version (123) 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, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // containing non-empty chain_id: { CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}), []byte{ 0x2c, // total 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 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff, // timestamp 0x3a, // (field_number << 3) | wire_type 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID }, } for i, tc := range tests { got, err := cdc.MarshalBinary(tc.canonicalVote) require.NoError(t, err) require.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i) } } func TestVoteProposalNotEq(t *testing.T) { cv := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) p := CanonicalizeProposal("", &Proposal{Height: 1, Round: 1}) vb, err := cdc.MarshalBinary(cv) require.NoError(t, err) pb, err := cdc.MarshalBinary(p) require.NoError(t, err) require.NotEqual(t, vb, pb) } func TestVoteVerifySignature(t *testing.T) { privVal := NewMockPV() pubkey := privVal.GetPubKey() vote := examplePrecommit() signBytes := vote.SignBytes("test_chain_id") // sign it err := privVal.SignVote("test_chain_id", vote) require.NoError(t, err) // verify the same vote valid := pubkey.VerifyBytes(vote.SignBytes("test_chain_id"), vote.Signature) require.True(t, valid) // serialize, deserialize and verify again.... precommit := new(Vote) bs, err := cdc.MarshalBinary(vote) require.NoError(t, err) err = cdc.UnmarshalBinary(bs, &precommit) require.NoError(t, err) // verify the transmitted vote newSignBytes := precommit.SignBytes("test_chain_id") require.Equal(t, string(signBytes), string(newSignBytes)) valid = pubkey.VerifyBytes(newSignBytes, precommit.Signature) require.True(t, valid) } func TestIsVoteTypeValid(t *testing.T) { tc := []struct { name string in SignedMsgType out bool }{ {"Prevote", PrevoteType, true}, {"Precommit", PrecommitType, true}, {"InvalidType", 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) { privVal := NewMockPV() pubkey := privVal.GetPubKey() vote := examplePrevote() 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 TestMaxVoteBytes(t *testing.T) { vote := &Vote{ ValidatorAddress: tmhash.Sum([]byte("validator_address")), ValidatorIndex: math.MaxInt64, Height: math.MaxInt64, Round: math.MaxInt64, Timestamp: tmtime.Now(), Type: PrevoteType, BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), PartsHeader: PartSetHeader{ Total: math.MaxInt64, Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, }, } privVal := NewMockPV() err := privVal.SignVote("test_chain_id", vote) require.NoError(t, err) bz, err := cdc.MarshalBinary(vote) require.NoError(t, err) assert.EqualValues(t, MaxVoteBytes, len(bz)) }