github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/matching/core.go (about) 1 package matching 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math" 9 "time" 10 11 "github.com/rs/zerolog" 12 "go.opentelemetry.io/otel/attribute" 13 14 "github.com/onflow/flow-go/engine" 15 "github.com/onflow/flow-go/model/flow" 16 "github.com/onflow/flow-go/model/flow/filter" 17 "github.com/onflow/flow-go/module" 18 "github.com/onflow/flow-go/module/mempool" 19 "github.com/onflow/flow-go/module/metrics" 20 "github.com/onflow/flow-go/module/trace" 21 "github.com/onflow/flow-go/state/protocol" 22 "github.com/onflow/flow-go/storage" 23 "github.com/onflow/flow-go/utils/logging" 24 ) 25 26 // Config is a structure of values that configure behavior of matching engine 27 type Config struct { 28 SealingThreshold uint // threshold between sealed and finalized blocks 29 MaxResultsToRequest uint // maximum number of receipts to request 30 } 31 32 func DefaultConfig() Config { 33 return Config{ 34 SealingThreshold: 10, 35 MaxResultsToRequest: 20, 36 } 37 } 38 39 // Core represents the matching business logic, used to process receipts received from 40 // p2p network. Performs processing of pending receipts, storing of receipts and re-requesting 41 // missing execution receipts. 42 type Core struct { 43 log zerolog.Logger // used to log relevant actions with context 44 tracer module.Tracer // used to trace execution 45 metrics module.ConsensusMetrics // used to track consensus metrics 46 mempool module.MempoolMetrics // used to track mempool size 47 state protocol.State // used to access the protocol state 48 headersDB storage.Headers // used to check sealed headers 49 receiptsDB storage.ExecutionReceipts // to persist received execution receipts 50 receipts mempool.ExecutionTree // holds execution receipts; indexes them by height; can search all receipts derived from a given parent result 51 pendingReceipts mempool.PendingReceipts // buffer for receipts where an ancestor result is missing, so they can't be connected to the sealed results 52 seals mempool.IncorporatedResultSeals // holds candidate seals for incorporated results that have acquired sufficient approvals; candidate seals are constructed without consideration of the sealability of parent results 53 receiptValidator module.ReceiptValidator // used to validate receipts 54 receiptRequester module.Requester // used to request missing execution receipts by block ID 55 config Config // config for matching core 56 } 57 58 func NewCore( 59 log zerolog.Logger, 60 tracer module.Tracer, 61 metrics module.ConsensusMetrics, 62 mempool module.MempoolMetrics, 63 state protocol.State, 64 headersDB storage.Headers, 65 receiptsDB storage.ExecutionReceipts, 66 receipts mempool.ExecutionTree, 67 pendingReceipts mempool.PendingReceipts, 68 seals mempool.IncorporatedResultSeals, 69 receiptValidator module.ReceiptValidator, 70 receiptRequester module.Requester, 71 config Config, 72 ) *Core { 73 return &Core{ 74 log: log.With().Str("engine", "matching.Core").Logger(), 75 tracer: tracer, 76 metrics: metrics, 77 mempool: mempool, 78 state: state, 79 headersDB: headersDB, 80 receiptsDB: receiptsDB, 81 receipts: receipts, 82 pendingReceipts: pendingReceipts, 83 seals: seals, 84 receiptValidator: receiptValidator, 85 receiptRequester: receiptRequester, 86 config: config, 87 } 88 } 89 90 // ProcessReceipt processes a new execution receipt. 91 // Any error indicates an unexpected problem in the protocol logic. The node's 92 // internal state might be corrupted. Hence, returned errors should be treated as fatal. 93 func (c *Core) ProcessReceipt(receipt *flow.ExecutionReceipt) error { 94 // When receiving a receipt, we might not be able to verify it if its previous result 95 // is unknown. In this case, instead of dropping it, we store it in the pending receipts 96 // mempool, and process it later when its parent result has been received and processed. 97 // Therefore, if a receipt is processed, we will check if it is the previous results of 98 // some pending receipts and process them one after another. 99 receiptID := receipt.ID() 100 resultID := receipt.ExecutionResult.ID() 101 102 processed, err := c.processReceipt(receipt) 103 if err != nil { 104 marshalled, encErr := json.Marshal(receipt) 105 if encErr != nil { 106 marshalled = []byte("json_marshalling_failed") 107 } 108 c.log.Error().Err(err). 109 Hex("origin", logging.ID(receipt.ExecutorID)). 110 Hex("receipt_id", receiptID[:]). 111 Hex("result_id", resultID[:]). 112 Str("receipt", string(marshalled)). 113 Msg("internal error processing execution receipt") 114 115 return fmt.Errorf("internal error processing execution receipt %x: %w", receipt.ID(), err) 116 } 117 118 if !processed { 119 return nil 120 } 121 122 childReceipts := c.pendingReceipts.ByPreviousResultID(resultID) 123 c.pendingReceipts.Remove(receipt.ID()) 124 125 for _, childReceipt := range childReceipts { 126 // recursively processing the child receipts 127 err := c.ProcessReceipt(childReceipt) 128 if err != nil { 129 // we don't want to wrap the error with any info from its parent receipt, 130 // because the error has nothing to do with its parent receipt. 131 return err 132 } 133 } 134 135 return nil 136 } 137 138 // processReceipt checks validity of the given receipt and adds it to the node's validated information. 139 // Returns: 140 // - bool: true iff receipt is new (previously unknown), and its validity can be confirmed 141 // - error: any error indicates an unexpected problem in the protocol logic. The node's 142 // internal state might be corrupted. Hence, returned errors should be treated as fatal. 143 func (c *Core) processReceipt(receipt *flow.ExecutionReceipt) (bool, error) { 144 // setup logger to capture basic information about the receipt 145 log := c.log.With(). 146 Hex("receipt_id", logging.Entity(receipt)). 147 Hex("result_id", logging.Entity(receipt.ExecutionResult)). 148 Hex("execution_data_id", receipt.ExecutionResult.ExecutionDataID[:]). 149 Hex("previous_result", receipt.ExecutionResult.PreviousResultID[:]). 150 Hex("block_id", receipt.ExecutionResult.BlockID[:]). 151 Hex("executor_id", receipt.ExecutorID[:]). 152 Logger() 153 154 if c.receipts.HasReceipt(receipt) { 155 log.Debug().Msg("skipping processing of already known receipt") 156 return false, nil 157 } 158 159 startTime := time.Now() 160 defer func() { 161 c.metrics.OnReceiptProcessingDuration(time.Since(startTime)) 162 }() 163 164 receiptSpan, _ := c.tracer.StartBlockSpan(context.Background(), receipt.ExecutionResult.BlockID, trace.CONMatchProcessReceipt) 165 receiptSpan.SetAttributes( 166 attribute.String("result_id", receipt.ExecutionResult.ID().String()), 167 attribute.String("executor", receipt.ExecutorID.String()), 168 ) 169 defer receiptSpan.End() 170 171 initialState, finalState, err := getStartAndEndStates(receipt) 172 if err != nil { 173 if errors.Is(err, flow.ErrNoChunks) { 174 log.Error().Err(err).Msg("discarding malformed receipt") 175 return false, nil 176 } 177 return false, fmt.Errorf("internal problem retrieving start- and end-state commitment from receipt: %w", err) 178 } 179 log = log.With(). 180 Hex("initial_state", initialState[:]). 181 Hex("final_state", finalState[:]).Logger() 182 183 // if the receipt is for an unknown block, skip it. It will be re-requested 184 // later by `requestPending` function. 185 executedBlock, err := c.headersDB.ByBlockID(receipt.ExecutionResult.BlockID) 186 if err != nil { 187 log.Debug().Msg("discarding receipt for unknown block") 188 return false, nil 189 } 190 191 log = log.With(). 192 Uint64("block_view", executedBlock.View). 193 Uint64("block_height", executedBlock.Height). 194 Logger() 195 log.Info().Msg("execution receipt received") 196 197 // if Execution Receipt is for block whose height is lower or equal to already sealed height 198 // => drop Receipt 199 sealed, err := c.state.Sealed().Head() 200 if err != nil { 201 return false, fmt.Errorf("could not find sealed block: %w", err) 202 } 203 if executedBlock.Height <= sealed.Height { 204 log.Debug().Msg("discarding receipt for already sealed and finalized block height") 205 return false, nil 206 } 207 208 childSpan := c.tracer.StartSpanFromParent(receiptSpan, trace.CONMatchProcessReceiptVal) 209 err = c.receiptValidator.Validate(receipt) 210 childSpan.End() 211 212 if engine.IsUnverifiableInputError(err) { 213 // If previous result is missing, we can't validate this receipt. 214 // Although we will request its previous receipt(s), 215 // we don't want to drop it now, because when the missing previous arrive 216 // in a wrong order, they will still be dropped, and causing the catch up 217 // to be inefficient. 218 // Instead, we cache the receipt in case it arrives earlier than its 219 // previous receipt. 220 // For instance, given blocks A <- B <- C <- D <- E, if we receive their receipts 221 // in the order of [E,C,D,B,A], then: 222 // if we drop the missing previous receipts, then only A will be processed; 223 // if we cache the missing previous receipts, then all of them will be processed, because 224 // once A is processed, we will check if there is a child receipt pending, 225 // if yes, then process it. 226 c.pendingReceipts.Add(receipt) 227 log.Info().Msg("receipt is cached because its previous result is missing") 228 return false, nil 229 } 230 231 if err != nil { 232 if engine.IsInvalidInputError(err) { 233 log.Err(err).Msg("invalid execution receipt") 234 return false, nil 235 } 236 return false, fmt.Errorf("failed to validate execution receipt: %w", err) 237 } 238 239 added, err := c.storeReceipt(receipt, executedBlock) 240 if err != nil { 241 return false, fmt.Errorf("failed to store receipt: %w", err) 242 } 243 if added { 244 log.Info().Msg("execution result processed and stored") 245 } 246 247 return added, nil 248 } 249 250 // storeReceipt adds the receipt to the receipts mempool as well as to the persistent storage layer. 251 // Return values: 252 // - bool to indicate whether the receipt is stored. 253 // - exception in case something (unexpected) went wrong 254 func (c *Core) storeReceipt(receipt *flow.ExecutionReceipt, head *flow.Header) (bool, error) { 255 added, err := c.receipts.AddReceipt(receipt, head) 256 if err != nil { 257 return false, fmt.Errorf("adding receipt (%x) to mempool failed: %w", receipt.ID(), err) 258 } 259 if !added { 260 return false, nil 261 } 262 // TODO: we'd better wrap the `receipts` with the metrics method to avoid the metrics 263 // getting out of sync 264 c.mempool.MempoolEntries(metrics.ResourceReceipt, c.receipts.Size()) 265 266 // persist receipt in database. Even if the receipt is already in persistent storage, 267 // we still need to process it, as it is not in the mempool. This can happen if the 268 // mempool was wiped during a node crash. 269 err = c.receiptsDB.Store(receipt) // internally de-duplicates 270 if err != nil && !errors.Is(err, storage.ErrAlreadyExists) { 271 return false, fmt.Errorf("could not persist receipt: %w", err) 272 } 273 return true, nil 274 } 275 276 // requestPendingReceipts requests the execution receipts of unsealed finalized 277 // blocks. 278 // it returns the number of pending receipts requests being created, and 279 // the first finalized height at which there is no receipt for the block 280 func (c *Core) requestPendingReceipts() (int, uint64, error) { 281 finalSnapshot := c.state.Final() 282 final, err := finalSnapshot.Head() // last finalized block 283 if err != nil { 284 return 0, 0, fmt.Errorf("could not get finalized height: %w", err) 285 } 286 _, seal, err := finalSnapshot.SealedResult() // last finalized seal 287 if err != nil { 288 return 0, 0, fmt.Errorf("could not retrieve latest finalized seal: %w", err) 289 } 290 sealed, err := c.headersDB.ByBlockID(seal.BlockID) // last sealed block 291 if err != nil { 292 return 0, 0, fmt.Errorf("could not get sealed height: %w", err) 293 } 294 295 // only request if number of unsealed finalized blocks exceeds the threshold 296 if uint(final.Height-sealed.Height) < c.config.SealingThreshold { 297 return 0, 0, nil 298 } 299 300 // order the missing blocks by height from low to high such that when 301 // passing them to the missing block requester, they can be requested in the 302 // right order. The right order gives the priority to the execution result 303 // of lower height blocks to be requested first, since a gap in the sealing 304 // heights would stop the sealing. 305 missingBlocksOrderedByHeight := make([]flow.Identifier, 0, c.config.MaxResultsToRequest) 306 307 var firstMissingHeight uint64 = math.MaxUint64 308 // traverse each unsealed and finalized block with height from low to high, 309 // if the result is missing, then add the blockID to a missing block list in 310 // order to request them. 311 HEIGHT_LOOP: 312 for height := sealed.Height + 1; height <= final.Height; height++ { 313 // add at most <maxUnsealedResults> number of results 314 if len(missingBlocksOrderedByHeight) >= int(c.config.MaxResultsToRequest) { 315 break 316 } 317 318 // get the block header at this height (should not error as heights are finalized) 319 header, err := c.headersDB.ByHeight(height) 320 if err != nil { 321 return 0, 0, fmt.Errorf("could not get header (height=%d): %w", height, err) 322 } 323 blockID := header.ID() 324 325 receipts, err := c.receiptsDB.ByBlockID(blockID) 326 if err != nil && !errors.Is(err, storage.ErrNotFound) { 327 return 0, 0, fmt.Errorf("could not get receipts by block ID: %v, %w", blockID, err) 328 } 329 330 // We require at least 2 consistent receipts from different ENs to seal a block. If don't need to fetching receipts. 331 // CAUTION: This is a temporary shortcut incompatible with the mature BFT protocol! 332 // There might be multiple consistent receipts that commit to a wrong result. To guarantee 333 // sealing liveness, we need to fetch receipts from those ENs, whose receipts we don't have yet. 334 // TODO: update for full BFT 335 for _, receiptsForResult := range receipts.GroupByResultID() { 336 if receiptsForResult.GroupByExecutorID().NumberGroups() >= 2 { 337 continue HEIGHT_LOOP 338 } 339 } 340 341 missingBlocksOrderedByHeight = append(missingBlocksOrderedByHeight, blockID) 342 if height < firstMissingHeight { 343 firstMissingHeight = height 344 } 345 } 346 347 // request missing execution results, if sealed height is low enough 348 for _, blockID := range missingBlocksOrderedByHeight { 349 c.receiptRequester.Query(blockID, filter.Any) 350 } 351 352 return len(missingBlocksOrderedByHeight), firstMissingHeight, nil 353 } 354 355 func (c *Core) OnBlockFinalization() error { 356 startTime := time.Now() 357 358 // request execution receipts for unsealed finalized blocks 359 pendingReceiptRequests, firstMissingHeight, err := c.requestPendingReceipts() 360 if err != nil { 361 return fmt.Errorf("could not request pending block results: %w", err) 362 } 363 364 // Prune Execution Tree 365 lastSealed, err := c.state.Sealed().Head() 366 if err != nil { 367 return fmt.Errorf("could not retrieve last sealed block : %w", err) 368 } 369 err = c.receipts.PruneUpToHeight(lastSealed.Height) 370 if err != nil { 371 return fmt.Errorf("failed to prune execution tree up to latest sealed and finalized block %v, height: %v: %w", 372 lastSealed.ID(), lastSealed.Height, err) 373 } 374 375 err = c.pendingReceipts.PruneUpToHeight(lastSealed.Height) 376 if err != nil { 377 return fmt.Errorf("failed to prune pending receipts mempool up to latest sealed and finalized block %v, height: %v: %w", 378 lastSealed.ID(), lastSealed.Height, err) 379 } 380 381 c.log.Info(). 382 Uint64("first_height_missing_result", firstMissingHeight). 383 Uint("seals_size", c.seals.Size()). 384 Uint("receipts_size", c.receipts.Size()). 385 Int("pending_receipt_requests", pendingReceiptRequests). 386 Int64("duration_ms", time.Since(startTime).Milliseconds()). 387 Msg("finalized block processed successfully") 388 389 return nil 390 } 391 392 // getStartAndEndStates returns the pair: (start state commitment; final state commitment) 393 // Error returns: 394 // - ErrNoChunks: if there are no chunks, i.e. the ExecutionResult is malformed 395 // - all other errors are unexpected and symptoms of node-internal problems 396 func getStartAndEndStates(receipt *flow.ExecutionReceipt) (initialState flow.StateCommitment, finalState flow.StateCommitment, err error) { 397 initialState, err = receipt.ExecutionResult.InitialStateCommit() 398 if err != nil { 399 return initialState, finalState, fmt.Errorf("could not get commitment for initial state from receipt: %w", err) 400 } 401 finalState, err = receipt.ExecutionResult.FinalStateCommitment() 402 if err != nil { 403 return initialState, finalState, fmt.Errorf("could not get commitment for final state from receipt: %w", err) 404 } 405 return initialState, finalState, nil 406 }