github.com/evdatsion/aphelion-dpos-bft@v0.32.1/types/vote_test.go (about)

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