github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/proposal_test.go (about)

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