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 }