github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/validation/receipt_validator.go (about)

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