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.

390 lines
12 KiB

internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
internal/libs/protoio: optimize MarshalDelimited by plain byteslice allocations+sync.Pool (#7325) Noticed in profiles that invoking *VoteSignBytes always created a bytes.Buffer, then discarded it inside protoio.MarshalDelimited. I dug further and examined the call paths and noticed that we unconditionally create the bytes.Buffer, even though we might have proto messages (in the common case) that implement MarshalTo([]byte), and invoked varintWriter. Instead by inlining this case, we skip a bunch of allocations and CPU cycles, which then reflects properly on all calling functions. Here are the benchmark results: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta types.VoteSignBytes-8 705ns ± 3% 573ns ± 6% -18.74% (p=0.000 n=18+20) types.CommitVoteSignBytes-8 8.15µs ± 9% 6.81µs ± 4% -16.51% (p=0.000 n=20+19) protoio.MarshalDelimitedWithMarshalTo-8 788ns ± 8% 772ns ± 3% -2.01% (p=0.050 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 989ns ± 4% 845ns ± 2% -14.51% (p=0.000 n=20+18) name old alloc/op new alloc/op delta types.VoteSignBytes-8 792B ± 0% 600B ± 0% -24.24% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 9.52kB ± 0% 7.60kB ± 0% -20.17% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 808B ± 0% 440B ± 0% -45.54% (p=0.000 n=20+20) name old allocs/op new allocs/op delta types.VoteSignBytes-8 13.0 ± 0% 10.0 ± 0% -23.08% (p=0.000 n=20+20) types.CommitVoteSignBytes-8 140 ± 0% 110 ± 0% -21.43% (p=0.000 n=20+20) protoio.MarshalDelimitedNoMarshalTo-8 10.0 ± 0% 7.0 ± 0% -30.00% (p=0.000 n=20+20) ``` Thanks to Tharsis who tasked me to help them increase TPS and who are keen on improving Tendermint and efficiency.
3 years ago
  1. package types
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/gogo/protobuf/proto"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "github.com/tendermint/tendermint/crypto"
  10. "github.com/tendermint/tendermint/crypto/ed25519"
  11. "github.com/tendermint/tendermint/crypto/tmhash"
  12. "github.com/tendermint/tendermint/internal/libs/protoio"
  13. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  14. )
  15. func examplePrevote(t *testing.T) *Vote {
  16. t.Helper()
  17. return exampleVote(t, byte(tmproto.PrevoteType))
  18. }
  19. func examplePrecommit(t testing.TB) *Vote {
  20. t.Helper()
  21. return exampleVote(t, byte(tmproto.PrecommitType))
  22. }
  23. func exampleVote(tb testing.TB, t byte) *Vote {
  24. tb.Helper()
  25. var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z")
  26. require.NoError(tb, err)
  27. return &Vote{
  28. Type: tmproto.SignedMsgType(t),
  29. Height: 12345,
  30. Round: 2,
  31. Timestamp: stamp,
  32. BlockID: BlockID{
  33. Hash: tmhash.Sum([]byte("blockID_hash")),
  34. PartSetHeader: PartSetHeader{
  35. Total: 1000000,
  36. Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")),
  37. },
  38. },
  39. ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
  40. ValidatorIndex: 56789,
  41. }
  42. }
  43. func TestVoteSignable(t *testing.T) {
  44. vote := examplePrecommit(t)
  45. v := vote.ToProto()
  46. signBytes := VoteSignBytes("test_chain_id", v)
  47. pb := CanonicalizeVote("test_chain_id", v)
  48. expected, err := protoio.MarshalDelimited(&pb)
  49. require.NoError(t, err)
  50. require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.")
  51. }
  52. func TestVoteSignBytesTestVectors(t *testing.T) {
  53. tests := []struct {
  54. chainID string
  55. vote *Vote
  56. want []byte
  57. }{
  58. 0: {
  59. "", &Vote{},
  60. // NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
  61. []byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
  62. },
  63. // with proper (fixed size) height and round (PreCommit):
  64. 1: {
  65. "", &Vote{Height: 1, Round: 1, Type: tmproto.PrecommitType},
  66. []byte{
  67. 0x21, // length
  68. 0x8, // (field_number << 3) | wire_type
  69. 0x2, // PrecommitType
  70. 0x11, // (field_number << 3) | wire_type
  71. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
  72. 0x19, // (field_number << 3) | wire_type
  73. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
  74. 0x2a, // (field_number << 3) | wire_type
  75. // remaining fields (timestamp):
  76. 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
  77. },
  78. // with proper (fixed size) height and round (PreVote):
  79. 2: {
  80. "", &Vote{Height: 1, Round: 1, Type: tmproto.PrevoteType},
  81. []byte{
  82. 0x21, // length
  83. 0x8, // (field_number << 3) | wire_type
  84. 0x1, // PrevoteType
  85. 0x11, // (field_number << 3) | wire_type
  86. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
  87. 0x19, // (field_number << 3) | wire_type
  88. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
  89. 0x2a, // (field_number << 3) | wire_type
  90. // remaining fields (timestamp):
  91. 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
  92. },
  93. 3: {
  94. "", &Vote{Height: 1, Round: 1},
  95. []byte{
  96. 0x1f, // length
  97. 0x11, // (field_number << 3) | wire_type
  98. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
  99. 0x19, // (field_number << 3) | wire_type
  100. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
  101. // remaining fields (timestamp):
  102. 0x2a,
  103. 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
  104. },
  105. // containing non-empty chain_id:
  106. 4: {
  107. "test_chain_id", &Vote{Height: 1, Round: 1},
  108. []byte{
  109. 0x2e, // 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:
  115. 0x2a, // (field_number << 3) | wire_type
  116. 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
  117. // (field_number << 3) | wire_type
  118. 0x32,
  119. 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
  120. },
  121. // containing vote extension
  122. 5: {
  123. "test_chain_id", &Vote{Height: 1, Round: 1, VoteExtension: VoteExtension{
  124. AppDataToSign: []byte("signed"),
  125. AppDataSelfAuthenticating: []byte("auth"),
  126. }},
  127. []byte{
  128. 0x38, // length
  129. 0x11, // (field_number << 3) | wire_type
  130. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
  131. 0x19, // (field_number << 3) | wire_type
  132. 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
  133. // remaning fields:
  134. 0x2a, // (field_number << 3) | wire_type
  135. 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
  136. // (field_number << 3) | wire_type
  137. 0x32,
  138. 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, // chainID
  139. // (field_number << 3) | wire_type
  140. 0x3a,
  141. 0x8, // length
  142. 0xa, // (field_number << 3) | wire_type
  143. 0x6, // length
  144. 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, // AppDataSigned
  145. // SelfAuthenticating data is excluded on signing
  146. }, // chainID
  147. },
  148. }
  149. for i, tc := range tests {
  150. v := tc.vote.ToProto()
  151. got := VoteSignBytes(tc.chainID, v)
  152. assert.Equal(t, len(tc.want), len(got), "test case #%v: got unexpected sign bytes length for Vote.", i)
  153. assert.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i)
  154. }
  155. }
  156. func TestVoteProposalNotEq(t *testing.T) {
  157. cv := CanonicalizeVote("", &tmproto.Vote{Height: 1, Round: 1})
  158. p := CanonicalizeProposal("", &tmproto.Proposal{Height: 1, Round: 1})
  159. vb, err := proto.Marshal(&cv)
  160. require.NoError(t, err)
  161. pb, err := proto.Marshal(&p)
  162. require.NoError(t, err)
  163. require.NotEqual(t, vb, pb)
  164. }
  165. func TestVoteVerifySignature(t *testing.T) {
  166. ctx, cancel := context.WithCancel(context.Background())
  167. defer cancel()
  168. privVal := NewMockPV()
  169. pubkey, err := privVal.GetPubKey(ctx)
  170. require.NoError(t, err)
  171. vote := examplePrecommit(t)
  172. v := vote.ToProto()
  173. signBytes := VoteSignBytes("test_chain_id", v)
  174. // sign it
  175. err = privVal.SignVote(ctx, "test_chain_id", v)
  176. require.NoError(t, err)
  177. // verify the same vote
  178. valid := pubkey.VerifySignature(VoteSignBytes("test_chain_id", v), v.Signature)
  179. require.True(t, valid)
  180. // serialize, deserialize and verify again....
  181. precommit := new(tmproto.Vote)
  182. bs, err := proto.Marshal(v)
  183. require.NoError(t, err)
  184. err = proto.Unmarshal(bs, precommit)
  185. require.NoError(t, err)
  186. // verify the transmitted vote
  187. newSignBytes := VoteSignBytes("test_chain_id", precommit)
  188. require.Equal(t, string(signBytes), string(newSignBytes))
  189. valid = pubkey.VerifySignature(newSignBytes, precommit.Signature)
  190. require.True(t, valid)
  191. }
  192. func TestIsVoteTypeValid(t *testing.T) {
  193. tc := []struct {
  194. name string
  195. in tmproto.SignedMsgType
  196. out bool
  197. }{
  198. {"Prevote", tmproto.PrevoteType, true},
  199. {"Precommit", tmproto.PrecommitType, true},
  200. {"InvalidType", tmproto.SignedMsgType(0x3), false},
  201. }
  202. for _, tt := range tc {
  203. tt := tt
  204. t.Run(tt.name, func(st *testing.T) {
  205. if rs := IsVoteTypeValid(tt.in); rs != tt.out {
  206. t.Errorf("got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out)
  207. }
  208. })
  209. }
  210. }
  211. func TestVoteVerify(t *testing.T) {
  212. ctx, cancel := context.WithCancel(context.Background())
  213. defer cancel()
  214. privVal := NewMockPV()
  215. pubkey, err := privVal.GetPubKey(ctx)
  216. require.NoError(t, err)
  217. vote := examplePrevote(t)
  218. vote.ValidatorAddress = pubkey.Address()
  219. err = vote.Verify("test_chain_id", ed25519.GenPrivKey().PubKey())
  220. if assert.Error(t, err) {
  221. assert.Equal(t, ErrVoteInvalidValidatorAddress, err)
  222. }
  223. err = vote.Verify("test_chain_id", pubkey)
  224. if assert.Error(t, err) {
  225. assert.Equal(t, ErrVoteInvalidSignature, err)
  226. }
  227. }
  228. func TestVoteString(t *testing.T) {
  229. str := examplePrecommit(t).String()
  230. expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
  231. if str != expected {
  232. t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str)
  233. }
  234. str2 := examplePrevote(t).String()
  235. expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
  236. if str2 != expected {
  237. t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2)
  238. }
  239. }
  240. func TestVoteValidateBasic(t *testing.T) {
  241. privVal := NewMockPV()
  242. testCases := []struct {
  243. testName string
  244. malleateVote func(*Vote)
  245. expectErr bool
  246. }{
  247. {"Good Vote", func(v *Vote) {}, false},
  248. {"Negative Height", func(v *Vote) { v.Height = -1 }, true},
  249. {"Negative Round", func(v *Vote) { v.Round = -1 }, true},
  250. {"Invalid BlockID", func(v *Vote) {
  251. v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}
  252. }, true},
  253. {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true},
  254. {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true},
  255. {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true},
  256. {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true},
  257. }
  258. for _, tc := range testCases {
  259. tc := tc
  260. t.Run(tc.testName, func(t *testing.T) {
  261. ctx, cancel := context.WithCancel(context.Background())
  262. defer cancel()
  263. vote := examplePrecommit(t)
  264. v := vote.ToProto()
  265. err := privVal.SignVote(ctx, "test_chain_id", v)
  266. vote.Signature = v.Signature
  267. require.NoError(t, err)
  268. tc.malleateVote(vote)
  269. assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result")
  270. })
  271. }
  272. }
  273. func TestVoteProtobuf(t *testing.T) {
  274. ctx, cancel := context.WithCancel(context.Background())
  275. defer cancel()
  276. privVal := NewMockPV()
  277. vote := examplePrecommit(t)
  278. v := vote.ToProto()
  279. err := privVal.SignVote(ctx, "test_chain_id", v)
  280. vote.Signature = v.Signature
  281. require.NoError(t, err)
  282. testCases := []struct {
  283. msg string
  284. v1 *Vote
  285. expPass bool
  286. }{
  287. {"success", vote, true},
  288. {"fail vote validate basic", &Vote{}, false},
  289. {"failure nil", nil, false},
  290. }
  291. for _, tc := range testCases {
  292. protoProposal := tc.v1.ToProto()
  293. v, err := VoteFromProto(protoProposal)
  294. if tc.expPass {
  295. require.NoError(t, err)
  296. require.Equal(t, tc.v1, v, tc.msg)
  297. } else {
  298. require.Error(t, err)
  299. }
  300. }
  301. }
  302. var sink interface{}
  303. func getSampleCommit(ctx context.Context, t testing.TB) *Commit {
  304. t.Helper()
  305. lastID := makeBlockIDRandom()
  306. voteSet, _, vals := randVoteSet(ctx, t, 2, 1, tmproto.PrecommitType, 10, 1)
  307. commit, err := makeCommit(ctx, lastID, 2, 1, voteSet, vals, time.Now())
  308. require.NoError(t, err)
  309. return commit
  310. }
  311. func BenchmarkVoteSignBytes(b *testing.B) {
  312. protoVote := examplePrecommit(b).ToProto()
  313. b.ReportAllocs()
  314. b.ResetTimer()
  315. for i := 0; i < b.N; i++ {
  316. sink = VoteSignBytes("test_chain_id", protoVote)
  317. }
  318. if sink == nil {
  319. b.Fatal("Benchmark did not run")
  320. }
  321. // Reset the sink.
  322. sink = (interface{})(nil)
  323. }
  324. func BenchmarkCommitVoteSignBytes(b *testing.B) {
  325. ctx, cancel := context.WithCancel(context.Background())
  326. defer cancel()
  327. sampleCommit := getSampleCommit(ctx, b)
  328. b.ReportAllocs()
  329. b.ResetTimer()
  330. for i := 0; i < b.N; i++ {
  331. for index := range sampleCommit.Signatures {
  332. sink = sampleCommit.VoteSignBytes("test_chain_id", int32(index))
  333. }
  334. }
  335. if sink == nil {
  336. b.Fatal("Benchmark did not run")
  337. }
  338. // Reset the sink.
  339. sink = (interface{})(nil)
  340. }