github.com/Finschia/ostracon@v1.1.5/types/vote_test.go (about)

     1  package types
     2  
     3  import (
     4  	"math"
     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  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    13  
    14  	"github.com/Finschia/ostracon/crypto"
    15  	"github.com/Finschia/ostracon/crypto/ed25519"
    16  	"github.com/Finschia/ostracon/crypto/tmhash"
    17  	"github.com/Finschia/ostracon/libs/protoio"
    18  )
    19  
    20  func examplePrevote() *Vote {
    21  	return exampleVote(byte(tmproto.PrevoteType))
    22  }
    23  
    24  func examplePrecommit() *Vote {
    25  	return exampleVote(byte(tmproto.PrecommitType))
    26  }
    27  
    28  func exampleVote(t byte) *Vote {
    29  	var stamp, err = time.Parse(TimeFormat, "2017-12-25T03:00:01.234Z")
    30  	if err != nil {
    31  		panic(err)
    32  	}
    33  
    34  	return &Vote{
    35  		Type:      tmproto.SignedMsgType(t),
    36  		Height:    12345,
    37  		Round:     2,
    38  		Timestamp: stamp,
    39  		BlockID: BlockID{
    40  			Hash: tmhash.Sum([]byte("blockID_hash")),
    41  			PartSetHeader: PartSetHeader{
    42  				Total: 1000000,
    43  				Hash:  tmhash.Sum([]byte("blockID_part_set_header_hash")),
    44  			},
    45  		},
    46  		ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
    47  		ValidatorIndex:   56789,
    48  	}
    49  }
    50  
    51  func TestVoteSignable(t *testing.T) {
    52  	vote := examplePrecommit()
    53  	v := vote.ToProto()
    54  	signBytes := VoteSignBytes("test_chain_id", v)
    55  	pb := CanonicalizeVote("test_chain_id", v)
    56  	expected, err := protoio.MarshalDelimited(&pb)
    57  	require.NoError(t, err)
    58  
    59  	require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.")
    60  }
    61  
    62  func TestVoteSignBytesTestVectors(t *testing.T) {
    63  
    64  	tests := []struct {
    65  		chainID string
    66  		vote    *Vote
    67  		want    []byte
    68  	}{
    69  		0: {
    70  			"", &Vote{},
    71  			// NOTE: Height and Round are skipped here. This case needs to be considered while parsing.
    72  			[]byte{0xd, 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
    73  		},
    74  		// with proper (fixed size) height and round (PreCommit):
    75  		1: {
    76  			"", &Vote{Height: 1, Round: 1, Type: tmproto.PrecommitType},
    77  			[]byte{
    78  				0x21,                                   // length
    79  				0x8,                                    // (field_number << 3) | wire_type
    80  				0x2,                                    // PrecommitType
    81  				0x11,                                   // (field_number << 3) | wire_type
    82  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
    83  				0x19,                                   // (field_number << 3) | wire_type
    84  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
    85  				0x2a, // (field_number << 3) | wire_type
    86  				// remaining fields (timestamp):
    87  				0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
    88  		},
    89  		// with proper (fixed size) height and round (PreVote):
    90  		2: {
    91  			"", &Vote{Height: 1, Round: 1, Type: tmproto.PrevoteType},
    92  			[]byte{
    93  				0x21,                                   // length
    94  				0x8,                                    // (field_number << 3) | wire_type
    95  				0x1,                                    // PrevoteType
    96  				0x11,                                   // (field_number << 3) | wire_type
    97  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
    98  				0x19,                                   // (field_number << 3) | wire_type
    99  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
   100  				0x2a, // (field_number << 3) | wire_type
   101  				// remaining fields (timestamp):
   102  				0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
   103  		},
   104  		3: {
   105  			"", &Vote{Height: 1, Round: 1},
   106  			[]byte{
   107  				0x1f,                                   // length
   108  				0x11,                                   // (field_number << 3) | wire_type
   109  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
   110  				0x19,                                   // (field_number << 3) | wire_type
   111  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
   112  				// remaining fields (timestamp):
   113  				0x2a,
   114  				0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1},
   115  		},
   116  		// containing non-empty chain_id:
   117  		4: {
   118  			"test_chain_id", &Vote{Height: 1, Round: 1},
   119  			[]byte{
   120  				0x2e,                                   // length
   121  				0x11,                                   // (field_number << 3) | wire_type
   122  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height
   123  				0x19,                                   // (field_number << 3) | wire_type
   124  				0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round
   125  				// remaining fields:
   126  				0x2a,                                                                // (field_number << 3) | wire_type
   127  				0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp
   128  				// (field_number << 3) | wire_type
   129  				0x32,
   130  				0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID
   131  		},
   132  	}
   133  	for i, tc := range tests {
   134  		v := tc.vote.ToProto()
   135  		got := VoteSignBytes(tc.chainID, v)
   136  		assert.Equal(t, len(tc.want), len(got), "test case #%v: got unexpected sign bytes length for Vote.", i)
   137  		assert.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i)
   138  	}
   139  }
   140  
   141  func TestVoteProposalNotEq(t *testing.T) {
   142  	cv := CanonicalizeVote("", &tmproto.Vote{Height: 1, Round: 1})
   143  	p := CanonicalizeProposal("", &tmproto.Proposal{Height: 1, Round: 1})
   144  	vb, err := proto.Marshal(&cv)
   145  	require.NoError(t, err)
   146  	pb, err := proto.Marshal(&p)
   147  	require.NoError(t, err)
   148  	require.NotEqual(t, vb, pb)
   149  }
   150  
   151  func TestVoteVerifySignature(t *testing.T) {
   152  	privVal := NewMockPV()
   153  	pubkey, err := privVal.GetPubKey()
   154  	require.NoError(t, err)
   155  
   156  	vote := examplePrecommit()
   157  	v := vote.ToProto()
   158  	signBytes := VoteSignBytes("test_chain_id", v)
   159  
   160  	// sign it
   161  	err = privVal.SignVote("test_chain_id", v)
   162  	require.NoError(t, err)
   163  
   164  	// verify the same vote
   165  	valid := pubkey.VerifySignature(VoteSignBytes("test_chain_id", v), v.Signature)
   166  	require.True(t, valid)
   167  
   168  	// serialize, deserialize and verify again....
   169  	precommit := new(tmproto.Vote)
   170  	bs, err := proto.Marshal(v)
   171  	require.NoError(t, err)
   172  	err = proto.Unmarshal(bs, precommit)
   173  	require.NoError(t, err)
   174  
   175  	// verify the transmitted vote
   176  	newSignBytes := VoteSignBytes("test_chain_id", precommit)
   177  	require.Equal(t, string(signBytes), string(newSignBytes))
   178  	valid = pubkey.VerifySignature(newSignBytes, precommit.Signature)
   179  	require.True(t, valid)
   180  }
   181  
   182  func TestIsVoteTypeValid(t *testing.T) {
   183  	tc := []struct {
   184  		name string
   185  		in   tmproto.SignedMsgType
   186  		out  bool
   187  	}{
   188  		{"Prevote", tmproto.PrevoteType, true},
   189  		{"Precommit", tmproto.PrecommitType, true},
   190  		{"InvalidType", tmproto.SignedMsgType(0x3), false},
   191  	}
   192  
   193  	for _, tt := range tc {
   194  		tt := tt
   195  		t.Run(tt.name, func(st *testing.T) {
   196  			if rs := IsVoteTypeValid(tt.in); rs != tt.out {
   197  				t.Errorf("got unexpected Vote type. Expected:\n%v\nGot:\n%v", rs, tt.out)
   198  			}
   199  		})
   200  	}
   201  }
   202  
   203  func TestVoteVerify(t *testing.T) {
   204  	privVal := NewMockPV()
   205  	pubkey, err := privVal.GetPubKey()
   206  	require.NoError(t, err)
   207  
   208  	vote := examplePrevote()
   209  	vote.ValidatorAddress = pubkey.Address()
   210  
   211  	err = vote.Verify("test_chain_id", ed25519.GenPrivKey().PubKey())
   212  	if assert.Error(t, err) {
   213  		assert.Equal(t, ErrVoteInvalidValidatorAddress, err)
   214  	}
   215  
   216  	err = vote.Verify("test_chain_id", pubkey)
   217  	if assert.Error(t, err) {
   218  		assert.Equal(t, ErrVoteInvalidSignature, err)
   219  	}
   220  }
   221  
   222  func TestMaxVoteBytes(t *testing.T) {
   223  	// time is varint encoded so need to pick the max.
   224  	// year int, month Month, day, hour, min, sec, nsec int, loc *Location
   225  	timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC)
   226  	vote := &Vote{
   227  		ValidatorAddress: crypto.AddressHash([]byte("validator_address")),
   228  		ValidatorIndex:   math.MaxInt32,
   229  		Height:           math.MaxInt64,
   230  		Round:            math.MaxInt32,
   231  		Timestamp:        timestamp,
   232  		Type:             tmproto.ProposalType,
   233  		BlockID: BlockID{
   234  			Hash: tmhash.Sum([]byte("blockID_hash")),
   235  			PartSetHeader: PartSetHeader{
   236  				Total: math.MaxInt32,
   237  				Hash:  tmhash.Sum([]byte("blockID_part_set_header_hash")),
   238  			},
   239  		},
   240  	}
   241  
   242  	privVal := NewMockPV()
   243  	pbVote := vote.ToProto()
   244  	err := privVal.SignVote("test_chain_id", pbVote)
   245  	require.NoError(t, err)
   246  
   247  	bz, err := pbVote.Marshal()
   248  	require.NoError(t, err)
   249  	assert.Equal(t, MaxVoteBytes, int64(len(bz)))
   250  }
   251  
   252  func TestVoteString(t *testing.T) {
   253  	str := examplePrecommit().String()
   254  	expected := `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PRECOMMIT(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
   255  	if str != expected {
   256  		t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str)
   257  	}
   258  
   259  	str2 := examplePrevote().String()
   260  	expected = `Vote{56789:6AF1F4111082 12345/02/SIGNED_MSG_TYPE_PREVOTE(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` //nolint:lll //ignore line length for tests
   261  	if str2 != expected {
   262  		t.Errorf("got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2)
   263  	}
   264  }
   265  
   266  func TestVoteValidateBasic(t *testing.T) {
   267  	privVal := NewMockPV()
   268  
   269  	testCases := []struct {
   270  		testName     string
   271  		malleateVote func(*Vote)
   272  		expectErr    bool
   273  	}{
   274  		{"Good Vote", func(v *Vote) {}, false},
   275  		{"Negative Height", func(v *Vote) { v.Height = -1 }, true},
   276  		{"Negative Round", func(v *Vote) { v.Round = -1 }, true},
   277  		{"Invalid BlockID", func(v *Vote) {
   278  			v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}
   279  		}, true},
   280  		{"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true},
   281  		{"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true},
   282  		{"Invalid Signature", func(v *Vote) { v.Signature = nil }, true},
   283  		{"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true},
   284  	}
   285  	for _, tc := range testCases {
   286  		tc := tc
   287  		t.Run(tc.testName, func(t *testing.T) {
   288  			vote := examplePrecommit()
   289  			v := vote.ToProto()
   290  			err := privVal.SignVote("test_chain_id", v)
   291  			vote.Signature = v.Signature
   292  			require.NoError(t, err)
   293  			tc.malleateVote(vote)
   294  			assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result")
   295  		})
   296  	}
   297  }
   298  
   299  func TestVoteProtobuf(t *testing.T) {
   300  	privVal := NewMockPV()
   301  	vote := examplePrecommit()
   302  	v := vote.ToProto()
   303  	err := privVal.SignVote("test_chain_id", v)
   304  	vote.Signature = v.Signature
   305  	require.NoError(t, err)
   306  
   307  	testCases := []struct {
   308  		msg     string
   309  		v1      *Vote
   310  		expPass bool
   311  	}{
   312  		{"success", vote, true},
   313  		{"fail vote validate basic", &Vote{}, false},
   314  		{"failure nil", nil, false},
   315  	}
   316  	for _, tc := range testCases {
   317  		protoProposal := tc.v1.ToProto()
   318  
   319  		v, err := VoteFromProto(protoProposal)
   320  		if tc.expPass {
   321  			require.NoError(t, err)
   322  			require.Equal(t, tc.v1, v, tc.msg)
   323  		} else {
   324  			require.Error(t, err)
   325  		}
   326  	}
   327  }