github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/validator/validator.go (about) 1 package validator 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/onflow/flow-go/consensus/hotstuff" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/module/signature" 11 ) 12 13 // Validator is responsible for validating QC, Block and Vote 14 type Validator struct { 15 committee hotstuff.Replicas 16 verifier hotstuff.Verifier 17 } 18 19 var _ hotstuff.Validator = (*Validator)(nil) 20 21 // New creates a new Validator instance 22 func New( 23 committee hotstuff.Replicas, 24 verifier hotstuff.Verifier, 25 ) *Validator { 26 return &Validator{ 27 committee: committee, 28 verifier: verifier, 29 } 30 } 31 32 // ValidateTC validates the TimeoutCertificate `TC`. 33 // During normal operations, the following error returns are expected: 34 // - model.InvalidTCError if the TC is invalid 35 // - model.ErrViewForUnknownEpoch if the TC refers unknown epoch 36 // 37 // Any other error should be treated as exception 38 func (v *Validator) ValidateTC(tc *flow.TimeoutCertificate) error { 39 newestQC := tc.NewestQC 40 if newestQC == nil { 41 return newInvalidTCError(tc, fmt.Errorf("TC must include a QC but found nil")) 42 } 43 44 // The TC's view cannot be smaller than the view of the QC it contains. 45 // Note: we specifically allow for the TC to have the same view as the highest QC. 46 // This is useful as a fallback, because it allows replicas other than the designated 47 // leader to also collect votes and generate a QC. 48 if tc.View < newestQC.View { 49 return newInvalidTCError(tc, fmt.Errorf("TC's QC cannot be newer than the TC's view")) 50 } 51 52 // 1. Check if there is super-majority of votes 53 allParticipants, err := v.committee.IdentitiesByEpoch(tc.View) 54 if err != nil { 55 return fmt.Errorf("could not get consensus participants at view %d: %w", tc.View, err) 56 } 57 signers, err := signature.DecodeSignerIndicesToIdentities(allParticipants, tc.SignerIndices) 58 if err != nil { 59 if signature.IsInvalidSignerIndicesError(err) { 60 return newInvalidTCError(tc, fmt.Errorf("invalid signer indices: %w", err)) 61 } 62 // unexpected error 63 return fmt.Errorf("unexpected internal error decoding signer indices: %w", err) 64 } 65 66 // determine whether signers reach minimally required weight threshold for consensus 67 threshold, err := v.committee.QuorumThresholdForView(tc.View) 68 if err != nil { 69 return fmt.Errorf("could not get weight threshold for view %d: %w", tc.View, err) 70 } 71 if signers.TotalWeight() < threshold { 72 return newInvalidTCError(tc, fmt.Errorf("tc signers have insufficient weight of %d (required=%d)", signers.TotalWeight(), threshold)) 73 } 74 75 // Verify multi-message BLS sig of TC, by far the most expensive check 76 err = v.verifier.VerifyTC(signers, tc.SigData, tc.View, tc.NewestQCViews) 77 if err != nil { 78 // Considerations about other errors that `VerifyTC` could return: 79 // * model.InsufficientSignaturesError: we previously checked the total weight of all signers 80 // meets the supermajority threshold, which is a _positive_ number. Hence, there must be at 81 // least one signer. Hence, receiving this error would be a symptom of a fatal internal bug. 82 switch { 83 case model.IsInvalidFormatError(err): 84 return newInvalidTCError(tc, fmt.Errorf("TC's signature data has an invalid structure: %w", err)) 85 case errors.Is(err, model.ErrInvalidSignature): 86 return newInvalidTCError(tc, fmt.Errorf("TC contains invalid signature(s): %w", err)) 87 default: 88 return fmt.Errorf("cannot verify tc's aggregated signature (tc.View: %d): %w", tc.View, err) 89 } 90 } 91 92 // verifying that tc.NewestQC is the QC with the highest view. 93 // Note: A byzantine TC could include `nil` for tc.NewestQCViews, in which case `tc.NewestQCViews[0]` 94 // would panic. Though, per API specification `verifier.VerifyTC(…)` should return a `model.InvalidFormatError` 95 // if `signers` and `tc.NewestQCViews` have different length. Hence, the following code is safe only if it is executed 96 // 1. _after_ checking the quorum threshold (thereby we guarantee that `signers` is not empty); and 97 // 2. _after_ `verifier.VerifyTC(…)`, which enforces that `signers` and `tc.NewestQCViews` have identical length. 98 // Only then we can be sure that `tc.NewestQCViews` cannot be nil. 99 newestQCView := tc.NewestQCViews[0] 100 for _, view := range tc.NewestQCViews { 101 if newestQCView < view { 102 newestQCView = view 103 } 104 } 105 if newestQCView > tc.NewestQC.View { 106 return newInvalidTCError(tc, fmt.Errorf("included QC (view=%d) should be equal or higher to highest contributed view: %d", tc.NewestQC.View, newestQCView)) 107 } 108 109 // Validate QC 110 err = v.ValidateQC(newestQC) 111 if err != nil { 112 if model.IsInvalidQCError(err) { 113 return newInvalidTCError(tc, fmt.Errorf("invalid QC included in TC: %w", err)) 114 } 115 if errors.Is(err, model.ErrViewForUnknownEpoch) { 116 // We require each replica to be bootstrapped with a QC pointing to a finalized block. Consensus safety rules guarantee that 117 // a QC at least as new as the root QC must be contained in any TC. This is because the TC must include signatures from a 118 // supermajority of replicas, including at least one honest replica, which attest to their locally highest known QC. Hence, 119 // any QC included in a TC must be the root QC or newer. Therefore, we should know the Epoch for any QC we encounter. 120 // receiving a `model.ErrViewForUnknownEpoch` is conceptually impossible, i.e. a symptom of an internal bug or invalid 121 // bootstrapping information. 122 return fmt.Errorf("no Epoch information availalbe for QC that was included in TC; symptom of internal bug or invalid bootstrapping information: %s", err.Error()) 123 } 124 return fmt.Errorf("unexpected internal error while verifying the QC included in the TC: %w", err) 125 } 126 127 return nil 128 } 129 130 // ValidateQC validates the Quorum Certificate `qc`. 131 // During normal operations, the following error returns are expected: 132 // - model.InvalidQCError if the QC is invalid 133 // - model.ErrViewForUnknownEpoch if the QC refers unknown epoch 134 // 135 // Any other error should be treated as exception 136 func (v *Validator) ValidateQC(qc *flow.QuorumCertificate) error { 137 // Retrieve the initial identities of consensus participants for this epoch, 138 // and those that signed the QC. IdentitiesByEpoch contains all nodes that were 139 // authorized to sign during this epoch. Ejection and dynamic weight adjustments 140 // are not taken into account here. By using an epoch-static set of authorized 141 // signers, we can check QC validity without needing all ancestor blocks. 142 allParticipants, err := v.committee.IdentitiesByEpoch(qc.View) 143 if err != nil { 144 return fmt.Errorf("could not get consensus participants at view %d: %w", qc.View, err) 145 } 146 147 signers, err := signature.DecodeSignerIndicesToIdentities(allParticipants, qc.SignerIndices) 148 if err != nil { 149 if signature.IsInvalidSignerIndicesError(err) { 150 return newInvalidQCError(qc, fmt.Errorf("invalid signer indices: %w", err)) 151 } 152 // unexpected error 153 return fmt.Errorf("unexpected internal error decoding signer indices: %w", err) 154 } 155 156 // determine whether signers reach minimally required weight threshold for consensus 157 threshold, err := v.committee.QuorumThresholdForView(qc.View) 158 if err != nil { 159 return fmt.Errorf("could not get weight threshold for view %d: %w", qc.View, err) 160 } 161 if signers.TotalWeight() < threshold { 162 return newInvalidQCError(qc, fmt.Errorf("QC signers have insufficient weight of %d (required=%d)", signers.TotalWeight(), threshold)) 163 } 164 165 // verify whether the signature bytes are valid for the QC 166 err = v.verifier.VerifyQC(signers, qc.SigData, qc.View, qc.BlockID) 167 if err != nil { 168 // Considerations about other errors that `VerifyQC` could return: 169 // * model.InvalidSignerError: for the time being, we assume that _every_ HotStuff participant 170 // is also a member of the random beacon committee. Consequently, `InvalidSignerError` should 171 // not occur atm. 172 // TODO: if the random beacon committee is a strict subset of the HotStuff committee, 173 // we expect `model.InvalidSignerError` here during normal operations. 174 // * model.InsufficientSignaturesError: we previously checked the total weight of all signers 175 // meets the supermajority threshold, which is a _positive_ number. Hence, there must be at 176 // least one signer. Hence, receiving this error would be a symptom of a fatal internal bug. 177 switch { 178 case model.IsInvalidFormatError(err): 179 return newInvalidQCError(qc, fmt.Errorf("QC's signature data has an invalid structure: %w", err)) 180 case errors.Is(err, model.ErrInvalidSignature): 181 return newInvalidQCError(qc, fmt.Errorf("QC contains invalid signature(s): %w", err)) 182 case errors.Is(err, model.ErrViewForUnknownEpoch): 183 // We have earlier queried the Identities for the QC's view, which must have returned proper values, 184 // otherwise, we wouldn't reach this code. Therefore, it should be impossible for `verifier.VerifyQC` 185 // to return ErrViewForUnknownEpoch. To avoid confusion with expected sentinel errors, we only preserve 186 // the error messages here, but not the error types. 187 return fmt.Errorf("internal error, as querying identities for view %d succeeded earlier but now the view supposedly belongs to an unknown epoch: %s", qc.View, err.Error()) 188 default: 189 return fmt.Errorf("cannot verify qc's aggregated signature (qc.BlockID: %x): %w", qc.BlockID, err) 190 } 191 } 192 193 return nil 194 } 195 196 // ValidateProposal validates the block proposal 197 // A block is considered as valid if it's a valid extension of existing forks. 198 // Note it doesn't check if it's conflicting with finalized block 199 // During normal operations, the following error returns are expected: 200 // - model.InvalidProposalError if the block is invalid 201 // - model.ErrViewForUnknownEpoch if the proposal refers unknown epoch 202 // 203 // Any other error should be treated as exception 204 func (v *Validator) ValidateProposal(proposal *model.Proposal) error { 205 qc := proposal.Block.QC 206 block := proposal.Block 207 208 // validate the proposer's vote and get his identity 209 _, err := v.ValidateVote(proposal.ProposerVote()) 210 if model.IsInvalidVoteError(err) { 211 return model.NewInvalidProposalErrorf(proposal, "invalid proposer signature: %w", err) 212 } 213 if err != nil { 214 return fmt.Errorf("error verifying leader signature for block %x: %w", block.BlockID, err) 215 } 216 217 // check the proposer is the leader for the proposed block's view 218 leader, err := v.committee.LeaderForView(block.View) 219 if err != nil { 220 return fmt.Errorf("error determining leader for block %x: %w", block.BlockID, err) 221 } 222 if leader != block.ProposerID { 223 return model.NewInvalidProposalErrorf(proposal, "proposer %s is not leader (%s) for view %d", block.ProposerID, leader, block.View) 224 } 225 226 // The Block must contain a proof that the primary legitimately entered the respective view. 227 // Transitioning to proposal.Block.View is possible either by observing a QC or a TC for the 228 // previous round. If and only if the QC is _not_ for the previous round we require a TC for 229 // the previous view to be present. 230 lastViewSuccessful := proposal.Block.View == proposal.Block.QC.View+1 231 if !lastViewSuccessful { 232 // check if proposal is correctly structured 233 if proposal.LastViewTC == nil { 234 return model.NewInvalidProposalErrorf(proposal, "QC in block is not for previous view, so expecting a TC but none is included in block") 235 } 236 237 // check if included TC is for previous view 238 if proposal.Block.View != proposal.LastViewTC.View+1 { 239 return model.NewInvalidProposalErrorf(proposal, "QC in block is not for previous view, so expecting a TC for view %d but got TC for view %d", proposal.Block.View-1, proposal.LastViewTC.View) 240 } 241 242 // Check if proposal extends either the newest QC specified in the TC, or a newer QC 243 // in edge cases a leader may construct a TC and QC concurrently such that TC contains 244 // an older QC - in these case we still want to build on the newest QC, so this case is allowed. 245 if proposal.Block.QC.View < proposal.LastViewTC.NewestQC.View { 246 return model.NewInvalidProposalErrorf(proposal, "TC in block contains a newer QC than the block itself, which is a protocol violation") 247 } 248 } else if proposal.LastViewTC != nil { 249 // last view ended with QC, including TC is a protocol violation 250 return model.NewInvalidProposalErrorf(proposal, "last view has ended with QC but proposal includes LastViewTC") 251 } 252 253 // Check signatures, keep the most expensive the last to check 254 255 // check if included QC is valid 256 err = v.ValidateQC(qc) 257 if err != nil { 258 if model.IsInvalidQCError(err) { 259 return model.NewInvalidProposalErrorf(proposal, "invalid qc included: %w", err) 260 } 261 if errors.Is(err, model.ErrViewForUnknownEpoch) { 262 // We require each replica to be bootstrapped with a QC pointing to a finalized block. Therefore, we should know the 263 // Epoch for any QC.View and TC.View we encounter. Receiving a `model.ErrViewForUnknownEpoch` is conceptually impossible, 264 // i.e. a symptom of an internal bug or invalid bootstrapping information. 265 return fmt.Errorf("no Epoch information availalbe for QC that was included in proposal; symptom of internal bug or invalid bootstrapping information: %s", err.Error()) 266 } 267 return fmt.Errorf("unexpected error verifying qc: %w", err) 268 } 269 270 if !lastViewSuccessful { 271 // check if included TC is valid 272 err = v.ValidateTC(proposal.LastViewTC) 273 if err != nil { 274 if model.IsInvalidTCError(err) { 275 return model.NewInvalidProposalErrorf(proposal, "proposals TC's is not valid: %w", err) 276 } 277 if errors.Is(err, model.ErrViewForUnknownEpoch) { 278 // We require each replica to be bootstrapped with a QC pointing to a finalized block. Therefore, we should know the 279 // Epoch for any QC.View and TC.View we encounter. Receiving a `model.ErrViewForUnknownEpoch` is conceptually impossible, 280 // i.e. a symptom of an internal bug or invalid bootstrapping information. 281 return fmt.Errorf("no Epoch information availalbe for QC that was included in TC; symptom of internal bug or invalid bootstrapping information: %s", err.Error()) 282 } 283 return fmt.Errorf("unexpected internal error while verifying the TC included in block: %w", err) 284 } 285 } 286 287 return nil 288 } 289 290 // ValidateVote validates the vote and returns the identity of the voter who signed 291 // vote - the vote to be validated 292 // During normal operations, the following error returns are expected: 293 // - model.InvalidVoteError for invalid votes 294 // - model.ErrViewForUnknownEpoch if the vote refers unknown epoch 295 // 296 // Any other error should be treated as exception 297 func (v *Validator) ValidateVote(vote *model.Vote) (*flow.IdentitySkeleton, error) { 298 voter, err := v.committee.IdentityByEpoch(vote.View, vote.SignerID) 299 if model.IsInvalidSignerError(err) { 300 return nil, newInvalidVoteError(vote, err) 301 } 302 if err != nil { 303 return nil, fmt.Errorf("error retrieving voter Identity at view %d: %w", vote.View, err) 304 } 305 306 // check whether the signature data is valid for the vote in the hotstuff context 307 err = v.verifier.VerifyVote(voter, vote.SigData, vote.View, vote.BlockID) 308 if err != nil { 309 // Theoretically, `VerifyVote` could also return a `model.InvalidSignerError`. However, 310 // for the time being, we assume that _every_ HotStuff participant is also a member of 311 // the random beacon committee. Consequently, `InvalidSignerError` should not occur atm. 312 // TODO: if the random beacon committee is a strict subset of the HotStuff committee, 313 // we expect `model.InvalidSignerError` here during normal operations. 314 if model.IsInvalidFormatError(err) || errors.Is(err, model.ErrInvalidSignature) { 315 return nil, newInvalidVoteError(vote, err) 316 } 317 if errors.Is(err, model.ErrViewForUnknownEpoch) { 318 return nil, fmt.Errorf("no Epoch information availalbe for vote; symptom of internal bug or invalid bootstrapping information: %s", err.Error()) 319 } 320 return nil, fmt.Errorf("cannot verify signature for vote (%x): %w", vote.ID(), err) 321 } 322 323 return voter, nil 324 } 325 326 func newInvalidQCError(qc *flow.QuorumCertificate, err error) error { 327 return model.InvalidQCError{ 328 BlockID: qc.BlockID, 329 View: qc.View, 330 Err: err, 331 } 332 } 333 334 func newInvalidTCError(tc *flow.TimeoutCertificate, err error) error { 335 return model.InvalidTCError{ 336 View: tc.View, 337 Err: err, 338 } 339 } 340 341 func newInvalidVoteError(vote *model.Vote, err error) error { 342 return model.InvalidVoteError{ 343 Vote: vote, 344 Err: err, 345 } 346 }