github.com/koko1123/flow-go-1@v0.29.6/state/fork/traversal.go (about)

     1  package fork
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/koko1123/flow-go-1/model/flow"
     7  	"github.com/koko1123/flow-go-1/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  		block, err = headers.ByBlockID(block.ParentID)
   111  		if err != nil {
   112  			return nil, fmt.Errorf("failed to revtrieve block header %x: %w", block.ParentID, err)
   113  		}
   114  	}
   115  }