github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/approvals/verifying_assignment_collector.go (about)

     1  package approvals
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/rs/zerolog"
     8  	"github.com/rs/zerolog/log"
     9  
    10  	"github.com/onflow/flow-go/engine"
    11  	"github.com/onflow/flow-go/engine/consensus"
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/model/flow/filter"
    14  	"github.com/onflow/flow-go/model/messages"
    15  	"github.com/onflow/flow-go/module/mempool"
    16  	"github.com/onflow/flow-go/state/protocol"
    17  	"github.com/onflow/flow-go/utils/rand"
    18  )
    19  
    20  // **Emergency-sealing parameters**
    21  
    22  // DefaultEmergencySealingThresholdForFinalization is the minimal number of unsealed but finalized descendants that a
    23  // block must have in order to be eligible for emergency sealing (further conditions apply for emergency sealing).
    24  const DefaultEmergencySealingThresholdForFinalization = 100
    25  
    26  // DefaultEmergencySealingThresholdForVerification is the minimal number of finalized descendants
    27  // that the block _incorporating_ an Execution Result [ER] must have for the ER to be eligible for
    28  // emergency sealing (further conditions apply for emergency sealing).
    29  const DefaultEmergencySealingThresholdForVerification = 25
    30  
    31  // VerifyingAssignmentCollector
    32  // Context:
    33  //   - When the same result is incorporated in multiple different forks,
    34  //     unique verifier assignment is determined for each fork.
    35  //   - The assignment collector is intended to encapsulate the known
    36  //     assignments for a particular execution result.
    37  //
    38  // VerifyingAssignmentCollector has a strict ordering of processing, before processing
    39  // approvals at least one incorporated result has to be processed.
    40  // VerifyingAssignmentCollector takes advantage of internal caching to speed up processing approvals for different assignments
    41  // VerifyingAssignmentCollector is responsible for validating approvals on result-level (checking signature, identity).
    42  type VerifyingAssignmentCollector struct {
    43  	AssignmentCollectorBase
    44  
    45  	log                    zerolog.Logger
    46  	lock                   sync.RWMutex
    47  	collectors             map[flow.Identifier]*ApprovalCollector // collectors is a mapping IncorporatedBlockID -> ApprovalCollector
    48  	authorizedApprovers    map[flow.Identifier]*flow.Identity     // map of approvers pre-selected at block that is being sealed
    49  	verifiedApprovalsCache *ApprovalsCache                        // in-memory cache of approvals (already verified)
    50  }
    51  
    52  // NewVerifyingAssignmentCollector instantiates a new VerifyingAssignmentCollector.
    53  // All errors are unexpected and potential symptoms of internal bugs or state corruption (fatal).
    54  func NewVerifyingAssignmentCollector(collectorBase AssignmentCollectorBase) (*VerifyingAssignmentCollector, error) {
    55  	// pre-select all authorized verifiers at the block that is being sealed
    56  	authorizedApprovers, err := authorizedVerifiersAtBlock(collectorBase.state, collectorBase.BlockID())
    57  	if err != nil {
    58  		return nil, fmt.Errorf("could not determine authorized verifiers for sealing candidate: %w", err)
    59  	}
    60  	numberChunks := collectorBase.result.Chunks.Len()
    61  
    62  	return &VerifyingAssignmentCollector{
    63  		AssignmentCollectorBase: collectorBase,
    64  		log:                     collectorBase.log.With().Str("component", "verifying_assignment_collector").Logger(),
    65  		lock:                    sync.RWMutex{},
    66  		collectors:              make(map[flow.Identifier]*ApprovalCollector),
    67  		authorizedApprovers:     authorizedApprovers,
    68  		verifiedApprovalsCache:  NewApprovalsCache(uint(numberChunks * len(authorizedApprovers))),
    69  	}, nil
    70  }
    71  
    72  func (ac *VerifyingAssignmentCollector) collectorByBlockID(incorporatedBlockID flow.Identifier) *ApprovalCollector {
    73  	ac.lock.RLock()
    74  	defer ac.lock.RUnlock()
    75  	return ac.collectors[incorporatedBlockID]
    76  }
    77  
    78  // emergencySealable determines whether an incorporated Result qualifies for "emergency sealing".
    79  // ATTENTION: this is a temporary solution, which is NOT BFT compatible. When the approval process
    80  // hangs far enough behind finalization (measured in finalized but unsealed blocks), emergency
    81  // sealing kicks in. This will be removed when implementation of Sealing & Verification is finished.
    82  func (ac *VerifyingAssignmentCollector) emergencySealable(collector *ApprovalCollector, finalizedBlockHeight uint64) bool {
    83  	// Criterion for emergency sealing, both of the following condition need to be true for trigger emergency sealing:
    84  	// 1. There must be at least DefaultEmergencySealingThresholdForFinalization number of blocks between
    85  	//    the executed block and the latest finalized block
    86  	// 2. there must be at least DefaultEmergencySealingThresholdForVerification number of blocks between
    87  	//    the block that _incorporates_ result and the latest finalized block
    88  	return collector.executedBlock.Height+DefaultEmergencySealingThresholdForFinalization <= finalizedBlockHeight &&
    89  		collector.IncorporatedBlock().Height+DefaultEmergencySealingThresholdForVerification <= finalizedBlockHeight
    90  }
    91  
    92  // CheckEmergencySealing checks the managed assignments whether their result can be emergency
    93  // sealed. Seals the results where possible.
    94  // It returns error when running into any exception
    95  // It returns nil when it's done the checking regardless whether there is any results being emergency sealed or not
    96  func (ac *VerifyingAssignmentCollector) CheckEmergencySealing(observer consensus.SealingObservation, finalizedBlockHeight uint64) error {
    97  	for _, collector := range ac.allCollectors() {
    98  		sealable := ac.emergencySealable(collector, finalizedBlockHeight)
    99  		observer.QualifiesForEmergencySealing(collector.IncorporatedResult(), sealable)
   100  		if sealable {
   101  			err := collector.SealResult()
   102  			if err != nil {
   103  				return fmt.Errorf("could not create emergency seal for result %x incorporated at %x: %w",
   104  					ac.ResultID(), collector.IncorporatedBlockID(), err)
   105  			}
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (ac *VerifyingAssignmentCollector) ProcessingStatus() ProcessingStatus {
   113  	return VerifyingApprovals
   114  }
   115  
   116  // ProcessIncorporatedResult starts tracking the approval for IncorporatedResult.
   117  // Method is idempotent.
   118  // Error Returns:
   119  //   - no errors expected during normal operation;
   120  //     errors might be symptoms of bugs or internal state corruption (fatal)
   121  func (ac *VerifyingAssignmentCollector) ProcessIncorporatedResult(incorporatedResult *flow.IncorporatedResult) error {
   122  	ac.log.Debug().
   123  		Str("result_id", incorporatedResult.Result.ID().String()).
   124  		Str("incorporated_block_id", incorporatedResult.IncorporatedBlockID.String()).
   125  		Str("block_id", incorporatedResult.Result.BlockID.String()).
   126  		Msg("processing incorporated result")
   127  
   128  	// check that result is the one that this VerifyingAssignmentCollector manages
   129  	if irID := incorporatedResult.Result.ID(); irID != ac.ResultID() {
   130  		return fmt.Errorf("this VerifyingAssignmentCollector manages result %x but got %x", ac.ResultID(), irID)
   131  	}
   132  
   133  	// NoOp, if we already have a collector for this incorporatedResult
   134  	incorporatedBlockID := incorporatedResult.IncorporatedBlockID
   135  	if collector := ac.collectorByBlockID(incorporatedBlockID); collector != nil {
   136  		return nil
   137  	}
   138  
   139  	// Constructing ApprovalCollector for IncorporatedResult
   140  	// The VerifyingAssignmentCollector is not locked while instantiating the ApprovalCollector. Hence, it is possible that
   141  	// multiple threads simultaneously compute the verifier assignment. Nevertheless, the implementation is safe in
   142  	// that only one of the instantiated ApprovalCollectors will be stored in the cache. In terms of locking duration,
   143  	// it's better to perform extra computation in edge cases than lock this logic with a mutex,
   144  	// since it's quite unlikely that same incorporated result will be processed by multiple goroutines simultaneously.
   145  	assignment, err := ac.assigner.Assign(incorporatedResult.Result, incorporatedBlockID)
   146  	if err != nil {
   147  		return fmt.Errorf("could not determine chunk assignment: %w", err)
   148  	}
   149  	incorporatedBlock, err := ac.headers.ByBlockID(incorporatedBlockID)
   150  	if err != nil {
   151  		return fmt.Errorf("failed to retrieve header of incorporated block %s: %w",
   152  			incorporatedBlockID, err)
   153  	}
   154  	executedBlock, err := ac.headers.ByBlockID(incorporatedResult.Result.BlockID)
   155  	if err != nil {
   156  		return fmt.Errorf("failed to retrieve header of incorporatedResult %s: %w",
   157  			incorporatedResult.Result.BlockID, err)
   158  	}
   159  	collector, err := NewApprovalCollector(ac.log, incorporatedResult, incorporatedBlock, executedBlock, assignment, ac.seals, ac.requiredApprovalsForSealConstruction)
   160  	if err != nil {
   161  		return fmt.Errorf("instantiation of ApprovalCollector failed: %w", err)
   162  	}
   163  
   164  	// Now, we add the ApprovalCollector to the VerifyingAssignmentCollector:
   165  	// no-op if an ApprovalCollector has already been added by a different routine
   166  	isDuplicate := ac.putCollector(incorporatedBlockID, collector)
   167  	if isDuplicate {
   168  		return nil
   169  	}
   170  
   171  	// process approvals that have passed needed checks and are ready to be processed
   172  	for _, approval := range ac.verifiedApprovalsCache.All() {
   173  		// those approvals are verified already and shouldn't yield any errors
   174  		err = collector.ProcessApproval(approval)
   175  		if err != nil {
   176  			return fmt.Errorf("processing already validated approval %x failed: %w", approval.ID(), err)
   177  		}
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  // putCollector stores the collector if it is not already present in the collectors map
   184  // and returns false (no duplicate). NoOp if a collector for the incorporatedBlockID is
   185  // already stored, in which case true is returned (indicating a duplicate).
   186  func (ac *VerifyingAssignmentCollector) putCollector(incorporatedBlockID flow.Identifier, collector *ApprovalCollector) bool {
   187  	ac.lock.Lock()
   188  	defer ac.lock.Unlock()
   189  	if _, ok := ac.collectors[incorporatedBlockID]; ok {
   190  		return true
   191  	}
   192  	ac.collectors[incorporatedBlockID] = collector
   193  	return false
   194  }
   195  
   196  func (ac *VerifyingAssignmentCollector) allCollectors() []*ApprovalCollector {
   197  	ac.lock.RLock()
   198  	defer ac.lock.RUnlock()
   199  	collectors := make([]*ApprovalCollector, 0, len(ac.collectors))
   200  	for _, collector := range ac.collectors {
   201  		collectors = append(collectors, collector)
   202  	}
   203  	return collectors
   204  }
   205  
   206  func (ac *VerifyingAssignmentCollector) verifyAttestationSignature(approval *flow.ResultApprovalBody, nodeIdentity *flow.Identity) error {
   207  	id := approval.Attestation.ID()
   208  	valid, err := nodeIdentity.StakingPubKey.Verify(approval.AttestationSignature, id[:], ac.sigHasher)
   209  	if err != nil {
   210  		return fmt.Errorf("failed to verify attestation signature: %w", err)
   211  	}
   212  
   213  	if !valid {
   214  		return engine.NewInvalidInputErrorf("invalid attestation signature for (%x)", nodeIdentity.NodeID)
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  func (ac *VerifyingAssignmentCollector) verifySignature(approval *flow.ResultApproval, nodeIdentity *flow.Identity) error {
   221  	id := approval.Body.ID()
   222  	valid, err := nodeIdentity.StakingPubKey.Verify(approval.VerifierSignature, id[:], ac.sigHasher)
   223  	if err != nil {
   224  		return fmt.Errorf("failed to verify approval signature: %w", err)
   225  	}
   226  
   227  	if !valid {
   228  		return engine.NewInvalidInputErrorf("invalid signature for (%x)", nodeIdentity.NodeID)
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // validateApproval performs result level checks of flow.ResultApproval
   235  // checks:
   236  // - verification node identity
   237  // - attestation signature
   238  // - signature of verification node
   239  // - chunk index sanity check
   240  // - block ID sanity check
   241  // Returns:
   242  // - engine.InvalidInputError - result approval is invalid
   243  // - exception in case of any other error, usually this is not expected
   244  // - nil on successful check
   245  func (ac *VerifyingAssignmentCollector) validateApproval(approval *flow.ResultApproval) error {
   246  	// check that approval is for the expected result to reject incompatible inputs
   247  	if approval.Body.ExecutionResultID != ac.ResultID() {
   248  		return fmt.Errorf("AssignmentCollector processes only approvals for result (%x) but got one for (%x)", ac.ResultID(), approval.Body.ExecutionResultID)
   249  	}
   250  
   251  	// approval has to refer same block as execution result
   252  	if approval.Body.BlockID != ac.BlockID() {
   253  		return engine.NewInvalidInputErrorf("result approval for invalid block, expected (%x) vs (%x)",
   254  			ac.BlockID(), approval.Body.BlockID)
   255  	}
   256  
   257  	chunkIndex := approval.Body.ChunkIndex
   258  	if chunkIndex >= uint64(ac.result.Chunks.Len()) {
   259  		return engine.NewInvalidInputErrorf("chunk index out of range: %v", chunkIndex)
   260  	}
   261  
   262  	identity, found := ac.authorizedApprovers[approval.Body.ApproverID]
   263  	if !found {
   264  		return engine.NewInvalidInputErrorf("approval not from authorized verifier")
   265  	}
   266  
   267  	err := ac.verifyAttestationSignature(&approval.Body, identity)
   268  	if err != nil {
   269  		return fmt.Errorf("validating attestation signature failed: %w", err)
   270  	}
   271  
   272  	err = ac.verifySignature(approval, identity)
   273  	if err != nil {
   274  		return fmt.Errorf("validating approval signature failed: %w", err)
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  // ProcessApproval ingests Result Approvals and triggers sealing of execution result
   281  // when sufficient approvals have arrived.
   282  // Error Returns:
   283  //   - nil in case of success (outdated approvals might be silently discarded)
   284  //   - engine.InvalidInputError if the result approval is invalid
   285  //   - any other errors might be symptoms of bugs or internal state corruption (fatal)
   286  func (ac *VerifyingAssignmentCollector) ProcessApproval(approval *flow.ResultApproval) error {
   287  	ac.log.Debug().
   288  		Str("result_id", approval.Body.ExecutionResultID.String()).
   289  		Str("verifier_id", approval.Body.ApproverID.String()).
   290  		Msg("processing result approval")
   291  
   292  	// we have this approval cached already, no need to process it again
   293  	// here we need to use PartialID to have a hash over Attestation + ApproverID
   294  	// there is no need to use hash over full approval since it contains extra information
   295  	// and we are only interested in approval body.
   296  	approvalCacheID := approval.Body.PartialID()
   297  	if cached := ac.verifiedApprovalsCache.Get(approvalCacheID); cached != nil {
   298  		return nil
   299  	}
   300  
   301  	err := ac.validateApproval(approval)
   302  	if err != nil {
   303  		return fmt.Errorf("could not validate approval: %w", err)
   304  	}
   305  
   306  	newlyAdded := ac.verifiedApprovalsCache.Put(approvalCacheID, approval)
   307  	if !newlyAdded {
   308  		return nil
   309  	}
   310  
   311  	for _, collector := range ac.allCollectors() {
   312  		// approvals are verified already and shouldn't yield any errors
   313  		err := collector.ProcessApproval(approval)
   314  		if err != nil {
   315  			return fmt.Errorf("could not process approval: %w", err)
   316  		}
   317  	}
   318  
   319  	return nil
   320  }
   321  
   322  // RequestMissingApprovals traverses all collectors and requests missing approval
   323  // for every chunk that didn't get enough approvals from verifiers.
   324  // Returns number of requests made and error in case something goes wrong.
   325  func (ac *VerifyingAssignmentCollector) RequestMissingApprovals(observation consensus.SealingObservation, maxHeightForRequesting uint64) (uint, error) {
   326  	overallRequestCount := uint(0) // number of approval requests for all different assignments for this result
   327  	for _, collector := range ac.allCollectors() {
   328  		if collector.IncorporatedBlock().Height > maxHeightForRequesting {
   329  			continue
   330  		}
   331  
   332  		missingChunks := collector.CollectMissingVerifiers()
   333  		observation.ApprovalsMissing(collector.IncorporatedResult(), missingChunks)
   334  		requestCount := uint(0)
   335  		for chunkIndex, verifiers := range missingChunks {
   336  			// Retrieve information about requests made for this chunk. Skip
   337  			// requesting if the blackout period hasn't expired. Otherwise,
   338  			// update request count and reset blackout period.
   339  			requestTrackerItem, updated, err := ac.requestTracker.TryUpdate(ac.result, collector.IncorporatedBlockID(), chunkIndex)
   340  			if err != nil {
   341  				// it could happen that other gorotuine will prune request tracker because of sealing progress
   342  				// in this case we should just stop requesting approvals as block was already sealed
   343  				if mempool.IsBelowPrunedThresholdError(err) {
   344  					return 0, nil
   345  				}
   346  				return 0, err
   347  			}
   348  			if !updated {
   349  				continue
   350  			}
   351  
   352  			// for monitoring/debugging purposes, log requests if we start
   353  			// making more than 10
   354  			if requestTrackerItem.Requests >= 10 {
   355  				log.Debug().Msgf("requesting approvals for result %v, incorporatedBlockID %v chunk %d: %d requests",
   356  					ac.ResultID(),
   357  					collector.IncorporatedBlockID(),
   358  					chunkIndex,
   359  					requestTrackerItem.Requests,
   360  				)
   361  			}
   362  
   363  			nonce, err := rand.Uint64()
   364  			if err != nil {
   365  				return 0, fmt.Errorf("nonce generation failed during request missing approvals: %w", err)
   366  			}
   367  
   368  			// prepare the request
   369  			req := &messages.ApprovalRequest{
   370  				Nonce:      nonce,
   371  				ResultID:   ac.ResultID(),
   372  				ChunkIndex: chunkIndex,
   373  			}
   374  
   375  			requestCount++
   376  			err = ac.approvalConduit.Publish(req, verifiers...)
   377  			if err != nil {
   378  				log.Error().Err(err).
   379  					Msgf("could not publish approval request for chunk %d", chunkIndex)
   380  			}
   381  		}
   382  
   383  		observation.ApprovalsRequested(collector.IncorporatedResult(), requestCount)
   384  		overallRequestCount += requestCount
   385  	}
   386  
   387  	return overallRequestCount, nil
   388  }
   389  
   390  // authorizedVerifiersAtBlock pre-select all authorized Verifiers at the block that incorporates the result.
   391  // The method returns the set of all node IDs that:
   392  //   - are authorized members of the network at the given block and
   393  //   - have the Verification role and
   394  //   - have _positive_ weight and
   395  //   - are not ejected
   396  func authorizedVerifiersAtBlock(state protocol.State, blockID flow.Identifier) (map[flow.Identifier]*flow.Identity, error) {
   397  	authorizedVerifierList, err := state.AtBlockID(blockID).Identities(
   398  		filter.And(
   399  			filter.HasRole[flow.Identity](flow.RoleVerification),
   400  			filter.HasInitialWeight[flow.Identity](true),
   401  			filter.IsValidCurrentEpochParticipant,
   402  		))
   403  	if err != nil {
   404  		return nil, fmt.Errorf("failed to retrieve Identities for block %v: %w", blockID, err)
   405  	}
   406  	if len(authorizedVerifierList) == 0 {
   407  		return nil, fmt.Errorf("no authorized verifiers found for block %v", blockID)
   408  	}
   409  
   410  	return authorizedVerifierList.Lookup(), nil
   411  }