github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/validator/validator_test.go (about) 1 package validator 2 3 import ( 4 "errors" 5 "fmt" 6 "math/rand" 7 "testing" 8 "time" 9 10 "github.com/koko1123/flow-go-1/module/signature" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 "github.com/stretchr/testify/suite" 16 17 "github.com/koko1123/flow-go-1/consensus/hotstuff/helper" 18 "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 19 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 20 "github.com/koko1123/flow-go-1/model/flow" 21 "github.com/koko1123/flow-go-1/model/flow/filter" 22 "github.com/koko1123/flow-go-1/utils/unittest" 23 ) 24 25 func TestValidateProposal(t *testing.T) { 26 suite.Run(t, new(ProposalSuite)) 27 } 28 29 type ProposalSuite struct { 30 suite.Suite 31 participants flow.IdentityList 32 leader *flow.Identity 33 finalized uint64 34 parent *model.Block 35 block *model.Block 36 voters flow.IdentityList 37 proposal *model.Proposal 38 vote *model.Vote 39 voter *flow.Identity 40 committee *mocks.Committee 41 forks *mocks.Forks 42 verifier *mocks.Verifier 43 validator *Validator 44 } 45 46 func (ps *ProposalSuite) SetupTest() { 47 // the leader is a random node for now 48 rand.Seed(time.Now().UnixNano()) 49 ps.finalized = uint64(rand.Uint32() + 1) 50 ps.participants = unittest.IdentityListFixture(8, unittest.WithRole(flow.RoleConsensus)) 51 ps.leader = ps.participants[0] 52 53 // the parent is the last finalized block, followed directly by a block from the leader 54 ps.parent = helper.MakeBlock( 55 helper.WithBlockView(ps.finalized), 56 ) 57 58 indices, err := signature.EncodeSignersToIndices(ps.participants.NodeIDs(), ps.participants.NodeIDs()) 59 require.NoError(ps.T(), err) 60 61 ps.block = helper.MakeBlock( 62 helper.WithBlockView(ps.finalized+1), 63 helper.WithBlockProposer(ps.leader.NodeID), 64 helper.WithParentBlock(ps.parent), 65 helper.WithParentSigners(indices), 66 ) 67 68 voterIDs, err := signature.DecodeSignerIndicesToIdentifiers(ps.participants.NodeIDs(), ps.block.QC.SignerIndices) 69 require.NoError(ps.T(), err) 70 71 ps.voters = ps.participants.Filter(filter.HasNodeID(voterIDs...)) 72 ps.proposal = &model.Proposal{Block: ps.block} 73 ps.vote = ps.proposal.ProposerVote() 74 ps.voter = ps.leader 75 76 // set up the mocked hotstuff Committee state 77 ps.committee = &mocks.Committee{} 78 ps.committee.On("LeaderForView", ps.block.View).Return(ps.leader.NodeID, nil) 79 ps.committee.On("Identities", mock.Anything).Return( 80 func(blockID flow.Identifier) flow.IdentityList { 81 return ps.participants 82 }, 83 nil, 84 ) 85 for _, participant := range ps.participants { 86 ps.committee.On("Identity", mock.Anything, participant.NodeID).Return(participant, nil) 87 } 88 89 // the finalized view is the one of the parent of the 90 ps.forks = &mocks.Forks{} 91 ps.forks.On("FinalizedView").Return(ps.finalized) 92 ps.forks.On("GetBlock", ps.parent.BlockID).Return(ps.parent, true) 93 ps.forks.On("GetBlock", ps.block.BlockID).Return(ps.block, true) 94 95 // set up the mocked verifier 96 ps.verifier = &mocks.Verifier{} 97 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(nil) 98 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 99 100 // set up the validator with the mocked dependencies 101 ps.validator = New(ps.committee, ps.forks, ps.verifier) 102 } 103 104 func (ps *ProposalSuite) TestProposalOK() { 105 106 err := ps.validator.ValidateProposal(ps.proposal) 107 assert.NoError(ps.T(), err, "a valid proposal should be accepted") 108 } 109 110 func (ps *ProposalSuite) TestProposalSignatureError() { 111 112 // change the verifier to error on signature validation with unspecific error 113 *ps.verifier = mocks.Verifier{} 114 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(nil) 115 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(errors.New("dummy error")) 116 117 // check that validation now fails 118 err := ps.validator.ValidateProposal(ps.proposal) 119 assert.Error(ps.T(), err, "a proposal should be rejected if signature check fails") 120 121 // check that the error is not one that leads to invalid 122 assert.False(ps.T(), model.IsInvalidBlockError(err), "if signature check fails, we should not receive an ErrorInvalidBlock") 123 } 124 125 func (ps *ProposalSuite) TestProposalSignatureInvalidFormat() { 126 127 // change the verifier to fail signature validation with InvalidFormatError error 128 *ps.verifier = mocks.Verifier{} 129 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(nil) 130 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(model.NewInvalidFormatErrorf("")) 131 132 // check that validation now fails 133 err := ps.validator.ValidateProposal(ps.proposal) 134 assert.Error(ps.T(), err, "a proposal with an invalid signature should be rejected") 135 136 // check that the error is an invalid proposal error to allow creating slashing challenge 137 assert.True(ps.T(), model.IsInvalidBlockError(err), "if signature is invalid, we should generate an invalid error") 138 } 139 140 func (ps *ProposalSuite) TestProposalSignatureInvalid() { 141 142 // change the verifier to fail signature validation 143 *ps.verifier = mocks.Verifier{} 144 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(nil) 145 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(model.ErrInvalidSignature) 146 147 // check that validation now fails 148 err := ps.validator.ValidateProposal(ps.proposal) 149 assert.Error(ps.T(), err, "a proposal with an invalid signature should be rejected") 150 151 // check that the error is an invalid proposal error to allow creating slashing challenge 152 assert.True(ps.T(), model.IsInvalidBlockError(err), "if signature is invalid, we should generate an invalid error") 153 } 154 155 func (ps *ProposalSuite) TestProposalWrongLeader() { 156 157 // change the hotstuff.Committee to return a different leader 158 *ps.committee = mocks.Committee{} 159 ps.committee.On("LeaderForView", ps.block.View).Return(ps.participants[1].NodeID, nil) 160 for _, participant := range ps.participants { 161 ps.committee.On("Identity", mock.Anything, participant.NodeID).Return(participant, nil) 162 } 163 164 // check that validation fails now 165 err := ps.validator.ValidateProposal(ps.proposal) 166 assert.Error(ps.T(), err, "a proposal from the wrong proposer should be rejected") 167 168 // check that the error is an invalid proposal error to allow creating slashing challenge 169 assert.True(ps.T(), model.IsInvalidBlockError(err), "if the proposal has wrong proposer, we should generate a invalid error") 170 } 171 172 func (ps *ProposalSuite) TestProposalMismatchingView() { 173 174 // change the QC's view to be different from the parent 175 ps.proposal.Block.QC.View++ 176 177 // check that validation fails now 178 err := ps.validator.ValidateProposal(ps.proposal) 179 assert.Error(ps.T(), err, "a proposal with a mismatching QC view should be rejected") 180 181 // check that the error is an invalid proposal error to allow creating slashing challenge 182 assert.True(ps.T(), model.IsInvalidBlockError(err), "if the QC has a mismatching view, we should generate a invalid error") 183 } 184 185 func (ps *ProposalSuite) TestProposalMissingParentHigher() { 186 187 // change forks to not find the parent 188 ps.block.QC.View = ps.finalized 189 *ps.forks = mocks.Forks{} 190 ps.forks.On("FinalizedView").Return(ps.finalized) 191 ps.forks.On("GetBlock", ps.block.QC.BlockID).Return(nil, false) 192 193 // check that validation fails now 194 err := ps.validator.ValidateProposal(ps.proposal) 195 assert.Error(ps.T(), err, "a proposal with a missing parent should be rejected") 196 197 // check that the error is a missing block error because we should have the block but we don't 198 assert.True(ps.T(), model.IsMissingBlockError(err), "if we don't have the proposal parent for a QC above or equal finalized view, we should generate an missing block error") 199 } 200 201 func (ps *ProposalSuite) TestProposalMissingParentLower() { 202 203 // change forks to not find the parent 204 ps.block.QC.View = ps.finalized - 1 205 *ps.forks = mocks.Forks{} 206 ps.forks.On("FinalizedView").Return(ps.finalized) 207 ps.forks.On("GetBlock", ps.block.QC.BlockID).Return(nil, false) 208 209 // check that validation fails now 210 err := ps.validator.ValidateProposal(ps.proposal) 211 assert.Error(ps.T(), err, "a proposal with a missing parent should be rejected") 212 213 // check that the error is an unverifiable block because we can't verify the block 214 assert.True(ps.T(), errors.Is(err, model.ErrUnverifiableBlock), "if we don't have the proposal parent for a QC below finalized view, we should generate an unverifiable block error") 215 } 216 217 // TestProposalQCInvalid checks that Validator handles the verifier's error returns correctly. 218 // In case of `model.InvalidFormatError` and model.ErrInvalidSignature`, we expect the Validator 219 // to recognize those as an invalid QC, i.e. returns an `model.InvalidBlockError`. 220 // In contrast, unexpected exceptions and `model.InvalidSignerError` should _not_ be 221 // interpreted as a sign of an invalid QC. 222 func (ps *ProposalSuite) TestProposalQCInvalid() { 223 ps.Run("invalid signature", func() { 224 *ps.verifier = mocks.Verifier{} 225 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return( 226 fmt.Errorf("invalid qc: %w", model.ErrInvalidSignature)) 227 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 228 229 // check that validation fails and the failure case is recognized as an invalid block 230 err := ps.validator.ValidateProposal(ps.proposal) 231 assert.True(ps.T(), model.IsInvalidBlockError(err), "if the block's QC signature is invalid, an ErrorInvalidBlock error should be raised") 232 }) 233 234 ps.Run("invalid format", func() { 235 *ps.verifier = mocks.Verifier{} 236 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(model.NewInvalidFormatErrorf("invalid qc")) 237 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 238 239 // check that validation fails and the failure case is recognized as an invalid block 240 err := ps.validator.ValidateProposal(ps.proposal) 241 assert.True(ps.T(), model.IsInvalidBlockError(err), "if the block's QC has an invalid format, an ErrorInvalidBlock error should be raised") 242 }) 243 244 // Theoretically, `VerifyQC` could also return a `model.InvalidSignerError`. However, 245 // for the time being, we assume that _every_ HotStuff participant is also a member of 246 // the random beacon committee. Consequently, `InvalidSignerError` should not occur atm. 247 // TODO: if the random beacon committee is a strict subset of the HotStuff committee, 248 // we expect `model.InvalidSignerError` here during normal operations. 249 ps.Run("invalid signer", func() { 250 *ps.verifier = mocks.Verifier{} 251 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return( 252 fmt.Errorf("invalid qc: %w", model.NewInvalidSignerErrorf(""))) 253 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 254 255 // check that validation fails and the failure case is recognized as an invalid block 256 err := ps.validator.ValidateProposal(ps.proposal) 257 assert.Error(ps.T(), err) 258 assert.False(ps.T(), model.IsInvalidBlockError(err)) 259 }) 260 261 ps.Run("unknown exception", func() { 262 exception := errors.New("exception") 263 *ps.verifier = mocks.Verifier{} 264 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(exception) 265 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 266 267 // check that validation fails and the failure case is recognized as an invalid block 268 err := ps.validator.ValidateProposal(ps.proposal) 269 assert.ErrorIs(ps.T(), err, exception) 270 assert.False(ps.T(), model.IsInvalidBlockError(err)) 271 }) 272 } 273 274 func (ps *ProposalSuite) TestProposalQCError() { 275 276 // change verifier to fail on QC validation 277 *ps.verifier = mocks.Verifier{} 278 ps.verifier.On("VerifyQC", ps.voters, ps.block.QC.SigData, ps.parent).Return(fmt.Errorf("some exception")) 279 ps.verifier.On("VerifyVote", ps.voter, ps.vote.SigData, ps.block).Return(nil) 280 281 // check that validation fails now 282 err := ps.validator.ValidateProposal(ps.proposal) 283 assert.Error(ps.T(), err, "a proposal with an invalid QC should be rejected") 284 285 // check that the error is an invalid proposal error to allow creating slashing challenge 286 assert.False(ps.T(), model.IsInvalidBlockError(err), "if we can't verify the QC, we should not generate a invalid error") 287 } 288 289 func TestValidateVote(t *testing.T) { 290 suite.Run(t, new(VoteSuite)) 291 } 292 293 type VoteSuite struct { 294 suite.Suite 295 signer *flow.Identity 296 block *model.Block 297 vote *model.Vote 298 forks *mocks.Forks 299 verifier *mocks.Verifier 300 committee *mocks.Committee 301 validator *Validator 302 } 303 304 func (vs *VoteSuite) SetupTest() { 305 306 // create a random signing identity 307 vs.signer = unittest.IdentityFixture(unittest.WithRole(flow.RoleConsensus)) 308 309 // create a block that should be signed 310 vs.block = helper.MakeBlock() 311 312 // create a vote for this block 313 vs.vote = &model.Vote{ 314 View: vs.block.View, 315 BlockID: vs.block.BlockID, 316 SignerID: vs.signer.NodeID, 317 SigData: []byte{}, 318 } 319 320 // set up the mocked forks 321 vs.forks = &mocks.Forks{} 322 vs.forks.On("GetBlock", vs.block.BlockID).Return(vs.block, true) 323 324 // set up the mocked verifier 325 vs.verifier = &mocks.Verifier{} 326 vs.verifier.On("VerifyVote", vs.signer, vs.vote.SigData, vs.block).Return(nil) 327 328 // the leader for the block view is the correct one 329 vs.committee = &mocks.Committee{} 330 vs.committee.On("Identity", mock.Anything, vs.signer.NodeID).Return(vs.signer, nil) 331 332 // set up the validator with the mocked dependencies 333 vs.validator = New(vs.committee, vs.forks, vs.verifier) 334 } 335 336 // TestVoteOK checks the happy case, which is the default for the suite 337 func (vs *VoteSuite) TestVoteOK() { 338 _, err := vs.validator.ValidateVote(vs.vote, vs.block) 339 assert.NoError(vs.T(), err, "a valid vote should be accepted") 340 } 341 342 // TestVoteMismatchingView checks that the Validator handles the case where the 343 // vote contains a mismatching `View` value. In this case, the vote is invalid. 344 // Hence, we expect the Validator to return a `model.InvalidVoteError`. 345 func (vs *VoteSuite) TestVoteMismatchingView() { 346 vs.vote.View++ 347 348 // check that the vote is no longer validated 349 _, err := vs.validator.ValidateVote(vs.vote, vs.block) 350 assert.Error(vs.T(), err, "a vote with a mismatching view should be rejected") 351 352 // TODO: this should raise an error that allows a slashing challenge 353 assert.True(vs.T(), model.IsInvalidVoteError(err), "a mismatching view should create a invalid vote error") 354 } 355 356 // TestVoteSignatureError checks that the Validator does not misinterpret 357 // unexpected exceptions for invalid votes. 358 func (vs *VoteSuite) TestVoteSignatureError() { 359 *vs.verifier = mocks.Verifier{} 360 vs.verifier.On("VerifyVote", vs.signer, vs.vote.SigData, vs.block).Return(fmt.Errorf("some exception")) 361 362 // check that the vote is no longer validated 363 _, err := vs.validator.ValidateVote(vs.vote, vs.block) 364 assert.Error(vs.T(), err, "a vote with error on signature validation should be rejected") 365 assert.False(vs.T(), model.IsInvalidVoteError(err), "internal exception should not be interpreted as invalid vote") 366 } 367 368 // TestVoteInvalidSignerID checks that the Validator correctly handles a vote 369 // with a SignerID that does not correspond to a valid consensus participant. 370 // In this case, the `hotstuff.Committee` returns a `model.InvalidSignerError`, 371 // which the Validator should recognize as a symptom for an invalid vote. 372 // Hence, we expect the validator to return a `model.InvalidVoteError`. 373 func (vs *VoteSuite) TestVoteInvalidSignerID() { 374 *vs.committee = mocks.Committee{} 375 vs.committee.On("Identity", vs.block.BlockID, vs.vote.SignerID).Return(nil, model.NewInvalidSignerErrorf("")) 376 377 // A `model.InvalidSignerError` from the committee should be interpreted as 378 // the Vote being invalid, i.e. we expect an InvalidVoteError to be returned 379 _, err := vs.validator.ValidateVote(vs.vote, vs.block) 380 assert.Error(vs.T(), err, "a vote with unknown SignerID should be rejected") 381 assert.True(vs.T(), model.IsInvalidVoteError(err), "a vote with unknown SignerID should be rejected") 382 } 383 384 // TestVoteSignatureInvalid checks that the Validator correctly handles votes 385 // with cryptographically invalid signature. In this case, the `hotstuff.Verifier` 386 // returns a `model.ErrInvalidSignature`, which the Validator should recognize as 387 // a symptom for an invalid vote. 388 // Hence, we expect the validator to return a `model.InvalidVoteError`. 389 func (vs *VoteSuite) TestVoteSignatureInvalid() { 390 *vs.verifier = mocks.Verifier{} 391 vs.verifier.On("VerifyVote", vs.signer, vs.vote.SigData, vs.block).Return(fmt.Errorf("staking sig is invalid: %w", model.ErrInvalidSignature)) 392 393 // A `model.ErrInvalidSignature` from the `hotstuff.Verifier` should be interpreted as 394 // the Vote being invalid, i.e. we expect an InvalidVoteError to be returned 395 _, err := vs.validator.ValidateVote(vs.vote, vs.block) 396 assert.Error(vs.T(), err, "a vote with an invalid signature should be rejected") 397 assert.True(vs.T(), model.IsInvalidVoteError(err), "a vote with an invalid signature should be rejected") 398 } 399 400 func TestValidateQC(t *testing.T) { 401 suite.Run(t, new(QCSuite)) 402 } 403 404 type QCSuite struct { 405 suite.Suite 406 participants flow.IdentityList 407 signers flow.IdentityList 408 block *model.Block 409 qc *flow.QuorumCertificate 410 committee *mocks.Committee 411 verifier *mocks.Verifier 412 validator *Validator 413 } 414 415 func (qs *QCSuite) SetupTest() { 416 // create a list of 10 nodes with 1-weight each 417 qs.participants = unittest.IdentityListFixture(10, 418 unittest.WithRole(flow.RoleConsensus), 419 unittest.WithWeight(1), 420 ) 421 422 // signers are a qualified majority at 7 423 qs.signers = qs.participants[:7] 424 425 // create a block that has the signers in its QC 426 qs.block = helper.MakeBlock() 427 indices, err := signature.EncodeSignersToIndices(qs.participants.NodeIDs(), qs.signers.NodeIDs()) 428 require.NoError(qs.T(), err) 429 430 qs.qc = helper.MakeQC(helper.WithQCBlock(qs.block), helper.WithQCSigners(indices)) 431 432 // return the correct participants and identities from view state 433 qs.committee = &mocks.Committee{} 434 qs.committee.On("Identities", mock.Anything).Return( 435 func(blockID flow.Identifier) flow.IdentityList { 436 return qs.participants 437 }, 438 nil, 439 ) 440 441 // set up the mocked verifier to verify the QC correctly 442 qs.verifier = &mocks.Verifier{} 443 qs.verifier.On("VerifyQC", qs.signers, qs.qc.SigData, qs.block).Return(nil) 444 445 // set up the validator with the mocked dependencies 446 qs.validator = New(qs.committee, nil, qs.verifier) 447 } 448 449 // TestQCOK verifies the default happy case 450 func (qs *QCSuite) TestQCOK() { 451 err := qs.validator.ValidateQC(qs.qc, qs.block) 452 assert.NoError(qs.T(), err, "a valid QC should be accepted") 453 } 454 455 // TestQCRetrievingParticipantsError tests that validation errors if: 456 // there is an error retrieving identities of consensus participants 457 func (qs *QCSuite) TestQCRetrievingParticipantsError() { 458 // change the hotstuff.Committee to fail on retrieving participants 459 *qs.committee = mocks.Committee{} 460 qs.committee.On("Identities", mock.Anything, mock.Anything).Return(qs.participants, errors.New("FATAL internal error")) 461 462 // verifier should escalate unspecific internal error to surrounding logic, but NOT as ErrorInvalidBlock 463 err := qs.validator.ValidateQC(qs.qc, qs.block) 464 assert.Error(qs.T(), err, "unspecific error when retrieving consensus participants should be escalated to surrounding logic") 465 assert.False(qs.T(), model.IsInvalidBlockError(err), "unspecific internal errors should not result in ErrorInvalidBlock error") 466 } 467 468 // TestQCSignersError tests that a qc fails validation if: 469 // QC signer's have insufficient weight (but are all valid consensus participants otherwise) 470 func (qs *QCSuite) TestQCInsufficientWeight() { 471 // signers only have weight 6 out of 10 total (NOT have a supermajority) 472 qs.signers = qs.participants[:6] 473 indices, err := signature.EncodeSignersToIndices(qs.participants.NodeIDs(), qs.signers.NodeIDs()) 474 require.NoError(qs.T(), err) 475 476 qs.qc = helper.MakeQC(helper.WithQCBlock(qs.block), helper.WithQCSigners(indices)) 477 478 // the QC should not be validated anymore 479 err = qs.validator.ValidateQC(qs.qc, qs.block) 480 assert.Error(qs.T(), err, "a QC should be rejected if it has insufficient voted weight") 481 482 // we should get a threshold error to bubble up for extra info 483 assert.True(qs.T(), model.IsInvalidBlockError(err), "if there is insufficient voted weight, an invalid block error should be raised") 484 } 485 486 // TestQCSignatureError tests that validation errors if: 487 // there is an unspecific internal error while validating the signature 488 func (qs *QCSuite) TestQCSignatureError() { 489 490 // set up the verifier to fail QC verification 491 *qs.verifier = mocks.Verifier{} 492 qs.verifier.On("VerifyQC", qs.signers, qs.qc.SigData, qs.block).Return(errors.New("dummy error")) 493 494 // verifier should escalate unspecific internal error to surrounding logic, but NOT as ErrorInvalidBlock 495 err := qs.validator.ValidateQC(qs.qc, qs.block) 496 assert.Error(qs.T(), err, "unspecific sig verification error should be escalated to surrounding logic") 497 assert.False(qs.T(), model.IsInvalidBlockError(err), "unspecific internal errors should not result in ErrorInvalidBlock error") 498 } 499 500 // TestQCSignatureInvalid verifies that the Validator correctly handles the model.ErrInvalidSignature. 501 // This error return from `Verifier.VerifyQC` is an expected failure case in case of a byzantine input, where 502 // one of the signatures in the QC is broken. Hence, the Validator should wrap it as InvalidBlockError. 503 func (qs *QCSuite) TestQCSignatureInvalid() { 504 // change the verifier to fail the QC signature 505 *qs.verifier = mocks.Verifier{} 506 qs.verifier.On("VerifyQC", qs.signers, qs.qc.SigData, qs.block).Return( 507 fmt.Errorf("invalid signer sig: %w", model.ErrInvalidSignature)) 508 509 // the QC be considered as invalid 510 err := qs.validator.ValidateQC(qs.qc, qs.block) 511 assert.True(qs.T(), model.IsInvalidBlockError(err), "if the signature is invalid an ErrorInvalidBlock error should be raised") 512 } 513 514 // TestQCSignatureInvalidFormat verifies that the Validator correctly handles the model.InvalidFormatError. 515 // This error return from `Verifier.VerifyQC` is an expected failure case in case of a byzantine input, where 516 // some binary vector (e.g. `sigData`) is broken. Hence, the Validator should wrap it as InvalidBlockError. 517 func (qs *QCSuite) TestQCSignatureInvalidFormat() { 518 // change the verifier to fail the QC signature 519 *qs.verifier = mocks.Verifier{} 520 qs.verifier.On("VerifyQC", qs.signers, qs.qc.SigData, qs.block).Return( 521 fmt.Errorf("%w", model.NewInvalidFormatErrorf("invalid sigType"))) 522 523 // the QC be considered as invalid 524 err := qs.validator.ValidateQC(qs.qc, qs.block) 525 assert.True(qs.T(), model.IsInvalidBlockError(err), "if the signature has an invalid format, an ErrorInvalidBlock error should be raised") 526 } 527 528 // TestQCEmptySigners verifies that the Validator correctly handles the model.InsufficientSignaturesError: 529 // In the validator, we previously checked the total weight of all signers meets the supermajority threshold, 530 // which is a _positive_ number. Hence, there must be at least one signer. Hence, `Verifier.VerifyQC` 531 // returning this error would be a symptom of a fatal internal bug. The Validator should _not_ interpret 532 // this error as an invalid QC / invalid block, i.e. it should _not_ return an `InvalidBlockError`. 533 func (qs *QCSuite) TestQCEmptySigners() { 534 *qs.verifier = mocks.Verifier{} 535 qs.verifier.On("VerifyQC", mock.Anything, qs.qc.SigData, qs.block).Return( 536 fmt.Errorf("%w", model.NewInsufficientSignaturesErrorf(""))) 537 538 // the Validator should _not_ interpret this as a invalid QC, but as an internal error 539 err := qs.validator.ValidateQC(qs.qc, qs.block) 540 assert.True(qs.T(), model.IsInsufficientSignaturesError(err)) // unexpected error should be wrapped and propagated upwards 541 assert.False(qs.T(), model.IsInvalidBlockError(err), err, "should _not_ interpret this as a invalid QC, but as an internal error") 542 }