github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/vote_test.go (about) 1 package types 2 3 import ( 4 "context" 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 "github.com/ari-anchor/sei-tendermint/crypto" 13 "github.com/ari-anchor/sei-tendermint/crypto/ed25519" 14 "github.com/ari-anchor/sei-tendermint/internal/libs/protoio" 15 tmtime "github.com/ari-anchor/sei-tendermint/libs/time" 16 tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 17 ) 18 19 func examplePrevote(t *testing.T) *Vote { 20 t.Helper() 21 return exampleVote(t, byte(tmproto.PrevoteType)) 22 } 23 24 func examplePrecommit(t testing.TB) *Vote { 25 t.Helper() 26 vote := exampleVote(t, byte(tmproto.PrecommitType)) 27 vote.ExtensionSignature = []byte("signature") 28 return vote 29 } 30 31 func exampleVote(tb testing.TB, t byte) *Vote { 32 tb.Helper() 33 var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z") 34 require.NoError(tb, err) 35 36 return &Vote{ 37 Type: tmproto.SignedMsgType(t), 38 Height: 12345, 39 Round: 2, 40 Timestamp: stamp, 41 BlockID: BlockID{ 42 Hash: crypto.Checksum([]byte("blockID_hash")), 43 PartSetHeader: PartSetHeader{ 44 Total: 1000000, 45 Hash: crypto.Checksum([]byte("blockID_part_set_header_hash")), 46 }, 47 }, 48 ValidatorAddress: crypto.AddressHash([]byte("validator_address")), 49 ValidatorIndex: 56789, 50 } 51 } 52 53 func TestVoteSignable(t *testing.T) { 54 vote := examplePrecommit(t) 55 v := vote.ToProto() 56 signBytes := VoteSignBytes("test_chain_id", v) 57 pb := CanonicalizeVote("test_chain_id", v) 58 expected, err := protoio.MarshalDelimited(&pb) 59 require.NoError(t, err) 60 61 require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.") 62 } 63 64 func TestVoteSignBytesTestVectors(t *testing.T) { 65 66 tests := []struct { 67 chainID string 68 vote *Vote 69 want []byte 70 }{ 71 0: { 72 "", &Vote{}, 73 // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. 74 []byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 75 }, 76 // with proper (fixed size) height and round (PreCommit): 77 1: { 78 "", &Vote{Height: 1, Round: 1, Type: tmproto.PrecommitType}, 79 []byte{ 80 0x21, // length 81 0x8, // (field_number << 3) | wire_type 82 0x2, // PrecommitType 83 0x11, // (field_number << 3) | wire_type 84 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 85 0x19, // (field_number << 3) | wire_type 86 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 87 0x2a, // (field_number << 3) | wire_type 88 // remaining fields (timestamp): 89 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 90 }, 91 // with proper (fixed size) height and round (PreVote): 92 2: { 93 "", &Vote{Height: 1, Round: 1, Type: tmproto.PrevoteType}, 94 []byte{ 95 0x21, // length 96 0x8, // (field_number << 3) | wire_type 97 0x1, // PrevoteType 98 0x11, // (field_number << 3) | wire_type 99 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 100 0x19, // (field_number << 3) | wire_type 101 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 102 0x2a, // (field_number << 3) | wire_type 103 // remaining fields (timestamp): 104 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 105 }, 106 3: { 107 "", &Vote{Height: 1, Round: 1}, 108 []byte{ 109 0x1f, // length 110 0x11, // (field_number << 3) | wire_type 111 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 112 0x19, // (field_number << 3) | wire_type 113 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 114 // remaining fields (timestamp): 115 0x2a, 116 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, 117 }, 118 // containing non-empty chain_id: 119 4: { 120 "test_chain_id", &Vote{Height: 1, Round: 1}, 121 []byte{ 122 0x2e, // length 123 0x11, // (field_number << 3) | wire_type 124 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 125 0x19, // (field_number << 3) | wire_type 126 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 127 // remaining fields: 128 0x2a, // (field_number << 3) | wire_type 129 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp 130 // (field_number << 3) | wire_type 131 0x32, 132 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID 133 }, 134 // containing vote extension 135 5: { 136 "test_chain_id", &Vote{ 137 Height: 1, 138 Round: 1, 139 Extension: []byte("extension"), 140 }, 141 []byte{ 142 0x2e, // length 143 0x11, // (field_number << 3) | wire_type 144 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 145 0x19, // (field_number << 3) | wire_type 146 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 147 // remaning fields: 148 0x2a, // (field_number << 3) | wire_type 149 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp 150 // (field_number << 3) | wire_type 151 0x32, 152 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, // chainID 153 }, // chainID 154 }, 155 } 156 for i, tc := range tests { 157 v := tc.vote.ToProto() 158 got := VoteSignBytes(tc.chainID, v) 159 assert.Equal(t, len(tc.want), len(got), "test case #%v: got unexpected sign bytes length for Vote.", i) 160 assert.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i) 161 } 162 } 163 164 func TestVoteProposalNotEq(t *testing.T) { 165 cv := CanonicalizeVote("", &tmproto.Vote{Height: 1, Round: 1}) 166 p := CanonicalizeProposal("", &tmproto.Proposal{Height: 1, Round: 1}) 167 vb, err := proto.Marshal(&cv) 168 require.NoError(t, err) 169 pb, err := proto.Marshal(&p) 170 require.NoError(t, err) 171 require.NotEqual(t, vb, pb) 172 } 173 174 func TestVoteVerifySignature(t *testing.T) { 175 ctx, cancel := context.WithCancel(context.Background()) 176 defer cancel() 177 178 privVal := NewMockPV() 179 pubkey, err := privVal.GetPubKey(ctx) 180 require.NoError(t, err) 181 182 vote := examplePrecommit(t) 183 v := vote.ToProto() 184 signBytes := VoteSignBytes("test_chain_id", v) 185 186 // sign it 187 err = privVal.SignVote(ctx, "test_chain_id", v) 188 require.NoError(t, err) 189 190 // verify the same vote 191 valid := pubkey.VerifySignature(VoteSignBytes("test_chain_id", v), v.Signature) 192 require.True(t, valid) 193 194 // serialize, deserialize and verify again.... 195 precommit := new(tmproto.Vote) 196 bs, err := proto.Marshal(v) 197 require.NoError(t, err) 198 err = proto.Unmarshal(bs, precommit) 199 require.NoError(t, err) 200 201 // verify the transmitted vote 202 newSignBytes := VoteSignBytes("test_chain_id", precommit) 203 require.Equal(t, string(signBytes), string(newSignBytes)) 204 valid = pubkey.VerifySignature(newSignBytes, precommit.Signature) 205 require.True(t, valid) 206 } 207 208 // TestVoteExtension tests that the vote verification behaves correctly in each case 209 // of vote extension being set on the vote. 210 func TestVoteExtension(t *testing.T) { 211 ctx, cancel := context.WithCancel(context.Background()) 212 defer cancel() 213 214 testCases := []struct { 215 name string 216 extension []byte 217 includeSignature bool 218 expectError bool 219 }{ 220 { 221 name: "all fields present", 222 extension: []byte("extension"), 223 includeSignature: true, 224 expectError: false, 225 }, 226 { 227 name: "no extension signature", 228 extension: []byte("extension"), 229 includeSignature: false, 230 expectError: true, 231 }, 232 { 233 name: "empty extension", 234 includeSignature: true, 235 expectError: false, 236 }, 237 { 238 name: "no extension and no signature", 239 includeSignature: false, 240 expectError: true, 241 }, 242 } 243 244 for _, tc := range testCases { 245 t.Run(tc.name, func(t *testing.T) { 246 height, round := int64(1), int32(0) 247 privVal := NewMockPV() 248 pk, err := privVal.GetPubKey(ctx) 249 require.NoError(t, err) 250 blk := Block{} 251 ps, err := blk.MakePartSet(BlockPartSizeBytes) 252 require.NoError(t, err) 253 vote := &Vote{ 254 ValidatorAddress: pk.Address(), 255 ValidatorIndex: 0, 256 Height: height, 257 Round: round, 258 Timestamp: tmtime.Now(), 259 Type: tmproto.PrecommitType, 260 BlockID: BlockID{blk.Hash(), ps.Header()}, 261 } 262 263 v := vote.ToProto() 264 err = privVal.SignVote(ctx, "test_chain_id", v) 265 require.NoError(t, err) 266 vote.Signature = v.Signature 267 if tc.includeSignature { 268 vote.ExtensionSignature = v.ExtensionSignature 269 } 270 err = vote.VerifyExtension("test_chain_id", pk) 271 if tc.expectError { 272 require.Error(t, err) 273 } else { 274 require.NoError(t, err) 275 } 276 }) 277 } 278 } 279 280 func TestIsVoteTypeValid(t *testing.T) { 281 tc := []struct { 282 name string 283 in tmproto.SignedMsgType 284 out bool 285 }{ 286 {"Prevote", tmproto.PrevoteType, true}, 287 {"Precommit", tmproto.PrecommitType, true}, 288 {"InvalidType", tmproto.SignedMsgType(0x3), false}, 289 } 290 291 for _, tt := range tc { 292 tt := tt 293 t.Run(tt.name, func(st *testing.T) { 294 if rs := IsVoteTypeValid(tt.in); rs != tt.out { 295 t.Errorf("got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out) 296 } 297 }) 298 } 299 } 300 301 func TestVoteVerify(t *testing.T) { 302 ctx, cancel := context.WithCancel(context.Background()) 303 defer cancel() 304 305 privVal := NewMockPV() 306 pubkey, err := privVal.GetPubKey(ctx) 307 require.NoError(t, err) 308 309 vote := examplePrevote(t) 310 vote.ValidatorAddress = pubkey.Address() 311 312 err = vote.Verify("test_chain_id", ed25519.GenPrivKey().PubKey()) 313 if assert.Error(t, err) { 314 assert.Equal(t, ErrVoteInvalidValidatorAddress, err) 315 } 316 317 err = vote.Verify("test_chain_id", pubkey) 318 if assert.Error(t, err) { 319 assert.Equal(t, ErrVoteInvalidSignature, err) 320 } 321 } 322 323 func TestVoteString(t *testing.T) { 324 str := examplePrecommit(t).String() 325 expected := `Vote{index=56789:6AF1F4111082EFB388211BC72C55BCD61E9AC3D5 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests 326 if str != expected { 327 t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str) 328 } 329 330 str2 := examplePrevote(t).String() 331 expected = `Vote{index=56789:6AF1F4111082EFB388211BC72C55BCD61E9AC3D5 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests 332 if str2 != expected { 333 t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2) 334 } 335 } 336 337 func signVote(ctx context.Context, t *testing.T, pv PrivValidator, chainID string, vote *Vote) { 338 t.Helper() 339 340 v := vote.ToProto() 341 require.NoError(t, pv.SignVote(ctx, chainID, v)) 342 vote.Signature = v.Signature 343 vote.ExtensionSignature = v.ExtensionSignature 344 } 345 346 func TestValidVotes(t *testing.T) { 347 ctx, cancel := context.WithCancel(context.Background()) 348 defer cancel() 349 privVal := NewMockPV() 350 351 testCases := []struct { 352 name string 353 vote *Vote 354 malleateVote func(*Vote) 355 }{ 356 {"good prevote", examplePrevote(t), func(v *Vote) {}}, 357 {"good precommit without vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = nil }}, 358 {"good precommit with vote extension", examplePrecommit(t), func(v *Vote) { v.Extension = []byte("extension") }}, 359 } 360 for _, tc := range testCases { 361 signVote(ctx, t, privVal, "test_chain_id", tc.vote) 362 tc.malleateVote(tc.vote) 363 require.NoError(t, tc.vote.ValidateBasic(), "ValidateBasic for %s", tc.name) 364 require.NoError(t, tc.vote.EnsureExtension(), "EnsureExtension for %s", tc.name) 365 } 366 } 367 368 func TestInvalidVotes(t *testing.T) { 369 ctx, cancel := context.WithCancel(context.Background()) 370 defer cancel() 371 privVal := NewMockPV() 372 373 testCases := []struct { 374 name string 375 malleateVote func(*Vote) 376 }{ 377 {"negative height", func(v *Vote) { v.Height = -1 }}, 378 {"negative round", func(v *Vote) { v.Round = -1 }}, 379 {"invalid block ID", func(v *Vote) { v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} }}, 380 {"invalid address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }}, 381 {"invalid validator index", func(v *Vote) { v.ValidatorIndex = -1 }}, 382 {"invalid signature", func(v *Vote) { v.Signature = nil }}, 383 {"oversized signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }}, 384 } 385 for _, tc := range testCases { 386 prevote := examplePrevote(t) 387 signVote(ctx, t, privVal, "test_chain_id", prevote) 388 tc.malleateVote(prevote) 389 require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s in invalid prevote", tc.name) 390 require.NoError(t, prevote.EnsureExtension(), "EnsureExtension for %s in invalid prevote", tc.name) 391 392 precommit := examplePrecommit(t) 393 signVote(ctx, t, privVal, "test_chain_id", precommit) 394 tc.malleateVote(precommit) 395 require.Error(t, precommit.ValidateBasic(), "ValidateBasic for %s in invalid precommit", tc.name) 396 require.NoError(t, precommit.EnsureExtension(), "EnsureExtension for %s in invalid precommit", tc.name) 397 } 398 } 399 400 func TestInvalidPrevotes(t *testing.T) { 401 ctx, cancel := context.WithCancel(context.Background()) 402 defer cancel() 403 privVal := NewMockPV() 404 405 testCases := []struct { 406 name string 407 malleateVote func(*Vote) 408 }{ 409 {"vote extension present", func(v *Vote) { v.Extension = []byte("extension") }}, 410 {"vote extension signature present", func(v *Vote) { v.ExtensionSignature = []byte("signature") }}, 411 } 412 for _, tc := range testCases { 413 prevote := examplePrevote(t) 414 signVote(ctx, t, privVal, "test_chain_id", prevote) 415 tc.malleateVote(prevote) 416 require.Error(t, prevote.ValidateBasic(), "ValidateBasic for %s", tc.name) 417 require.NoError(t, prevote.EnsureExtension(), "EnsureExtension for %s", tc.name) 418 } 419 } 420 421 func TestInvalidPrecommitExtensions(t *testing.T) { 422 ctx, cancel := context.WithCancel(context.Background()) 423 defer cancel() 424 privVal := NewMockPV() 425 426 testCases := []struct { 427 name string 428 malleateVote func(*Vote) 429 }{ 430 {"vote extension present without signature", func(v *Vote) { 431 v.Extension = []byte("extension") 432 v.ExtensionSignature = nil 433 }}, 434 {"oversized vote extension signature", func(v *Vote) { v.ExtensionSignature = make([]byte, MaxSignatureSize+1) }}, 435 } 436 for _, tc := range testCases { 437 precommit := examplePrecommit(t) 438 signVote(ctx, t, privVal, "test_chain_id", precommit) 439 tc.malleateVote(precommit) 440 // ValidateBasic ensures that vote extensions, if present, are well formed 441 require.Error(t, precommit.ValidateBasic(), "ValidateBasic for %s", tc.name) 442 } 443 } 444 445 func TestEnsureVoteExtension(t *testing.T) { 446 ctx, cancel := context.WithCancel(context.Background()) 447 defer cancel() 448 privVal := NewMockPV() 449 450 testCases := []struct { 451 name string 452 malleateVote func(*Vote) 453 expectError bool 454 }{ 455 {"vote extension signature absent", func(v *Vote) { 456 v.Extension = nil 457 v.ExtensionSignature = nil 458 }, true}, 459 {"vote extension signature present", func(v *Vote) { 460 v.ExtensionSignature = []byte("extension signature") 461 }, false}, 462 } 463 for _, tc := range testCases { 464 precommit := examplePrecommit(t) 465 signVote(ctx, t, privVal, "test_chain_id", precommit) 466 tc.malleateVote(precommit) 467 if tc.expectError { 468 require.Error(t, precommit.EnsureExtension(), "EnsureExtension for %s", tc.name) 469 } else { 470 require.NoError(t, precommit.EnsureExtension(), "EnsureExtension for %s", tc.name) 471 } 472 } 473 } 474 475 func TestVoteProtobuf(t *testing.T) { 476 ctx, cancel := context.WithCancel(context.Background()) 477 defer cancel() 478 479 privVal := NewMockPV() 480 vote := examplePrecommit(t) 481 v := vote.ToProto() 482 err := privVal.SignVote(ctx, "test_chain_id", v) 483 vote.Signature = v.Signature 484 require.NoError(t, err) 485 486 testCases := []struct { 487 msg string 488 vote *Vote 489 convertsOk bool 490 passesValidateBasic bool 491 }{ 492 {"success", vote, true, true}, 493 {"fail vote validate basic", &Vote{}, true, false}, 494 } 495 for _, tc := range testCases { 496 protoProposal := tc.vote.ToProto() 497 498 v, err := VoteFromProto(protoProposal) 499 if tc.convertsOk { 500 require.NoError(t, err) 501 } else { 502 require.Error(t, err) 503 } 504 505 err = v.ValidateBasic() 506 if tc.passesValidateBasic { 507 require.NoError(t, err) 508 require.Equal(t, tc.vote, v, tc.msg) 509 } else { 510 require.Error(t, err) 511 } 512 } 513 } 514 515 var sink interface{} 516 517 func getSampleCommit(ctx context.Context, t testing.TB) *Commit { 518 t.Helper() 519 520 lastID := makeBlockIDRandom() 521 voteSet, _, vals := randVoteSet(ctx, t, 2, 1, tmproto.PrecommitType, 10, 1) 522 commit, err := makeExtCommit(ctx, lastID, 2, 1, voteSet, vals, time.Now()) 523 524 require.NoError(t, err) 525 526 return commit.ToCommit() 527 } 528 529 func BenchmarkVoteSignBytes(b *testing.B) { 530 protoVote := examplePrecommit(b).ToProto() 531 532 b.ReportAllocs() 533 b.ResetTimer() 534 535 for i := 0; i < b.N; i++ { 536 sink = VoteSignBytes("test_chain_id", protoVote) 537 } 538 539 if sink == nil { 540 b.Fatal("Benchmark did not run") 541 } 542 543 // Reset the sink. 544 sink = (interface{})(nil) 545 } 546 547 func BenchmarkCommitVoteSignBytes(b *testing.B) { 548 ctx, cancel := context.WithCancel(context.Background()) 549 defer cancel() 550 551 sampleCommit := getSampleCommit(ctx, b) 552 553 b.ReportAllocs() 554 b.ResetTimer() 555 556 for i := 0; i < b.N; i++ { 557 for index := range sampleCommit.Signatures { 558 sink = sampleCommit.VoteSignBytes("test_chain_id", int32(index)) 559 } 560 } 561 562 if sink == nil { 563 b.Fatal("Benchmark did not run") 564 } 565 566 // Reset the sink. 567 sink = (interface{})(nil) 568 }