github.com/Finschia/ostracon@v1.1.5/types/vote_test.go (about) 1 package types 2 3 import ( 4 "math" 5 "testing" 6 "time" 7 8 "github.com/gogo/protobuf/proto" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 13 14 "github.com/Finschia/ostracon/crypto" 15 "github.com/Finschia/ostracon/crypto/ed25519" 16 "github.com/Finschia/ostracon/crypto/tmhash" 17 "github.com/Finschia/ostracon/libs/protoio" 18 ) 19 20 func examplePrevote() *Vote { 21 return exampleVote(byte(tmproto.PrevoteType)) 22 } 23 24 func examplePrecommit() *Vote { 25 return exampleVote(byte(tmproto.PrecommitType)) 26 } 27 28 func exampleVote(t byte) *Vote { 29 var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z") 30 if err != nil { 31 panic(err) 32 } 33 34 return &Vote{ 35 Type: tmproto.SignedMsgType(t), 36 Height: 12345, 37 Round: 2, 38 Timestamp: stamp, 39 BlockID: BlockID{ 40 Hash: tmhash.Sum([]byte("blockID_hash")), 41 PartSetHeader: PartSetHeader{ 42 Total: 1000000, 43 Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), 44 }, 45 }, 46 ValidatorAddress: crypto.AddressHash([]byte("validator_address")), 47 ValidatorIndex: 56789, 48 } 49 } 50 51 func TestVoteSignable(t *testing.T) { 52 vote := examplePrecommit() 53 v := vote.ToProto() 54 signBytes := VoteSignBytes("test_chain_id", v) 55 pb := CanonicalizeVote("test_chain_id", v) 56 expected, err := protoio.MarshalDelimited(&pb) 57 require.NoError(t, err) 58 59 require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.") 60 } 61 62 func TestVoteSignBytesTestVectors(t *testing.T) { 63 64 tests := []struct { 65 chainID string 66 vote *Vote 67 want []byte 68 }{ 69 0: { 70 "", &Vote{}, 71 // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. 72 []byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 73 }, 74 // with proper (fixed size) height and round (PreCommit): 75 1: { 76 "", &Vote{Height: 1, Round: 1, Type: tmproto.PrecommitType}, 77 []byte{ 78 0x21, // length 79 0x8, // (field_number << 3) | wire_type 80 0x2, // PrecommitType 81 0x11, // (field_number << 3) | wire_type 82 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 83 0x19, // (field_number << 3) | wire_type 84 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 85 0x2a, // (field_number << 3) | wire_type 86 // remaining fields (timestamp): 87 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 88 }, 89 // with proper (fixed size) height and round (PreVote): 90 2: { 91 "", &Vote{Height: 1, Round: 1, Type: tmproto.PrevoteType}, 92 []byte{ 93 0x21, // length 94 0x8, // (field_number << 3) | wire_type 95 0x1, // PrevoteType 96 0x11, // (field_number << 3) | wire_type 97 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 98 0x19, // (field_number << 3) | wire_type 99 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 100 0x2a, // (field_number << 3) | wire_type 101 // remaining fields (timestamp): 102 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 103 }, 104 3: { 105 "", &Vote{Height: 1, Round: 1}, 106 []byte{ 107 0x1f, // length 108 0x11, // (field_number << 3) | wire_type 109 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 110 0x19, // (field_number << 3) | wire_type 111 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 112 // remaining fields (timestamp): 113 0x2a, 114 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 115 }, 116 // containing non-empty chain_id: 117 4: { 118 "test_chain_id", &Vote{Height: 1, Round: 1}, 119 []byte{ 120 0x2e, // length 121 0x11, // (field_number << 3) | wire_type 122 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 123 0x19, // (field_number << 3) | wire_type 124 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 125 // remaining fields: 126 0x2a, // (field_number << 3) | wire_type 127 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp 128 // (field_number << 3) | wire_type 129 0x32, 130 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID 131 }, 132 } 133 for i, tc := range tests { 134 v := tc.vote.ToProto() 135 got := VoteSignBytes(tc.chainID, v) 136 assert.Equal(t, len(tc.want), len(got), "test case #%v: got unexpected sign bytes length for Vote.", i) 137 assert.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i) 138 } 139 } 140 141 func TestVoteProposalNotEq(t *testing.T) { 142 cv := CanonicalizeVote("", &tmproto.Vote{Height: 1, Round: 1}) 143 p := CanonicalizeProposal("", &tmproto.Proposal{Height: 1, Round: 1}) 144 vb, err := proto.Marshal(&cv) 145 require.NoError(t, err) 146 pb, err := proto.Marshal(&p) 147 require.NoError(t, err) 148 require.NotEqual(t, vb, pb) 149 } 150 151 func TestVoteVerifySignature(t *testing.T) { 152 privVal := NewMockPV() 153 pubkey, err := privVal.GetPubKey() 154 require.NoError(t, err) 155 156 vote := examplePrecommit() 157 v := vote.ToProto() 158 signBytes := VoteSignBytes("test_chain_id", v) 159 160 // sign it 161 err = privVal.SignVote("test_chain_id", v) 162 require.NoError(t, err) 163 164 // verify the same vote 165 valid := pubkey.VerifySignature(VoteSignBytes("test_chain_id", v), v.Signature) 166 require.True(t, valid) 167 168 // serialize, deserialize and verify again.... 169 precommit := new(tmproto.Vote) 170 bs, err := proto.Marshal(v) 171 require.NoError(t, err) 172 err = proto.Unmarshal(bs, precommit) 173 require.NoError(t, err) 174 175 // verify the transmitted vote 176 newSignBytes := VoteSignBytes("test_chain_id", precommit) 177 require.Equal(t, string(signBytes), string(newSignBytes)) 178 valid = pubkey.VerifySignature(newSignBytes, precommit.Signature) 179 require.True(t, valid) 180 } 181 182 func TestIsVoteTypeValid(t *testing.T) { 183 tc := []struct { 184 name string 185 in tmproto.SignedMsgType 186 out bool 187 }{ 188 {"Prevote", tmproto.PrevoteType, true}, 189 {"Precommit", tmproto.PrecommitType, true}, 190 {"InvalidType", tmproto.SignedMsgType(0x3), false}, 191 } 192 193 for _, tt := range tc { 194 tt := tt 195 t.Run(tt.name, func(st *testing.T) { 196 if rs := IsVoteTypeValid(tt.in); rs != tt.out { 197 t.Errorf("got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out) 198 } 199 }) 200 } 201 } 202 203 func TestVoteVerify(t *testing.T) { 204 privVal := NewMockPV() 205 pubkey, err := privVal.GetPubKey() 206 require.NoError(t, err) 207 208 vote := examplePrevote() 209 vote.ValidatorAddress = pubkey.Address() 210 211 err = vote.Verify("test_chain_id", ed25519.GenPrivKey().PubKey()) 212 if assert.Error(t, err) { 213 assert.Equal(t, ErrVoteInvalidValidatorAddress, err) 214 } 215 216 err = vote.Verify("test_chain_id", pubkey) 217 if assert.Error(t, err) { 218 assert.Equal(t, ErrVoteInvalidSignature, err) 219 } 220 } 221 222 func TestMaxVoteBytes(t *testing.T) { 223 // time is varint encoded so need to pick the max. 224 // year int, month Month, day, hour, min, sec, nsec int, loc *Location 225 timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) 226 vote := &Vote{ 227 ValidatorAddress: crypto.AddressHash([]byte("validator_address")), 228 ValidatorIndex: math.MaxInt32, 229 Height: math.MaxInt64, 230 Round: math.MaxInt32, 231 Timestamp: timestamp, 232 Type: tmproto.ProposalType, 233 BlockID: BlockID{ 234 Hash: tmhash.Sum([]byte("blockID_hash")), 235 PartSetHeader: PartSetHeader{ 236 Total: math.MaxInt32, 237 Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), 238 }, 239 }, 240 } 241 242 privVal := NewMockPV() 243 pbVote := vote.ToProto() 244 err := privVal.SignVote("test_chain_id", pbVote) 245 require.NoError(t, err) 246 247 bz, err := pbVote.Marshal() 248 require.NoError(t, err) 249 assert.Equal(t, MaxVoteBytes, int64(len(bz))) 250 } 251 252 func TestVoteString(t *testing.T) { 253 str := examplePrecommit().String() 254 expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests 255 if str != expected { 256 t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str) 257 } 258 259 str2 := examplePrevote().String() 260 expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests 261 if str2 != expected { 262 t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2) 263 } 264 } 265 266 func TestVoteValidateBasic(t *testing.T) { 267 privVal := NewMockPV() 268 269 testCases := []struct { 270 testName string 271 malleateVote func(*Vote) 272 expectErr bool 273 }{ 274 {"Good Vote", func(v *Vote) {}, false}, 275 {"Negative Height", func(v *Vote) { v.Height = -1 }, true}, 276 {"Negative Round", func(v *Vote) { v.Round = -1 }, true}, 277 {"Invalid BlockID", func(v *Vote) { 278 v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} 279 }, true}, 280 {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true}, 281 {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true}, 282 {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true}, 283 {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true}, 284 } 285 for _, tc := range testCases { 286 tc := tc 287 t.Run(tc.testName, func(t *testing.T) { 288 vote := examplePrecommit() 289 v := vote.ToProto() 290 err := privVal.SignVote("test_chain_id", v) 291 vote.Signature = v.Signature 292 require.NoError(t, err) 293 tc.malleateVote(vote) 294 assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result") 295 }) 296 } 297 } 298 299 func TestVoteProtobuf(t *testing.T) { 300 privVal := NewMockPV() 301 vote := examplePrecommit() 302 v := vote.ToProto() 303 err := privVal.SignVote("test_chain_id", v) 304 vote.Signature = v.Signature 305 require.NoError(t, err) 306 307 testCases := []struct { 308 msg string 309 v1 *Vote 310 expPass bool 311 }{ 312 {"success", vote, true}, 313 {"fail vote validate basic", &Vote{}, false}, 314 {"failure nil", nil, false}, 315 } 316 for _, tc := range testCases { 317 protoProposal := tc.v1.ToProto() 318 319 v, err := VoteFromProto(protoProposal) 320 if tc.expPass { 321 require.NoError(t, err) 322 require.Equal(t, tc.v1, v, tc.msg) 323 } else { 324 require.Error(t, err) 325 } 326 } 327 }