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  }