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