github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/fork/traversal.go (about) 1 package fork 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/model/flow" 7 "github.com/onflow/flow-go/storage" 8 ) 9 10 // functor that will be called on each block header when traversing blocks. 11 type onVisitBlock = func(header *flow.Header) error 12 13 // TraverseBackward traverses the given fork (specified by block ID `forkHead`) 14 // in the order of decreasing height. The `terminal` defines when the traversal 15 // stops. The `visitor` callback is called for each block in this segment. 16 func TraverseBackward(headers storage.Headers, forkHead flow.Identifier, visitor onVisitBlock, terminal Terminal) error { 17 startBlock, err := headers.ByBlockID(forkHead) 18 if err != nil { 19 return fmt.Errorf("could not retrieve fork head with id %x: %w", forkHead, err) 20 } 21 lowestHeightToVisit, err := terminal.LowestHeightToVisit(headers) 22 if err != nil { 23 return fmt.Errorf("error determinging terminal height: %w", err) 24 } 25 if startBlock.Height < lowestHeightToVisit { 26 return nil 27 } 28 29 lowestBlock, err := unsafeTraverse(headers, startBlock, visitor, lowestHeightToVisit) 30 if err != nil { 31 return fmt.Errorf("traversing fork aborted: %w", err) 32 } 33 34 err = terminal.ConfirmTerminalReached(headers, lowestBlock) 35 if err != nil { 36 return fmt.Errorf("lowest visited block at height %d failed sanity check: %w", lowestHeightToVisit, err) 37 } 38 39 return nil 40 } 41 42 // TraverseForward traverses the given fork (specified by block ID `forkHead`) 43 // in the order of increasing height. The `terminal` defines when the traversal 44 // begins. The `visitor` callback is called for each block in this segment. 45 func TraverseForward(headers storage.Headers, 46 forkHead flow.Identifier, 47 visitor onVisitBlock, 48 terminal Terminal, 49 ) error { 50 startBlock, err := headers.ByBlockID(forkHead) 51 if err != nil { 52 return fmt.Errorf("could not retrieve fork head with id %x: %w", forkHead, err) 53 } 54 lowestHeightToVisit, err := terminal.LowestHeightToVisit(headers) 55 if err != nil { 56 return fmt.Errorf("error determinging terminal height: %w", err) 57 } 58 if startBlock.Height < lowestHeightToVisit { 59 return nil 60 } 61 62 blocks := make([]*flow.Header, 0, startBlock.Height-lowestHeightToVisit+1) 63 lowestBlock, err := unsafeTraverse(headers, 64 startBlock, 65 func(header *flow.Header) error { 66 blocks = append(blocks, header) 67 return nil 68 }, 69 lowestHeightToVisit, 70 ) 71 if err != nil { 72 return fmt.Errorf("traversing fork aborted: %w", err) 73 } 74 75 err = terminal.ConfirmTerminalReached(headers, lowestBlock) 76 if err != nil { 77 return fmt.Errorf("lowest visited block at height %d failed sanity check: %w", lowestHeightToVisit, err) 78 } 79 80 for i := len(blocks) - 1; i >= 0; i-- { 81 block := blocks[i] 82 err = visitor(block) 83 if err != nil { 84 return fmt.Errorf("visitor errored on block %x at height %d: %w", block.ID(), block.Height, err) 85 } 86 } 87 88 return nil 89 } 90 91 // unsafeTraverse implements the fork traversal in the order of decreasing height. 92 // It is unsafe because: 93 // it assumes the stop condition for the for-loop has been pre-checked, 94 // which is `startBlock.Height < lowestHeightToVisit`. With the pre-check, 95 // it is guaranteed the for loop will stop eventually. 96 // In other words, this unsafe function should only be called after the pre-check. 97 // The `TraverseBackward` and `TraverseForward` are "safe" functions since they 98 // do the pre-check before calling the `unsafeTraverse` 99 func unsafeTraverse(headers storage.Headers, block *flow.Header, visitor onVisitBlock, lowestHeightToVisit uint64) (*flow.Header, error) { 100 for { 101 err := visitor(block) 102 if err != nil { 103 return nil, fmt.Errorf("visitor errored on block %x at height %d: %w", block.ID(), block.Height, err) 104 } 105 106 if block.Height == lowestHeightToVisit { 107 return block, nil 108 } 109 110 parent, err := headers.ByBlockID(block.ParentID) 111 if err != nil { 112 return nil, fmt.Errorf("failed to retrieve block header (id=%x height=%d): %w", block.ParentID, block.Height-1, err) 113 } 114 115 block = parent 116 } 117 }