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 }