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 }