github.com/onflow/flow-go@v0.33.17/module/validation/receipt_validator.go (about)

     1  package validation
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/onflow/flow-go/crypto/hash"
     8  	"github.com/onflow/flow-go/engine"
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/module"
    11  	"github.com/onflow/flow-go/module/signature"
    12  	"github.com/onflow/flow-go/state"
    13  	"github.com/onflow/flow-go/state/fork"
    14  	"github.com/onflow/flow-go/state/protocol"
    15  	"github.com/onflow/flow-go/storage"
    16  )
    17  
    18  // receiptValidator holds all needed context for checking
    19  // receipt validity against the current protocol state.
    20  type receiptValidator struct {
    21  	headers         storage.Headers
    22  	seals           storage.Seals
    23  	state           protocol.State
    24  	index           storage.Index
    25  	results         storage.ExecutionResults
    26  	signatureHasher hash.Hasher
    27  }
    28  
    29  var _ module.ReceiptValidator = (*receiptValidator)(nil)
    30  
    31  func NewReceiptValidator(state protocol.State,
    32  	headers storage.Headers,
    33  	index storage.Index,
    34  	results storage.ExecutionResults,
    35  	seals storage.Seals,
    36  ) module.ReceiptValidator {
    37  	rv := &receiptValidator{
    38  		state:           state,
    39  		headers:         headers,
    40  		index:           index,
    41  		results:         results,
    42  		signatureHasher: signature.NewBLSHasher(signature.ExecutionReceiptTag),
    43  		seals:           seals,
    44  	}
    45  	return rv
    46  }
    47  
    48  func (v *receiptValidator) verifySignature(receipt *flow.ExecutionReceiptMeta, nodeIdentity *flow.Identity) error {
    49  	id := receipt.ID()
    50  	valid, err := nodeIdentity.StakingPubKey.Verify(receipt.ExecutorSignature, id[:], v.signatureHasher)
    51  	if err != nil {
    52  		return fmt.Errorf("failed to verify signature: %w", err)
    53  	}
    54  
    55  	if !valid {
    56  		return engine.NewInvalidInputErrorf("invalid signature for (%x)", nodeIdentity.NodeID)
    57  	}
    58  
    59  	return nil
    60  }
    61  
    62  func (v *receiptValidator) verifyChunksFormat(result *flow.ExecutionResult) error {
    63  	for index, chunk := range result.Chunks.Items() {
    64  		if uint(index) != chunk.CollectionIndex {
    65  			return engine.NewInvalidInputErrorf("invalid CollectionIndex, expected %d got %d", index, chunk.CollectionIndex)
    66  		}
    67  
    68  		if chunk.BlockID != result.BlockID {
    69  			return engine.NewInvalidInputErrorf("invalid blockID, expected %v got %v", result.BlockID, chunk.BlockID)
    70  		}
    71  	}
    72  
    73  	// we create one chunk per collection, plus the
    74  	// system chunk. so we can check if the chunk number matches with the
    75  	// number of guarantees plus one; this will ensure the execution receipt
    76  	// cannot lie about having less chunks and having the remaining ones
    77  	// approved
    78  	requiredChunks := 1 // system chunk: must exist for block's ExecutionResult, even if block payload itself is empty
    79  
    80  	index, err := v.index.ByBlockID(result.BlockID)
    81  	if err != nil {
    82  		// the mutator will always create payload index for a valid block
    83  		return fmt.Errorf("could not find payload index for executed block %v: %w", result.BlockID, err)
    84  	}
    85  
    86  	requiredChunks += len(index.CollectionIDs)
    87  
    88  	if result.Chunks.Len() != requiredChunks {
    89  		return engine.NewInvalidInputErrorf("invalid number of chunks, expected %d got %d",
    90  			requiredChunks, result.Chunks.Len())
    91  	}
    92  
    93  	return nil
    94  }
    95  
    96  func (v *receiptValidator) fetchResult(resultID flow.Identifier) (*flow.ExecutionResult, error) {
    97  	prevResult, err := v.results.ByID(resultID)
    98  	if err != nil {
    99  		if errors.Is(err, storage.ErrNotFound) {
   100  			return nil, engine.NewUnverifiableInputError("cannot retrieve result: %v", resultID)
   101  		}
   102  		return nil, err
   103  	}
   104  	return prevResult, nil
   105  }
   106  
   107  // subgraphCheck enforces that result forms a valid sub-graph:
   108  // Let R1 be a result that references block A, and R2 be R1's parent result.
   109  // The execution results form a valid subgraph if and only if R2 references
   110  // A's parent.
   111  func (v *receiptValidator) subgraphCheck(result *flow.ExecutionResult, prevResult *flow.ExecutionResult) error {
   112  	block, err := v.state.AtBlockID(result.BlockID).Head()
   113  	if err != nil {
   114  		if errors.Is(err, state.ErrUnknownSnapshotReference) {
   115  			return engine.NewInvalidInputErrorf("no block found %v %w", result.BlockID, err)
   116  		}
   117  		return err
   118  	}
   119  
   120  	// validating the PreviousResultID field
   121  	// ExecutionResult_X.PreviousResult.BlockID must equal to Block_X.ParentBlockID
   122  	// for instance: given the following chain
   123  	// A <- B <- C (ER_A) <- D
   124  	// a result ER_C with `ID(ER_A)` as its ER_C.Result.PreviousResultID
   125  	// would be invalid, because `ER_C.Result.PreviousResultID` must be ID(ER_B)
   126  	if prevResult.BlockID != block.ParentID {
   127  		return engine.NewInvalidInputErrorf("invalid block for previous result %v", prevResult.BlockID)
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // resultChainCheck enforces that the end state of the parent result
   134  // matches the current result's start state
   135  func (v *receiptValidator) resultChainCheck(result *flow.ExecutionResult, prevResult *flow.ExecutionResult) error {
   136  	finalState, err := prevResult.FinalStateCommitment()
   137  	if err != nil {
   138  		return fmt.Errorf("missing final state commitment in parent result %v", prevResult.ID())
   139  	}
   140  	initialState, err := result.InitialStateCommit()
   141  	if err != nil {
   142  		return engine.NewInvalidInputErrorf("missing initial state commitment in execution result %v", result.ID())
   143  	}
   144  	if initialState != finalState {
   145  		return engine.NewInvalidInputErrorf("execution results do not form chain: expecting init state %x, but got %x",
   146  			finalState, initialState)
   147  	}
   148  	return nil
   149  }
   150  
   151  // Validate verifies that the ExecutionReceipt satisfies
   152  // the following conditions:
   153  //   - is from Execution node with positive weight
   154  //   - has valid signature
   155  //   - chunks are in correct format
   156  //   - execution result has a valid parent and satisfies the subgraph check
   157  //
   158  // Returns nil if all checks passed successfully.
   159  // Expected errors during normal operations:
   160  //   - engine.InvalidInputError
   161  //     if receipt violates protocol condition
   162  //   - engine.UnverifiableInputError
   163  //     if receipt's parent result is unknown
   164  func (v *receiptValidator) Validate(receipt *flow.ExecutionReceipt) error {
   165  	// TODO: this can be optimized by checking if result was already stored and validated.
   166  	// This needs to be addressed later since many tests depend on this behavior.
   167  	prevResult, err := v.fetchResult(receipt.ExecutionResult.PreviousResultID)
   168  	if err != nil {
   169  		return fmt.Errorf("error fetching parent result of receipt %v: %w", receipt.ID(), err)
   170  	}
   171  
   172  	// first validate result to avoid signature check in in `validateReceipt` in case result is invalid.
   173  	err = v.validateResult(&receipt.ExecutionResult, prevResult)
   174  	if err != nil {
   175  		return fmt.Errorf("could not validate single result %v at index: %w", receipt.ExecutionResult.ID(), err)
   176  	}
   177  
   178  	err = v.validateReceipt(receipt.Meta(), receipt.ExecutionResult.BlockID)
   179  	if err != nil {
   180  		// It's very important that we fail the whole validation if one of the receipts is invalid.
   181  		// It allows us to make assumptions as stated in previous comment.
   182  		return fmt.Errorf("could not validate single receipt %v: %w", receipt.ID(), err)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  // ValidatePayload verifies the ExecutionReceipts and ExecutionResults
   189  // in the payload for compliance with the protocol:
   190  // Receipts:
   191  //   - are from Execution node with positive weight
   192  //   - have valid signature
   193  //   - chunks are in correct format
   194  //   - no duplicates in fork
   195  //
   196  // Results:
   197  //   - have valid parents and satisfy the subgraph check
   198  //   - extend the execution tree, where the tree root is the latest
   199  //     finalized block and only results from this fork are included
   200  //   - no duplicates in fork
   201  //
   202  // Expected errors during normal operations:
   203  //   - engine.InvalidInputError
   204  //     if some receipts in the candidate block violate protocol condition
   205  //   - engine.UnverifiableInputError
   206  //     if for some of the receipts, their respective parent result is unknown
   207  func (v *receiptValidator) ValidatePayload(candidate *flow.Block) error {
   208  	header := candidate.Header
   209  	payload := candidate.Payload
   210  
   211  	// return if nothing to validate
   212  	if len(payload.Receipts) == 0 && len(payload.Results) == 0 {
   213  		return nil
   214  	}
   215  
   216  	// Get the latest sealed result on this fork and the corresponding block,
   217  	// whose result is sealed. This block is not necessarily finalized.
   218  	lastSeal, err := v.seals.HighestInFork(header.ParentID)
   219  	if err != nil {
   220  		return fmt.Errorf("could not retrieve latest seal for fork with head %x: %w", header.ParentID, err)
   221  	}
   222  	latestSealedResult, err := v.results.ByID(lastSeal.ResultID)
   223  	if err != nil {
   224  		return fmt.Errorf("could not retrieve latest sealed result %x: %w", lastSeal.ResultID, err)
   225  	}
   226  
   227  	// forkBlocks is the set of all _unsealed_ blocks on the fork. We
   228  	// use it to identify receipts that are for blocks not in the fork.
   229  	forkBlocks := make(map[flow.Identifier]struct{})
   230  
   231  	// Sub-Set of the execution tree: only contains `ExecutionResult`s that descent from latestSealedResult.
   232  	// Used for detecting duplicates and results with invalid parent results.
   233  	executionTree := make(map[flow.Identifier]*flow.ExecutionResult)
   234  	executionTree[lastSeal.ResultID] = latestSealedResult
   235  
   236  	// Set of previously included receipts. Used for detecting duplicates.
   237  	forkReceipts := make(map[flow.Identifier]struct{})
   238  
   239  	// Start from the lowest unsealed block and walk the chain upwards until we
   240  	// hit the candidate's parent. For each visited block track:
   241  	bookKeeper := func(block *flow.Header) error {
   242  		blockID := block.ID()
   243  		// track encountered blocks
   244  		forkBlocks[blockID] = struct{}{}
   245  
   246  		payloadIndex, err := v.index.ByBlockID(blockID)
   247  		if err != nil {
   248  			return fmt.Errorf("could not retrieve payload index: %w", err)
   249  		}
   250  
   251  		// track encountered receipts
   252  		for _, recID := range payloadIndex.ReceiptIDs {
   253  			forkReceipts[recID] = struct{}{}
   254  		}
   255  
   256  		// extend execution tree
   257  		for _, resultID := range payloadIndex.ResultIDs {
   258  			result, err := v.results.ByID(resultID)
   259  			if err != nil {
   260  				return fmt.Errorf("could not retrieve result %v: %w", resultID, err)
   261  			}
   262  			if _, ok := executionTree[result.PreviousResultID]; !ok {
   263  				// We only collect results that directly descend from the last sealed result.
   264  				// Because Results are listed in an order that satisfies the parent-first
   265  				// relationship, we can skip all results whose parents are unknown.
   266  				continue
   267  			}
   268  			executionTree[resultID] = result
   269  		}
   270  		return nil
   271  	}
   272  	err = fork.TraverseForward(v.headers, header.ParentID, bookKeeper, fork.ExcludingBlock(lastSeal.BlockID))
   273  	if err != nil {
   274  		return fmt.Errorf("internal error while traversing the ancestor fork of unsealed blocks: %w", err)
   275  	}
   276  
   277  	// tracks the number of receipts committing to each result.
   278  	// it's ok to only index receipts at this point, because we will perform
   279  	// all needed checks after we have validated all results.
   280  	receiptsByResult := payload.Receipts.GroupByResultID()
   281  
   282  	// validate all results that are incorporated into the payload. If one is malformed, the entire block is invalid.
   283  	for i, result := range payload.Results {
   284  		resultID := result.ID()
   285  
   286  		// Every included result must be accompanied by a receipt with a corresponding `ResultID`, in the same block.
   287  		// If a result is included without a corresponding receipt, it cannot be attributed to any executor.
   288  		receiptsForResult := uint(len(receiptsByResult.GetGroup(resultID)))
   289  		if receiptsForResult == 0 {
   290  			return engine.NewInvalidInputErrorf("no receipts for result %v at index %d", resultID, i)
   291  		}
   292  
   293  		// check for duplicated results
   294  		if _, isDuplicate := executionTree[resultID]; isDuplicate {
   295  			return engine.NewInvalidInputErrorf("duplicate result %v at index %d", resultID, i)
   296  		}
   297  
   298  		// any result must extend the execution tree with root latestSealedResult
   299  		prevResult, extendsTree := executionTree[result.PreviousResultID]
   300  		if !extendsTree {
   301  			return engine.NewInvalidInputErrorf("results %v at index %d does not extend execution tree", resultID, i)
   302  		}
   303  
   304  		// result must be for block on fork
   305  		if _, forBlockOnFork := forkBlocks[result.BlockID]; !forBlockOnFork {
   306  			return engine.NewInvalidInputErrorf("results %v at index %d is for block not on fork (%x)", resultID, i, result.BlockID)
   307  		}
   308  
   309  		// validate result
   310  		err = v.validateResult(result, prevResult)
   311  		if err != nil {
   312  			return fmt.Errorf("could not validate result %v at index %d: %w", resultID, i, err)
   313  		}
   314  		executionTree[resultID] = result
   315  	}
   316  
   317  	// check receipts:
   318  	// * no duplicates
   319  	// * must commit to a result in the execution tree with root latestSealedResult,
   320  	//   but not latestSealedResult
   321  	// It's very important that we fail the whole validation if one of the receipts is invalid.
   322  	delete(executionTree, lastSeal.ResultID)
   323  	for i, receipt := range payload.Receipts {
   324  		receiptID := receipt.ID()
   325  
   326  		// error if the result is not part of the execution tree with root latestSealedResult
   327  		result, isForLegitimateResult := executionTree[receipt.ResultID]
   328  		if !isForLegitimateResult {
   329  			return engine.NewInvalidInputErrorf("receipt %v at index %d commits to unexpected result", receiptID, i)
   330  		}
   331  
   332  		// error if the receipt is duplicated in the fork
   333  		if _, isDuplicate := forkReceipts[receiptID]; isDuplicate {
   334  			return engine.NewInvalidInputErrorf("duplicate receipt %v at index %d", receiptID, i)
   335  		}
   336  		forkReceipts[receiptID] = struct{}{}
   337  
   338  		err = v.validateReceipt(receipt, result.BlockID)
   339  		if err != nil {
   340  			return fmt.Errorf("receipt %v at index %d failed validation: %w", receiptID, i, err)
   341  		}
   342  	}
   343  
   344  	return nil
   345  }
   346  
   347  func (v *receiptValidator) validateResult(result *flow.ExecutionResult, prevResult *flow.ExecutionResult) error {
   348  	err := v.verifyChunksFormat(result)
   349  	if err != nil {
   350  		return fmt.Errorf("invalid chunks format for result %v: %w", result.ID(), err)
   351  	}
   352  
   353  	err = v.subgraphCheck(result, prevResult)
   354  	if err != nil {
   355  		return fmt.Errorf("invalid execution result: %w", err)
   356  	}
   357  
   358  	err = v.resultChainCheck(result, prevResult)
   359  	if err != nil {
   360  		return fmt.Errorf("invalid execution results chain: %w", err)
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  func (v *receiptValidator) validateReceipt(receipt *flow.ExecutionReceiptMeta, blockID flow.Identifier) error {
   367  	identity, err := identityForNode(v.state, blockID, receipt.ExecutorID)
   368  	if err != nil {
   369  		return fmt.Errorf(
   370  			"failed to get executor identity %v at block %v: %w",
   371  			receipt.ExecutorID,
   372  			blockID,
   373  			err)
   374  	}
   375  
   376  	err = ensureNodeHasWeightAndRole(identity, flow.RoleExecution)
   377  	if err != nil {
   378  		return fmt.Errorf("node is not authorized execution node: %w", err)
   379  	}
   380  
   381  	err = v.verifySignature(receipt, identity)
   382  	if err != nil {
   383  		return fmt.Errorf("invalid receipt signature: %w", err)
   384  	}
   385  
   386  	return nil
   387  }