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.

309 lines
8.7 KiB

  1. package types
  2. import (
  3. "context"
  4. "math"
  5. "testing"
  6. "time"
  7. "github.com/gogo/protobuf/proto"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. "github.com/tendermint/tendermint/crypto/tmhash"
  11. "github.com/tendermint/tendermint/internal/libs/protoio"
  12. tmrand "github.com/tendermint/tendermint/libs/rand"
  13. tmtime "github.com/tendermint/tendermint/libs/time"
  14. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  15. )
  16. func getTestProposal(t testing.TB) *Proposal {
  17. t.Helper()
  18. stamp, err := time.Parse(TimeFormat, "2018-02-11T07:09:22.765Z")
  19. require.NoError(t, err)
  20. return &Proposal{
  21. Height: 12345,
  22. Round: 23456,
  23. BlockID: BlockID{Hash: []byte("--June_15_2020_amino_was_removed"),
  24. PartSetHeader: PartSetHeader{Total: 111, Hash: []byte("--June_15_2020_amino_was_removed")}},
  25. POLRound: -1,
  26. Timestamp: stamp,
  27. }
  28. }
  29. func TestProposalSignable(t *testing.T) {
  30. chainID := "test_chain_id"
  31. signBytes := ProposalSignBytes(chainID, getTestProposal(t).ToProto())
  32. pb := CanonicalizeProposal(chainID, getTestProposal(t).ToProto())
  33. expected, err := protoio.MarshalDelimited(&pb)
  34. require.NoError(t, err)
  35. require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Proposal")
  36. }
  37. func TestProposalString(t *testing.T) {
  38. str := getTestProposal(t).String()
  39. expected := `Proposal{12345/23456 (2D2D4A756E655F31355F323032305F616D696E6F5F7761735F72656D6F766564:111:2D2D4A756E65, -1) 000000000000 @ 2018-02-11T07:09:22.765Z}`
  40. if str != expected {
  41. t.Errorf("got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
  42. }
  43. }
  44. func TestProposalVerifySignature(t *testing.T) {
  45. ctx, cancel := context.WithCancel(context.Background())
  46. defer cancel()
  47. privVal := NewMockPV()
  48. pubKey, err := privVal.GetPubKey(ctx)
  49. require.NoError(t, err)
  50. prop := NewProposal(
  51. 4, 2, 2,
  52. BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}, tmtime.Now())
  53. p := prop.ToProto()
  54. signBytes := ProposalSignBytes("test_chain_id", p)
  55. // sign it
  56. err = privVal.SignProposal(ctx, "test_chain_id", p)
  57. require.NoError(t, err)
  58. prop.Signature = p.Signature
  59. // verify the same proposal
  60. valid := pubKey.VerifySignature(signBytes, prop.Signature)
  61. require.True(t, valid)
  62. // serialize, deserialize and verify again....
  63. newProp := new(tmproto.Proposal)
  64. pb := prop.ToProto()
  65. bs, err := proto.Marshal(pb)
  66. require.NoError(t, err)
  67. err = proto.Unmarshal(bs, newProp)
  68. require.NoError(t, err)
  69. np, err := ProposalFromProto(newProp)
  70. require.NoError(t, err)
  71. // verify the transmitted proposal
  72. newSignBytes := ProposalSignBytes("test_chain_id", pb)
  73. require.Equal(t, string(signBytes), string(newSignBytes))
  74. valid = pubKey.VerifySignature(newSignBytes, np.Signature)
  75. require.True(t, valid)
  76. }
  77. func BenchmarkProposalWriteSignBytes(b *testing.B) {
  78. pbp := getTestProposal(b).ToProto()
  79. b.ResetTimer()
  80. for i := 0; i < b.N; i++ {
  81. ProposalSignBytes("test_chain_id", pbp)
  82. }
  83. }
  84. func BenchmarkProposalSign(b *testing.B) {
  85. ctx, cancel := context.WithCancel(context.Background())
  86. defer cancel()
  87. privVal := NewMockPV()
  88. pbp := getTestProposal(b).ToProto()
  89. b.ResetTimer()
  90. for i := 0; i < b.N; i++ {
  91. err := privVal.SignProposal(ctx, "test_chain_id", pbp)
  92. if err != nil {
  93. b.Error(err)
  94. }
  95. }
  96. }
  97. func BenchmarkProposalVerifySignature(b *testing.B) {
  98. testProposal := getTestProposal(b)
  99. pbp := testProposal.ToProto()
  100. ctx, cancel := context.WithCancel(context.Background())
  101. defer cancel()
  102. privVal := NewMockPV()
  103. err := privVal.SignProposal(ctx, "test_chain_id", pbp)
  104. require.NoError(b, err)
  105. pubKey, err := privVal.GetPubKey(ctx)
  106. require.NoError(b, err)
  107. b.ResetTimer()
  108. for i := 0; i < b.N; i++ {
  109. pubKey.VerifySignature(ProposalSignBytes("test_chain_id", pbp), testProposal.Signature)
  110. }
  111. }
  112. func TestProposalValidateBasic(t *testing.T) {
  113. privVal := NewMockPV()
  114. testCases := []struct {
  115. testName string
  116. malleateProposal func(*Proposal)
  117. expectErr bool
  118. }{
  119. {"Good Proposal", func(p *Proposal) {}, false},
  120. {"Invalid Type", func(p *Proposal) { p.Type = tmproto.PrecommitType }, true},
  121. {"Invalid Height", func(p *Proposal) { p.Height = -1 }, true},
  122. {"Invalid Round", func(p *Proposal) { p.Round = -1 }, true},
  123. {"Invalid POLRound", func(p *Proposal) { p.POLRound = -2 }, true},
  124. {"Invalid BlockId", func(p *Proposal) {
  125. p.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}
  126. }, true},
  127. {"Invalid Signature", func(p *Proposal) {
  128. p.Signature = make([]byte, 0)
  129. }, true},
  130. {"Too big Signature", func(p *Proposal) {
  131. p.Signature = make([]byte, MaxSignatureSize+1)
  132. }, true},
  133. }
  134. blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
  135. for _, tc := range testCases {
  136. tc := tc
  137. t.Run(tc.testName, func(t *testing.T) {
  138. ctx, cancel := context.WithCancel(context.Background())
  139. defer cancel()
  140. prop := NewProposal(
  141. 4, 2, 2,
  142. blockID, tmtime.Now())
  143. p := prop.ToProto()
  144. err := privVal.SignProposal(ctx, "test_chain_id", p)
  145. prop.Signature = p.Signature
  146. require.NoError(t, err)
  147. tc.malleateProposal(prop)
  148. assert.Equal(t, tc.expectErr, prop.ValidateBasic() != nil, "Validate Basic had an unexpected result")
  149. })
  150. }
  151. }
  152. func TestProposalProtoBuf(t *testing.T) {
  153. proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash")), tmtime.Now())
  154. proposal.Signature = []byte("sig")
  155. proposal2 := NewProposal(1, 2, 3, BlockID{}, tmtime.Now())
  156. testCases := []struct {
  157. msg string
  158. p1 *Proposal
  159. expPass bool
  160. }{
  161. {"success", proposal, true},
  162. {"success", proposal2, false}, // blcokID cannot be empty
  163. {"empty proposal failure validatebasic", &Proposal{}, false},
  164. {"nil proposal", nil, false},
  165. }
  166. for _, tc := range testCases {
  167. protoProposal := tc.p1.ToProto()
  168. p, err := ProposalFromProto(protoProposal)
  169. if tc.expPass {
  170. require.NoError(t, err)
  171. require.Equal(t, tc.p1, p, tc.msg)
  172. } else {
  173. require.Error(t, err)
  174. }
  175. }
  176. }
  177. func TestIsTimely(t *testing.T) {
  178. genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
  179. require.NoError(t, err)
  180. testCases := []struct {
  181. name string
  182. genesisHeight int64
  183. proposalHeight int64
  184. proposalTime time.Time
  185. recvTime time.Time
  186. precision time.Duration
  187. msgDelay time.Duration
  188. expectTimely bool
  189. }{
  190. // proposalTime - precision <= localTime <= proposalTime + msgDelay + precision
  191. {
  192. // Checking that the following inequality evaluates to true:
  193. // 0 - 2 <= 1 <= 0 + 1 + 2
  194. name: "basic timely",
  195. genesisHeight: 1,
  196. proposalHeight: 2,
  197. proposalTime: genesisTime,
  198. recvTime: genesisTime.Add(1 * time.Nanosecond),
  199. precision: time.Nanosecond * 2,
  200. msgDelay: time.Nanosecond,
  201. expectTimely: true,
  202. },
  203. {
  204. // Checking that the following inequality evaluates to false:
  205. // 0 - 2 <= 4 <= 0 + 1 + 2
  206. name: "local time too large",
  207. genesisHeight: 1,
  208. proposalHeight: 2,
  209. proposalTime: genesisTime,
  210. recvTime: genesisTime.Add(4 * time.Nanosecond),
  211. precision: time.Nanosecond * 2,
  212. msgDelay: time.Nanosecond,
  213. expectTimely: false,
  214. },
  215. {
  216. // Checking that the following inequality evaluates to false:
  217. // 4 - 2 <= 0 <= 4 + 2 + 1
  218. name: "proposal time too large",
  219. genesisHeight: 1,
  220. proposalHeight: 2,
  221. proposalTime: genesisTime.Add(4 * time.Nanosecond),
  222. recvTime: genesisTime,
  223. precision: time.Nanosecond * 2,
  224. msgDelay: time.Nanosecond,
  225. expectTimely: false,
  226. },
  227. {
  228. // Checking that the following inequality evaluates to true:
  229. // 0 - 2 <= 4
  230. // and the following check is skipped
  231. // 4 <= 0 + 1 + 2
  232. name: "local time too large but proposal is for genesis",
  233. genesisHeight: 1,
  234. proposalHeight: 1,
  235. proposalTime: genesisTime,
  236. recvTime: genesisTime.Add(4 * time.Nanosecond),
  237. precision: time.Nanosecond * 2,
  238. msgDelay: time.Nanosecond,
  239. expectTimely: true,
  240. },
  241. {
  242. // Checking that the following inequality evaluates to false:
  243. // 4 - 2 <= 0
  244. name: "proposal time too large for genesis block proposal",
  245. genesisHeight: 1,
  246. proposalHeight: 1,
  247. proposalTime: genesisTime.Add(4 * time.Nanosecond),
  248. recvTime: genesisTime,
  249. precision: time.Nanosecond * 2,
  250. msgDelay: time.Nanosecond,
  251. expectTimely: false,
  252. },
  253. }
  254. for _, testCase := range testCases {
  255. t.Run(testCase.name, func(t *testing.T) {
  256. p := Proposal{
  257. Height: testCase.proposalHeight,
  258. Timestamp: testCase.proposalTime,
  259. }
  260. sp := SynchronyParams{
  261. Precision: testCase.precision,
  262. MessageDelay: testCase.msgDelay,
  263. }
  264. ti := p.IsTimely(testCase.recvTime, sp, testCase.genesisHeight)
  265. assert.Equal(t, testCase.expectTimely, ti)
  266. })
  267. }
  268. }