- package types
-
- import (
- "context"
- "math"
- "testing"
- "time"
-
- "github.com/gogo/protobuf/proto"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/crypto/tmhash"
- "github.com/tendermint/tendermint/internal/libs/protoio"
- tmrand "github.com/tendermint/tendermint/libs/rand"
- tmtime "github.com/tendermint/tendermint/libs/time"
- tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
- )
-
- func getTestProposal(t testing.TB) *Proposal {
- t.Helper()
-
- stamp, err := time.Parse(TimeFormat, "2018-02-11T07:09:22.765Z")
- require.NoError(t, err)
-
- return &Proposal{
- Height: 12345,
- Round: 23456,
- BlockID: BlockID{Hash: []byte("--June_15_2020_amino_was_removed"),
- PartSetHeader: PartSetHeader{Total: 111, Hash: []byte("--June_15_2020_amino_was_removed")}},
- POLRound: -1,
- Timestamp: stamp,
- }
- }
-
- func TestProposalSignable(t *testing.T) {
- chainID := "test_chain_id"
- signBytes := ProposalSignBytes(chainID, getTestProposal(t).ToProto())
- pb := CanonicalizeProposal(chainID, getTestProposal(t).ToProto())
-
- expected, err := protoio.MarshalDelimited(&pb)
- require.NoError(t, err)
- require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Proposal")
- }
-
- func TestProposalString(t *testing.T) {
- str := getTestProposal(t).String()
- expected := `Proposal{12345/23456 (2D2D4A756E655F31355F323032305F616D696E6F5F7761735F72656D6F766564:111:2D2D4A756E65, -1) 000000000000 @ 2018-02-11T07:09:22.765Z}`
- if str != expected {
- t.Errorf("got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str)
- }
- }
-
- func TestProposalVerifySignature(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
- pubKey, err := privVal.GetPubKey(ctx)
- require.NoError(t, err)
-
- prop := NewProposal(
- 4, 2, 2,
- BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}, tmtime.Now())
- p := prop.ToProto()
- signBytes := ProposalSignBytes("test_chain_id", p)
-
- // sign it
- err = privVal.SignProposal(ctx, "test_chain_id", p)
- require.NoError(t, err)
- prop.Signature = p.Signature
-
- // verify the same proposal
- valid := pubKey.VerifySignature(signBytes, prop.Signature)
- require.True(t, valid)
-
- // serialize, deserialize and verify again....
- newProp := new(tmproto.Proposal)
- pb := prop.ToProto()
-
- bs, err := proto.Marshal(pb)
- require.NoError(t, err)
-
- err = proto.Unmarshal(bs, newProp)
- require.NoError(t, err)
-
- np, err := ProposalFromProto(newProp)
- require.NoError(t, err)
-
- // verify the transmitted proposal
- newSignBytes := ProposalSignBytes("test_chain_id", pb)
- require.Equal(t, string(signBytes), string(newSignBytes))
- valid = pubKey.VerifySignature(newSignBytes, np.Signature)
- require.True(t, valid)
- }
-
- func BenchmarkProposalWriteSignBytes(b *testing.B) {
- pbp := getTestProposal(b).ToProto()
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- ProposalSignBytes("test_chain_id", pbp)
- }
- }
-
- func BenchmarkProposalSign(b *testing.B) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
-
- pbp := getTestProposal(b).ToProto()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- err := privVal.SignProposal(ctx, "test_chain_id", pbp)
- if err != nil {
- b.Error(err)
- }
- }
- }
-
- func BenchmarkProposalVerifySignature(b *testing.B) {
- testProposal := getTestProposal(b)
- pbp := testProposal.ToProto()
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- privVal := NewMockPV()
- err := privVal.SignProposal(ctx, "test_chain_id", pbp)
- require.NoError(b, err)
- pubKey, err := privVal.GetPubKey(ctx)
- require.NoError(b, err)
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- pubKey.VerifySignature(ProposalSignBytes("test_chain_id", pbp), testProposal.Signature)
- }
- }
-
- func TestProposalValidateBasic(t *testing.T) {
-
- privVal := NewMockPV()
- testCases := []struct {
- testName string
- malleateProposal func(*Proposal)
- expectErr bool
- }{
- {"Good Proposal", func(p *Proposal) {}, false},
- {"Invalid Type", func(p *Proposal) { p.Type = tmproto.PrecommitType }, true},
- {"Invalid Height", func(p *Proposal) { p.Height = -1 }, true},
- {"Invalid Round", func(p *Proposal) { p.Round = -1 }, true},
- {"Invalid POLRound", func(p *Proposal) { p.POLRound = -2 }, true},
- {"Invalid BlockId", func(p *Proposal) {
- p.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}
- }, true},
- {"Invalid Signature", func(p *Proposal) {
- p.Signature = make([]byte, 0)
- }, true},
- {"Too big Signature", func(p *Proposal) {
- p.Signature = make([]byte, MaxSignatureSize+1)
- }, true},
- }
- blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
-
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.testName, func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- prop := NewProposal(
- 4, 2, 2,
- blockID, tmtime.Now())
- p := prop.ToProto()
- err := privVal.SignProposal(ctx, "test_chain_id", p)
- prop.Signature = p.Signature
- require.NoError(t, err)
- tc.malleateProposal(prop)
- assert.Equal(t, tc.expectErr, prop.ValidateBasic() != nil, "Validate Basic had an unexpected result")
- })
- }
- }
-
- func TestProposalProtoBuf(t *testing.T) {
- proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash")), tmtime.Now())
- proposal.Signature = []byte("sig")
- proposal2 := NewProposal(1, 2, 3, BlockID{}, tmtime.Now())
-
- testCases := []struct {
- msg string
- p1 *Proposal
- expPass bool
- }{
- {"success", proposal, true},
- {"success", proposal2, false}, // blcokID cannot be empty
- {"empty proposal failure validatebasic", &Proposal{}, false},
- {"nil proposal", nil, false},
- }
- for _, tc := range testCases {
- protoProposal := tc.p1.ToProto()
-
- p, err := ProposalFromProto(protoProposal)
- if tc.expPass {
- require.NoError(t, err)
- require.Equal(t, tc.p1, p, tc.msg)
- } else {
- require.Error(t, err)
- }
- }
- }
-
- func TestIsTimely(t *testing.T) {
- genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
- require.NoError(t, err)
- testCases := []struct {
- name string
- proposalTime time.Time
- recvTime time.Time
- precision time.Duration
- msgDelay time.Duration
- expectTimely bool
- round int32
- }{
- // proposalTime - precision <= localTime <= proposalTime + msgDelay + precision
- {
- // Checking that the following inequality evaluates to true:
- // 0 - 2 <= 1 <= 0 + 1 + 2
- name: "basic timely",
- proposalTime: genesisTime,
- recvTime: genesisTime.Add(1 * time.Nanosecond),
- precision: time.Nanosecond * 2,
- msgDelay: time.Nanosecond,
- expectTimely: true,
- },
- {
- // Checking that the following inequality evaluates to false:
- // 0 - 2 <= 4 <= 0 + 1 + 2
- name: "local time too large",
- proposalTime: genesisTime,
- recvTime: genesisTime.Add(4 * time.Nanosecond),
- precision: time.Nanosecond * 2,
- msgDelay: time.Nanosecond,
- expectTimely: false,
- },
- {
- // Checking that the following inequality evaluates to false:
- // 4 - 2 <= 0 <= 4 + 2 + 1
- name: "proposal time too large",
- proposalTime: genesisTime.Add(4 * time.Nanosecond),
- recvTime: genesisTime,
- precision: time.Nanosecond * 2,
- msgDelay: time.Nanosecond,
- expectTimely: false,
- },
- {
- // Checking that the following inequality evaluates to true:
- // 0 - (2 * 2) <= 4 <= 0 + (1 * 2) + 2
- name: "message delay adapts after 10 rounds",
- proposalTime: genesisTime,
- recvTime: genesisTime.Add(4 * time.Nanosecond),
- precision: time.Nanosecond * 2,
- msgDelay: time.Nanosecond,
- expectTimely: true,
- round: 10,
- },
- {
- // check that values that overflow time.Duration still correctly register
- // as timely when round relaxation applied.
- name: "message delay fixed to not overflow time.Duration",
- proposalTime: genesisTime,
- recvTime: genesisTime.Add(4 * time.Nanosecond),
- precision: time.Nanosecond * 2,
- msgDelay: time.Nanosecond,
- expectTimely: true,
- round: 5000,
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.name, func(t *testing.T) {
- p := Proposal{
- Timestamp: testCase.proposalTime,
- }
-
- sp := SynchronyParams{
- Precision: testCase.precision,
- MessageDelay: testCase.msgDelay,
- }
-
- ti := p.IsTimely(testCase.recvTime, sp, testCase.round)
- assert.Equal(t, testCase.expectTimely, ti)
- })
- }
- }
|