github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/consensus/execution_tree.go (about)

     1  package consensus
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/onflow/flow-go/model/flow"
     8  	"github.com/onflow/flow-go/module/forest"
     9  	"github.com/onflow/flow-go/module/mempool"
    10  )
    11  
    12  // ExecutionTree is a mempool holding receipts, which is aware of the tree structure
    13  // formed by the results. The mempool supports pruning by height: only results
    14  // descending from the latest sealed and finalized result are relevant. Hence, we
    15  // can prune all results for blocks _below_ the latest block with a finalized seal.
    16  // Results of sufficient height for forks that conflict with the finalized fork are
    17  // retained. However, such orphaned forks do not grow anymore and their results
    18  // will be progressively flushed out with increasing sealed-finalized height.
    19  //
    20  // Safe for concurrent access. Internally, the mempool utilizes the LevelledForrest.
    21  // For an in-depth discussion of the core algorithm, see ./Fork-Aware_Mempools.md
    22  type ExecutionTree struct {
    23  	sync.RWMutex
    24  	forest forest.LevelledForest
    25  	size   uint
    26  }
    27  
    28  // NewExecutionTree instantiates a ExecutionTree
    29  func NewExecutionTree() *ExecutionTree {
    30  	return &ExecutionTree{
    31  		RWMutex: sync.RWMutex{},
    32  		forest:  *forest.NewLevelledForest(0),
    33  		size:    0,
    34  	}
    35  }
    36  
    37  // AddResult adds an Execution Result to the Execution Tree (without any receipts), in
    38  // case the result is not already stored in the tree.
    39  // This is useful for crash recovery:
    40  // After recovering from a crash, the mempools are wiped and the sealed results will not
    41  // be stored in the Execution Tree anymore. Adding the result to the tree allows to create
    42  // a vertex in the tree without attaching any Execution Receipts to it.
    43  func (et *ExecutionTree) AddResult(result *flow.ExecutionResult, block *flow.Header) error {
    44  	et.Lock()
    45  	defer et.Unlock()
    46  
    47  	// drop receipts for block heights lower than the lowest height.
    48  	if block.Height < et.forest.LowestLevel {
    49  		return nil
    50  	}
    51  
    52  	// sanity check: initial result should be for block
    53  	if block.ID() != result.BlockID {
    54  		return fmt.Errorf("receipt is for different block")
    55  	}
    56  
    57  	_, err := et.getEquivalenceClass(result, block)
    58  	if err != nil {
    59  		return fmt.Errorf("failed to get equivalence class for result (%x): %w", result.ID(), err)
    60  	}
    61  	return nil
    62  }
    63  
    64  // getEquivalenceClass retrieves the Equivalence class for the given result
    65  // or creates a new one and stores it into the levelled forest
    66  func (et *ExecutionTree) getEquivalenceClass(result *flow.ExecutionResult, block *flow.Header) (*ReceiptsOfSameResult, error) {
    67  	vertex, found := et.forest.GetVertex(result.ID())
    68  	var receiptsForResult *ReceiptsOfSameResult
    69  	if !found {
    70  		var err error
    71  		receiptsForResult, err = NewReceiptsOfSameResult(result, block)
    72  		if err != nil {
    73  			return nil, fmt.Errorf("constructing equivalence class for receipt failed: %w", err)
    74  		}
    75  		err = et.forest.VerifyVertex(receiptsForResult)
    76  		if err != nil {
    77  			return nil, fmt.Errorf("failed to store receipt's equivalence class: %w", err)
    78  		}
    79  		et.forest.AddVertex(receiptsForResult)
    80  		// this Receipt Equivalence class is empty (no receipts); hence we don't need to adjust the mempool size
    81  		return receiptsForResult, nil
    82  	}
    83  
    84  	return vertex.(*ReceiptsOfSameResult), nil
    85  }
    86  
    87  func (et *ExecutionTree) HasReceipt(receipt *flow.ExecutionReceipt) bool {
    88  	resultID := receipt.ExecutionResult.ID()
    89  	receiptID := receipt.ID()
    90  
    91  	et.RLock()
    92  	defer et.RUnlock()
    93  
    94  	vertex, found := et.forest.GetVertex(resultID)
    95  	if !found {
    96  		return false
    97  	}
    98  	return vertex.(*ReceiptsOfSameResult).Has(receiptID)
    99  }
   100  
   101  // AddReceipt adds the given execution receipt to the memory pool. Requires
   102  // height of the block the receipt is for. We enforce data consistency on
   103  // an API level by using the block header as input.
   104  func (et *ExecutionTree) AddReceipt(receipt *flow.ExecutionReceipt, block *flow.Header) (bool, error) {
   105  	et.Lock()
   106  	defer et.Unlock()
   107  
   108  	// drop receipts for block heights lower than the lowest height.
   109  	if block.Height < et.forest.LowestLevel {
   110  		return false, nil
   111  	}
   112  
   113  	// sanity check: initial result should be for block
   114  	if block.ID() != receipt.ExecutionResult.BlockID {
   115  		return false, fmt.Errorf("receipt is for different block")
   116  	}
   117  
   118  	receiptsForResult, err := et.getEquivalenceClass(&receipt.ExecutionResult, block)
   119  	if err != nil {
   120  		return false, fmt.Errorf("failed to get equivalence class for result (%x): %w", receipt.ExecutionResult.ID(), err)
   121  	}
   122  
   123  	added, err := receiptsForResult.AddReceipt(receipt)
   124  	if err != nil {
   125  		return false, fmt.Errorf("failed to add receipt to its equivalence class: %w", err)
   126  	}
   127  	et.size += added
   128  	return added > 0, nil
   129  }
   130  
   131  // ReachableReceipts returns a slice of ExecutionReceipt, whose result
   132  // is computationally reachable from resultID. Context:
   133  //   - Conceptually, the Execution results form a tree, which we refer to as
   134  //     Execution Tree. A fork in the execution can be due to a fork in the main
   135  //     chain. Furthermore, the execution forks if ENs disagree about the result
   136  //     for the same block.
   137  //   - As the ID of an execution result contains the BlockID, which the result
   138  //     for, all Execution Results with the same ID necessarily are for the same
   139  //     block. All Execution Receipts committing to the same result from an
   140  //     equivalence class and can be represented as one vertex in the Execution
   141  //     Tree.
   142  //   - An execution result r1 points (field ExecutionResult.ParentResultID) to
   143  //     its parent result r0 , whose end state was used as the starting state
   144  //     to compute r1. Formally, we have an edge r0 -> r1 in the Execution Tree,
   145  //     if a result r1 is stored in the mempool, whose ParentResultID points to
   146  //     r0.
   147  //
   148  // ReachableReceipts traverses the Execution Tree from the provided resultID.
   149  // Execution Receipts are traversed in a parent-first manner, meaning that
   150  // a receipt committing to the parent result is traversed first _before_
   151  // the receipt committing to the derived result.
   152  // The algorithm only traverses to results, for which there exists a
   153  // sequence of interim result in the mempool without any gaps.
   154  //
   155  // Error returns:
   156  // * UnknownExecutionResultError (sentinel) if resultID is unknown
   157  // * all other error are unexpected and potential indicators of corrupted internal state
   158  func (et *ExecutionTree) ReachableReceipts(resultID flow.Identifier, blockFilter mempool.BlockFilter, receiptFilter mempool.ReceiptFilter) ([]*flow.ExecutionReceipt, error) {
   159  	et.RLock()
   160  	defer et.RUnlock()
   161  
   162  	vertex, found := et.forest.GetVertex(resultID)
   163  	if !found {
   164  		return nil, mempool.NewUnknownExecutionResultErrorf("unknown result id %x", resultID)
   165  	}
   166  
   167  	receipts := make([]*flow.ExecutionReceipt, 0, 10) // we expect just below 10 execution Receipts per call
   168  	receipts = et.reachableReceipts(vertex, blockFilter, receiptFilter, receipts)
   169  
   170  	return receipts, nil
   171  }
   172  
   173  // reachableReceipts implements a depth-first search over the Execution Tree.
   174  // Entire sub-trees are skipped from search, if their root result is for a block which do _not_ pass the blockFilter
   175  // For each result (vertex in the Execution Tree), which the tree search visits, the known receipts are inspected.
   176  // Receipts that pass the receiptFilter are appended to `receipts` in the order they are encountered during the
   177  // tree search. the resulting slice is returned.
   178  func (et *ExecutionTree) reachableReceipts(vertex forest.Vertex, blockFilter mempool.BlockFilter, receiptFilter mempool.ReceiptFilter, receipts []*flow.ExecutionReceipt) []*flow.ExecutionReceipt {
   179  	receiptsForResult := vertex.(*ReceiptsOfSameResult)
   180  	if !blockFilter(receiptsForResult.blockHeader) {
   181  		return receipts
   182  	}
   183  
   184  	// add all Execution Receipts for result to `receipts` provided they pass the receiptFilter
   185  	for _, recMeta := range receiptsForResult.receipts {
   186  		receipt := flow.ExecutionReceiptFromMeta(*recMeta, *receiptsForResult.result)
   187  		if !receiptFilter(receipt) {
   188  			continue
   189  		}
   190  		receipts = append(receipts, receipt)
   191  	}
   192  
   193  	// travers down the tree in a deep-first-search manner
   194  	children := et.forest.GetChildren(vertex.VertexID())
   195  	for children.HasNext() {
   196  		child := children.NextVertex()
   197  		receipts = et.reachableReceipts(child, blockFilter, receiptFilter, receipts)
   198  	}
   199  	return receipts
   200  }
   201  
   202  // PruneUpToHeight prunes all results for all blocks with height up to but
   203  // NOT INCLUDING `newLowestHeight`. Errors if newLowestHeight is lower than
   204  // the previous value (as we cannot recover previously pruned results).
   205  func (et *ExecutionTree) PruneUpToHeight(limit uint64) error {
   206  	et.Lock()
   207  	defer et.Unlock()
   208  
   209  	// count how many receipts are stored in the Execution Tree that will be removed
   210  	numberReceiptsRemoved := uint(0)
   211  	if et.size > 0 {
   212  		for l := et.forest.LowestLevel; l < limit; l++ {
   213  			iterator := et.forest.GetVerticesAtLevel(l)
   214  			for iterator.HasNext() {
   215  				vertex := iterator.NextVertex()
   216  				numberReceiptsRemoved += vertex.(*ReceiptsOfSameResult).Size()
   217  			}
   218  		}
   219  	}
   220  
   221  	// remove vertices and adjust size
   222  	err := et.forest.PruneUpToLevel(limit)
   223  	if err != nil {
   224  		return fmt.Errorf("pruning Levelled Forest up to height (aka level) %d failed: %w", limit, err)
   225  	}
   226  	et.size -= numberReceiptsRemoved
   227  
   228  	return nil
   229  }
   230  
   231  // Size returns the number of receipts stored in the mempool
   232  func (et *ExecutionTree) Size() uint {
   233  	et.RLock()
   234  	defer et.RUnlock()
   235  	return et.size
   236  }
   237  
   238  // LowestHeight returns the lowest height, where results are still stored in the mempool.
   239  func (et *ExecutionTree) LowestHeight() uint64 {
   240  	et.RLock()
   241  	defer et.RUnlock()
   242  	return et.forest.LowestLevel
   243  }