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 }