github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/badger/mutator.go (about) 1 package badger 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/dgraph-io/badger/v2" 9 "github.com/rs/zerolog" 10 11 "github.com/onflow/flow-go/engine" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/module" 14 "github.com/onflow/flow-go/module/irrecoverable" 15 "github.com/onflow/flow-go/module/signature" 16 "github.com/onflow/flow-go/module/trace" 17 "github.com/onflow/flow-go/state" 18 "github.com/onflow/flow-go/state/protocol" 19 "github.com/onflow/flow-go/storage" 20 "github.com/onflow/flow-go/storage/badger/operation" 21 "github.com/onflow/flow-go/storage/badger/procedure" 22 "github.com/onflow/flow-go/storage/badger/transaction" 23 ) 24 25 // FollowerState implements a lighter version of a mutable protocol state. 26 // When extending the state, it performs hardly any checks on the block payload. 27 // Instead, the FollowerState relies on the consensus nodes to run the full 28 // payload check and uses quorum certificates to prove validity of block payloads. 29 // Consequently, a block B should only be considered valid, if 30 // there is a certifying QC for that block QC.View == Block.View && QC.BlockID == Block.ID(). 31 // 32 // The FollowerState allows non-consensus nodes to execute fork-aware queries 33 // against the protocol state, while minimizing the amount of payload checks 34 // the non-consensus nodes have to perform. 35 type FollowerState struct { 36 *State 37 38 index storage.Index 39 payloads storage.Payloads 40 tracer module.Tracer 41 logger zerolog.Logger 42 consumer protocol.Consumer 43 blockTimer protocol.BlockTimer 44 } 45 46 var _ protocol.FollowerState = (*FollowerState)(nil) 47 48 // ParticipantState implements a mutable state for consensus participant. It can extend the 49 // state with a new block, by checking the _entire_ block payload. 50 type ParticipantState struct { 51 *FollowerState 52 receiptValidator module.ReceiptValidator 53 sealValidator module.SealValidator 54 } 55 56 var _ protocol.ParticipantState = (*ParticipantState)(nil) 57 58 // NewFollowerState initializes a light-weight version of a mutable protocol 59 // state. This implementation is suitable only for NON-Consensus nodes. 60 func NewFollowerState( 61 logger zerolog.Logger, 62 tracer module.Tracer, 63 consumer protocol.Consumer, 64 state *State, 65 index storage.Index, 66 payloads storage.Payloads, 67 blockTimer protocol.BlockTimer, 68 ) (*FollowerState, error) { 69 followerState := &FollowerState{ 70 State: state, 71 index: index, 72 payloads: payloads, 73 tracer: tracer, 74 logger: logger, 75 consumer: consumer, 76 blockTimer: blockTimer, 77 } 78 return followerState, nil 79 } 80 81 // NewFullConsensusState initializes a new mutable protocol state backed by a 82 // badger database. When extending the state with a new block, it checks the 83 // _entire_ block payload. Consensus nodes should use the FullConsensusState, 84 // while other node roles can use the lighter FollowerState. 85 func NewFullConsensusState( 86 logger zerolog.Logger, 87 tracer module.Tracer, 88 consumer protocol.Consumer, 89 state *State, 90 index storage.Index, 91 payloads storage.Payloads, 92 blockTimer protocol.BlockTimer, 93 receiptValidator module.ReceiptValidator, 94 sealValidator module.SealValidator, 95 ) (*ParticipantState, error) { 96 followerState, err := NewFollowerState( 97 logger, 98 tracer, 99 consumer, 100 state, 101 index, 102 payloads, 103 blockTimer, 104 ) 105 if err != nil { 106 return nil, fmt.Errorf("initialization of Mutable Follower State failed: %w", err) 107 } 108 return &ParticipantState{ 109 FollowerState: followerState, 110 receiptValidator: receiptValidator, 111 sealValidator: sealValidator, 112 }, nil 113 } 114 115 // ExtendCertified extends the protocol state of a CONSENSUS FOLLOWER. While it checks 116 // the validity of the header; it does _not_ check the validity of the payload. 117 // Instead, the consensus follower relies on the consensus participants to 118 // validate the full payload. Payload validity can be proved by a valid quorum certificate. 119 // Certifying QC must match candidate block: 120 // 121 // candidate.View == certifyingQC.View && candidate.ID() == certifyingQC.BlockID 122 // 123 // Caution: 124 // - This function expects that `certifyingQC` has been validated. 125 // - The parent block must already be stored. 126 // 127 // No errors are expected during normal operations. 128 func (m *FollowerState) ExtendCertified(ctx context.Context, candidate *flow.Block, certifyingQC *flow.QuorumCertificate) error { 129 span, ctx := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorHeaderExtend) 130 defer span.End() 131 132 // check if candidate block has been already processed 133 blockID := candidate.ID() 134 isDuplicate, err := m.checkBlockAlreadyProcessed(blockID) 135 if err != nil || isDuplicate { 136 return err 137 } 138 deferredDbOps := transaction.NewDeferredDbOps() 139 140 // sanity check if certifyingQC actually certifies candidate block 141 if certifyingQC.View != candidate.Header.View { 142 return fmt.Errorf("qc doesn't certify candidate block, expect %d view, got %d", candidate.Header.View, certifyingQC.View) 143 } 144 if certifyingQC.BlockID != blockID { 145 return fmt.Errorf("qc doesn't certify candidate block, expect %x blockID, got %x", blockID, certifyingQC.BlockID) 146 } 147 148 // check if the block header is a valid extension of parent block 149 err = m.headerExtend(ctx, candidate, certifyingQC, deferredDbOps) 150 if err != nil { 151 // since we have a QC for this block, it cannot be an invalid extension 152 return fmt.Errorf("unexpected invalid block (id=%x) with certifying qc (id=%x): %s", 153 candidate.ID(), certifyingQC.ID(), err.Error()) 154 } 155 156 // find the last seal at the parent block 157 _, err = m.lastSealed(candidate, deferredDbOps) 158 if err != nil { 159 return fmt.Errorf("failed to determine the lastest sealed block in fork: %w", err) 160 } 161 162 // evolve protocol state and verify consistency with commitment included in 163 err = m.evolveProtocolState(ctx, candidate, deferredDbOps) 164 if err != nil { 165 return fmt.Errorf("evolving protocol state failed: %w", err) 166 } 167 168 // Execute the deferred database operations as one atomic transaction and emit scheduled notifications on success. 169 // The `candidate` block _must be valid_ (otherwise, the state will be corrupted)! 170 err = operation.RetryOnConflictTx(m.db, transaction.Update, deferredDbOps.Pending()) // No errors are expected during normal operations 171 if err != nil { 172 return fmt.Errorf("failed to persist candidate block %v and its dependencies: %w", blockID, err) 173 } 174 175 return nil 176 } 177 178 // Extend extends the protocol state of a CONSENSUS PARTICIPANT. It checks 179 // the validity of the _entire block_ (header and full payload). 180 // Expected errors during normal operations: 181 // - state.OutdatedExtensionError if the candidate block is outdated (e.g. orphaned) 182 // - state.InvalidExtensionError if the candidate block is invalid 183 func (m *ParticipantState) Extend(ctx context.Context, candidate *flow.Block) error { 184 span, ctx := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorExtend) 185 defer span.End() 186 187 // check if candidate block has been already processed 188 isDuplicate, err := m.checkBlockAlreadyProcessed(candidate.ID()) 189 if err != nil || isDuplicate { 190 return err 191 } 192 deferredDbOps := transaction.NewDeferredDbOps() 193 194 // check if the block header is a valid extension of parent block 195 err = m.headerExtend(ctx, candidate, nil, deferredDbOps) 196 if err != nil { 197 return fmt.Errorf("header not compliant with chain state: %w", err) 198 } 199 200 // check if the block header is a valid extension of the finalized state 201 err = m.checkOutdatedExtension(candidate.Header) 202 if err != nil { 203 if state.IsOutdatedExtensionError(err) { 204 return fmt.Errorf("candidate block is an outdated extension: %w", err) 205 } 206 return fmt.Errorf("could not check if block is an outdated extension: %w", err) 207 } 208 209 // check if the guarantees in the payload is a valid extension of the finalized state 210 err = m.guaranteeExtend(ctx, candidate) 211 if err != nil { 212 return fmt.Errorf("payload guarantee(s) not compliant with chain state: %w", err) 213 } 214 215 // check if the receipts in the payload are valid 216 err = m.receiptExtend(ctx, candidate) 217 if err != nil { 218 return fmt.Errorf("payload receipt(s) not compliant with chain state: %w", err) 219 } 220 221 // check if the seals in the payload is a valid extension of the finalized state 222 _, err = m.sealExtend(ctx, candidate, deferredDbOps) 223 if err != nil { 224 return fmt.Errorf("payload seal(s) not compliant with chain state: %w", err) 225 } 226 227 // evolve protocol state and verify consistency with commitment included in payload 228 err = m.evolveProtocolState(ctx, candidate, deferredDbOps) 229 if err != nil { 230 return fmt.Errorf("evolving protocol state failed: %w", err) 231 } 232 233 // Execute the deferred database operations and emit scheduled notifications on success. 234 // The `candidate` block _must be valid_ (otherwise, the state will be corrupted)! 235 err = operation.RetryOnConflictTx(m.db, transaction.Update, deferredDbOps.Pending()) // No errors are expected during normal operations 236 if err != nil { 237 return fmt.Errorf("failed to persist candiate block %v and its dependencies: %w", candidate.ID(), err) 238 } 239 return nil 240 } 241 242 // headerExtend verifies the validity of the block header (excluding verification of the 243 // consensus rules). Specifically, we check that 244 // 1. the payload is consistent with the payload hash stated in the header 245 // 2. candidate header is consistent with its parent: 246 // - ChainID is identical 247 // - height increases by 1 248 // - ParentView stated by the candidate block equals the parent's actual view 249 // 3. candidate's block time conforms to protocol rules 250 // 4. If a `certifyingQC` is given (can be nil), we sanity-check that it certifies the candidate block 251 // 252 // If all checks pass, this method queues the following operations to persist the candidate block and 253 // schedules `BlockProcessable` notification to be emitted in order of increasing height: 254 // 255 // 5a. store QC embedded into the candidate block and emit `BlockProcessable` notification for the parent 256 // 5b. store candidate block and index it as a child of its parent (needed for recovery to traverse unfinalized blocks) 257 // 5c. if we are given a certifyingQC, store it and queue a `BlockProcessable` notification for the candidate block 258 // 259 // If `headerExtend` is called by `ParticipantState.Extend` (full consensus participant) then `certifyingQC` will be nil, 260 // but the block payload will be validated. If `headerExtend` is called by `FollowerState.Extend` (consensus follower), 261 // then `certifyingQC` must be not nil which proves payload validity. 262 // 263 // Expected errors during normal operations: 264 // - state.InvalidExtensionError if the candidate block is invalid 265 func (m *FollowerState) headerExtend(ctx context.Context, candidate *flow.Block, certifyingQC *flow.QuorumCertificate, deferredDbOps *transaction.DeferredDbOps) error { 266 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorExtendCheckHeader) 267 defer span.End() 268 blockID := candidate.ID() 269 header := candidate.Header 270 271 // STEP 1: Check that the payload is consistent with the payload hash in the header 272 if candidate.Payload.Hash() != header.PayloadHash { 273 return state.NewInvalidExtensionError("payload integrity check failed") 274 } 275 276 // STEP 2: Next, we can check whether the block is a valid descendant of the 277 // parent. It should have the same chain ID and a height that is one bigger. 278 parent, err := m.headers.ByBlockID(header.ParentID) 279 if err != nil { 280 return state.NewInvalidExtensionErrorf("could not retrieve parent: %s", err) 281 } 282 if header.ChainID != parent.ChainID { 283 return state.NewInvalidExtensionErrorf("candidate built for invalid chain (candidate: %s, parent: %s)", 284 header.ChainID, parent.ChainID) 285 } 286 if header.ParentView != parent.View { 287 return state.NewInvalidExtensionErrorf("candidate build with inconsistent parent view (candidate: %d, parent %d)", 288 header.ParentView, parent.View) 289 } 290 if header.Height != parent.Height+1 { 291 return state.NewInvalidExtensionErrorf("candidate built with invalid height (candidate: %d, parent: %d)", 292 header.Height, parent.Height) 293 } 294 295 // STEP 3: check validity of block timestamp using parent's timestamp 296 err = m.blockTimer.Validate(parent.Timestamp, header.Timestamp) 297 if err != nil { 298 if protocol.IsInvalidBlockTimestampError(err) { 299 return state.NewInvalidExtensionErrorf("candidate contains invalid timestamp: %w", err) 300 } 301 return fmt.Errorf("validating block's time stamp failed with unexpected error: %w", err) 302 } 303 304 // STEP 4: if a certifying QC is given (can be nil), sanity-check that it actually certifies the candidate block 305 if certifyingQC != nil { 306 if certifyingQC.View != header.View { 307 return fmt.Errorf("qc doesn't certify candidate block, expect %d view, got %d", header.View, certifyingQC.View) 308 } 309 if certifyingQC.BlockID != blockID { 310 return fmt.Errorf("qc doesn't certify candidate block, expect %x blockID, got %x", blockID, certifyingQC.BlockID) 311 } 312 } 313 314 // STEP 5: 315 qc := candidate.Header.QuorumCertificate() 316 deferredDbOps.AddDbOp(func(tx *transaction.Tx) error { 317 // STEP 5a: Store QC for parent block and emit `BlockProcessable` notification if and only if 318 // - the QC for the parent has not been stored before (otherwise, we already emitted the notification) and 319 // - the parent block's height is larger than the finalized root height (the root block is already considered processed) 320 // Thereby, we reduce duplicated `BlockProcessable` notifications. 321 err := m.qcs.StoreTx(qc)(tx) 322 if err != nil { 323 if !errors.Is(err, storage.ErrAlreadyExists) { 324 return fmt.Errorf("could not store incorporated qc: %w", err) 325 } 326 } else { 327 // trigger BlockProcessable for parent block above root height 328 if parent.Height > m.finalizedRootHeight { 329 tx.OnSucceed(func() { 330 m.consumer.BlockProcessable(parent, qc) 331 }) 332 } 333 } 334 335 // STEP 5b: Store candidate block and index it as a child of its parent (needed for recovery to traverse unfinalized blocks) 336 err = m.blocks.StoreTx(candidate)(tx) // insert the block into the database AND cache 337 if err != nil { 338 return fmt.Errorf("could not store candidate block: %w", err) 339 } 340 err = transaction.WithTx(procedure.IndexNewBlock(blockID, candidate.Header.ParentID))(tx) 341 if err != nil { 342 return fmt.Errorf("could not index new block: %w", err) 343 } 344 345 // STEP 5c: if we are given a certifyingQC, store it and queue a `BlockProcessable` notification for the candidate block 346 if certifyingQC != nil { 347 err = m.qcs.StoreTx(certifyingQC)(tx) 348 if err != nil { 349 return fmt.Errorf("could not store certifying qc: %w", err) 350 } 351 tx.OnSucceed(func() { // queue a BlockProcessable event for candidate block, since it is certified 352 m.consumer.BlockProcessable(candidate.Header, certifyingQC) 353 }) 354 } 355 return nil 356 }) 357 358 return nil 359 } 360 361 // checkBlockAlreadyProcessed checks if block has been added to the protocol state. 362 // Returns: 363 // * (true, nil) - block has been already processed. 364 // * (false, nil) - block has not been processed. 365 // * (false, error) - unknown error when trying to query protocol state. 366 // No errors are expected during normal operation. 367 func (m *FollowerState) checkBlockAlreadyProcessed(blockID flow.Identifier) (bool, error) { 368 _, err := m.headers.ByBlockID(blockID) 369 if err != nil { 370 if errors.Is(err, storage.ErrNotFound) { 371 return false, nil 372 } 373 return false, fmt.Errorf("could not check if candidate block (%x) has been already processed: %w", blockID, err) 374 } 375 return true, nil 376 } 377 378 // checkOutdatedExtension checks whether given block is 379 // valid in the context of the entire state. For this, the block needs to 380 // directly connect, through its ancestors, to the last finalized block. 381 // Expected errors during normal operations: 382 // - state.OutdatedExtensionError if the candidate block is outdated (e.g. orphaned) 383 func (m *ParticipantState) checkOutdatedExtension(header *flow.Header) error { 384 var finalizedHeight uint64 385 err := m.db.View(operation.RetrieveFinalizedHeight(&finalizedHeight)) 386 if err != nil { 387 return fmt.Errorf("could not retrieve finalized height: %w", err) 388 } 389 var finalID flow.Identifier 390 err = m.db.View(operation.LookupBlockHeight(finalizedHeight, &finalID)) 391 if err != nil { 392 return fmt.Errorf("could not lookup finalized block: %w", err) 393 } 394 395 ancestorID := header.ParentID 396 for ancestorID != finalID { 397 ancestor, err := m.headers.ByBlockID(ancestorID) 398 if err != nil { 399 return fmt.Errorf("could not retrieve ancestor (%x): %w", ancestorID, err) 400 } 401 if ancestor.Height < finalizedHeight { 402 // this happens when the candidate block is on a fork that does not include all the 403 // finalized blocks. 404 // for instance: 405 // A (Finalized) <- B (Finalized) <- C (Finalized) <- D <- E <- F 406 // ^- G ^- H ^- I 407 // block G is not a valid block, because it does not have C (which has been finalized) as an ancestor 408 // block H and I are valid, because they do have C as an ancestor 409 return state.NewOutdatedExtensionErrorf( 410 "candidate block (height: %d) conflicts with finalized state (ancestor: %d final: %d)", 411 header.Height, ancestor.Height, finalizedHeight) 412 } 413 ancestorID = ancestor.ParentID 414 } 415 return nil 416 } 417 418 // guaranteeExtend verifies the validity of the collection guarantees that are 419 // included in the block. Specifically, we check for expired collections and 420 // duplicated collections (also including ancestor blocks). 421 // Expected errors during normal operations: 422 // - state.InvalidExtensionError if the candidate block contains invalid collection guarantees 423 func (m *ParticipantState) guaranteeExtend(ctx context.Context, candidate *flow.Block) error { 424 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorExtendCheckGuarantees) 425 defer span.End() 426 427 header := candidate.Header 428 payload := candidate.Payload 429 430 // we only look as far back for duplicates as the transaction expiry limit; 431 // if a guarantee was included before that, we will disqualify it on the 432 // basis of the reference block anyway 433 limit := header.Height - flow.DefaultTransactionExpiry 434 if limit > header.Height { // overflow check 435 limit = 0 436 } 437 if limit < m.sporkRootBlockHeight { 438 limit = m.sporkRootBlockHeight 439 } 440 441 // build a list of all previously used guarantees on this part of the chain 442 ancestorID := header.ParentID 443 lookup := make(map[flow.Identifier]struct{}) 444 for { 445 ancestor, err := m.headers.ByBlockID(ancestorID) 446 if err != nil { 447 return fmt.Errorf("could not retrieve ancestor header (%x): %w", ancestorID, err) 448 } 449 index, err := m.index.ByBlockID(ancestorID) 450 if err != nil { 451 return fmt.Errorf("could not retrieve ancestor index (%x): %w", ancestorID, err) 452 } 453 for _, collID := range index.CollectionIDs { 454 lookup[collID] = struct{}{} 455 } 456 if ancestor.Height <= limit { 457 break 458 } 459 ancestorID = ancestor.ParentID 460 } 461 462 // check each guarantee included in the payload for duplication and expiry 463 for _, guarantee := range payload.Guarantees { 464 465 // if the guarantee was already included before, error 466 _, duplicated := lookup[guarantee.ID()] 467 if duplicated { 468 return state.NewInvalidExtensionErrorf("payload includes duplicate guarantee (%x)", guarantee.ID()) 469 } 470 471 // get the reference block to check expiry 472 ref, err := m.headers.ByBlockID(guarantee.ReferenceBlockID) 473 if err != nil { 474 if errors.Is(err, storage.ErrNotFound) { 475 return state.NewInvalidExtensionErrorf("could not get reference block %x: %w", guarantee.ReferenceBlockID, err) 476 } 477 return fmt.Errorf("could not get reference block (%x): %w", guarantee.ReferenceBlockID, err) 478 } 479 480 // if the guarantee references a block with expired height, error 481 if ref.Height < limit { 482 return state.NewInvalidExtensionErrorf("payload includes expired guarantee (height: %d, limit: %d)", 483 ref.Height, limit) 484 } 485 486 // check the guarantors are correct 487 _, err = protocol.FindGuarantors(m, guarantee) 488 if err != nil { 489 if signature.IsInvalidSignerIndicesError(err) || 490 errors.Is(err, protocol.ErrNextEpochNotCommitted) || 491 errors.Is(err, protocol.ErrClusterNotFound) { 492 return state.NewInvalidExtensionErrorf("guarantee %v contains invalid guarantors: %w", guarantee.ID(), err) 493 } 494 return fmt.Errorf("could not find guarantor for guarantee %v: %w", guarantee.ID(), err) 495 } 496 } 497 498 return nil 499 } 500 501 // sealExtend checks the compliance of the payload seals. It queues a deferred database 502 // operation for indexing the latest seal as of the candidate block and returns the latest seal. 503 // Expected errors during normal operations: 504 // - state.InvalidExtensionError if the candidate block has invalid seals 505 func (m *ParticipantState) sealExtend(ctx context.Context, candidate *flow.Block, deferredDbOps *transaction.DeferredDbOps) (*flow.Seal, error) { 506 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorExtendCheckSeals) 507 defer span.End() 508 509 lastSeal, err := m.sealValidator.Validate(candidate) 510 if err != nil { 511 return nil, state.NewInvalidExtensionErrorf("seal validation error: %w", err) 512 } 513 514 deferredDbOps.AddBadgerOp(operation.IndexLatestSealAtBlock(candidate.ID(), lastSeal.ID())) 515 return lastSeal, nil 516 } 517 518 // receiptExtend checks the compliance of the receipt payload. 519 // - Receipts should pertain to blocks on the fork 520 // - Receipts should not appear more than once on a fork 521 // - Receipts should pass the ReceiptValidator check 522 // - No seal has been included for the respective block in this particular fork 523 // 524 // We require the receipts to be sorted by block height (within a payload). 525 // 526 // Expected errors during normal operations: 527 // - state.InvalidExtensionError if the candidate block contains invalid receipts 528 func (m *ParticipantState) receiptExtend(ctx context.Context, candidate *flow.Block) error { 529 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorExtendCheckReceipts) 530 defer span.End() 531 532 err := m.receiptValidator.ValidatePayload(candidate) 533 if err != nil { 534 // TODO: this might be not an error, potentially it can be solved by requesting more data and processing this receipt again 535 if errors.Is(err, storage.ErrNotFound) { 536 return state.NewInvalidExtensionErrorf("some entities referenced by receipts are missing: %w", err) 537 } 538 if engine.IsInvalidInputError(err) { 539 return state.NewInvalidExtensionErrorf("payload includes invalid receipts: %w", err) 540 } 541 return fmt.Errorf("unexpected payload validation error %w", err) 542 } 543 544 return nil 545 } 546 547 // lastSealed determines the highest sealed block from the fork with head `candidate`. 548 // It queues a deferred database operation for indexing the latest seal as of the candidate block. 549 // and returns the latest seal. 550 // 551 // For instance, here is the chain state: block 100 is the head, block 97 is finalized, 552 // and 95 is the last sealed block at the state of block 100. 553 // 95 (sealed) <- 96 <- 97 (finalized) <- 98 <- 99 <- 100 554 // Now, if block 101 is extending block 100, and its payload has a seal for 96, then it will 555 // be the last sealed for block 101. 556 // No errors are expected during normal operation. 557 func (m *FollowerState) lastSealed(candidate *flow.Block, deferredDbOps *transaction.DeferredDbOps) (latestSeal *flow.Seal, err error) { 558 payload := candidate.Payload 559 blockID := candidate.ID() 560 561 // If the candidate blocks' payload has no seals, the latest seal in this fork remains unchanged, i.e. latest seal as of the 562 // parent is also the latest seal as of the candidate block. Otherwise, we take the latest seal included in the candidate block. 563 // Note that seals might not be ordered in the block. 564 if len(payload.Seals) == 0 { 565 latestSeal, err = m.seals.HighestInFork(candidate.Header.ParentID) 566 if err != nil { 567 return nil, fmt.Errorf("could not retrieve parent seal (%x): %w", candidate.Header.ParentID, err) 568 } 569 } else { 570 ordered, err := protocol.OrderedSeals(payload.Seals, m.headers) 571 if err != nil { 572 // all errors are unexpected - differentiation is for clearer error messages 573 if errors.Is(err, storage.ErrNotFound) { 574 return nil, irrecoverable.NewExceptionf("ordering seals: candidate payload contains seals for unknown block: %w", err) 575 } 576 if errors.Is(err, protocol.ErrDiscontinuousSeals) || errors.Is(err, protocol.ErrMultipleSealsForSameHeight) { 577 return nil, irrecoverable.NewExceptionf("ordering seals: candidate payload contains invalid seal set: %w", err) 578 } 579 return nil, fmt.Errorf("unexpected error ordering seals: %w", err) 580 } 581 latestSeal = ordered[len(ordered)-1] 582 } 583 584 deferredDbOps.AddBadgerOp(operation.IndexLatestSealAtBlock(blockID, latestSeal.ID())) 585 return latestSeal, nil 586 } 587 588 // evolveProtocolState 589 // - instantiates a Protocol State Mutator from the parent block's state 590 // - applies any state-changing service events sealed by this block 591 // - verifies that the resulting protocol state is consistent with the commitment in the block 592 // 593 // Expected errors during normal operations: 594 // - state.InvalidExtensionError if the Protocol State commitment in the candidate block does 595 // not match the Protocol State we constructed locally 596 func (m *FollowerState) evolveProtocolState(ctx context.Context, candidate *flow.Block, deferredDbOps *transaction.DeferredDbOps) error { 597 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorEvolveProtocolState) 598 defer span.End() 599 600 // Evolve the Protocol State starting from the parent block's state. Information that may change the state is: 601 // the candidate block's view and Service Events from execution results sealed in the candidate block. 602 updatedStateID, dbUpdates, err := m.protocolState.EvolveState(candidate.Header.ParentID, candidate.Header.View, candidate.Payload.Seals) 603 if err != nil { 604 return fmt.Errorf("evolving protocol state failed: %w", err) 605 } 606 607 // verify Protocol State commitment in the candidate block matches the locally-constructed value 608 if updatedStateID != candidate.Payload.ProtocolStateID { 609 return state.NewInvalidExtensionErrorf("invalid protocol state commitment %x in block, which should be %x", candidate.Payload.ProtocolStateID, updatedStateID) 610 } 611 deferredDbOps.AddDbOps(dbUpdates.Pending().WithBlock(candidate.ID())) 612 return nil 613 } 614 615 // Finalize marks the specified block as finalized. 616 // This method only finalizes one block at a time. 617 // Hence, the parent of `blockID` has to be the last finalized block. 618 // No errors are expected during normal operations. 619 func (m *FollowerState) Finalize(ctx context.Context, blockID flow.Identifier) error { 620 // preliminaries: start tracer and retrieve full block 621 span, _ := m.tracer.StartSpanFromContext(ctx, trace.ProtoStateMutatorFinalize) 622 defer span.End() 623 block, err := m.blocks.ByID(blockID) 624 if err != nil { 625 return fmt.Errorf("could not retrieve full block that should be finalized: %w", err) 626 } 627 header := block.Header 628 629 // keep track of metrics updates and protocol events to emit: 630 // - metrics are updated after a successful database update 631 // - protocol events are emitted atomically with the database update 632 var metrics []func() 633 var events []func() 634 635 // Verify that the parent block is the latest finalized block. 636 // this must be the case, as the `Finalize` method only finalizes one block 637 // at a time and hence the parent of `blockID` must already be finalized. 638 var finalized uint64 639 err = m.db.View(operation.RetrieveFinalizedHeight(&finalized)) 640 if err != nil { 641 return fmt.Errorf("could not retrieve finalized height: %w", err) 642 } 643 var finalID flow.Identifier 644 err = m.db.View(operation.LookupBlockHeight(finalized, &finalID)) 645 if err != nil { 646 return fmt.Errorf("could not retrieve final header: %w", err) 647 } 648 if header.ParentID != finalID { 649 return fmt.Errorf("can only finalize child of last finalized block") 650 } 651 652 // We also want to update the last sealed height. Retrieve the block 653 // seal indexed for the block and retrieve the block that was sealed by it. 654 lastSeal, err := m.seals.HighestInFork(blockID) 655 if err != nil { 656 return fmt.Errorf("could not look up sealed header: %w", err) 657 } 658 sealed, err := m.headers.ByBlockID(lastSeal.BlockID) 659 if err != nil { 660 return fmt.Errorf("could not retrieve sealed header: %w", err) 661 } 662 663 // We update metrics and emit protocol events for epoch state changes when 664 // the block corresponding to the state change is finalized 665 parentPsSnapshot, err := m.protocolState.AtBlockID(block.Header.ParentID) 666 if err != nil { 667 return fmt.Errorf("could not retrieve protocol state snapshot for parent: %w", err) 668 } 669 finalizingPsSnapshot, err := m.protocolState.AtBlockID(blockID) 670 if err != nil { 671 return fmt.Errorf("could not retrieve protocol state snapshot: %w", err) 672 } 673 currentEpochSetup := finalizingPsSnapshot.EpochSetup() 674 epochFallbackTriggered, err := m.isEpochEmergencyFallbackTriggered() 675 if err != nil { 676 return fmt.Errorf("could not check persisted epoch emergency fallback flag: %w", err) 677 } 678 679 // if epoch fallback was not previously triggered, check whether this block triggers it 680 // TODO(efm-recovery): remove separate global EFM flag 681 if !epochFallbackTriggered && finalizingPsSnapshot.InvalidEpochTransitionAttempted() { 682 epochFallbackTriggered = true 683 // emit the protocol event only the first time epoch fallback is triggered 684 events = append(events, m.consumer.EpochEmergencyFallbackTriggered) 685 metrics = append(metrics, m.metrics.EpochEmergencyFallbackTriggered) 686 } 687 688 // Determine metric updates and protocol events related to epoch phase changes and epoch transitions. 689 epochPhaseMetrics, epochPhaseEvents, err := m.epochMetricsAndEventsOnBlockFinalized(parentPsSnapshot, finalizingPsSnapshot, header) 690 if err != nil { 691 return fmt.Errorf("could not determine epoch phase metrics/events for finalized block: %w", err) 692 } 693 metrics = append(metrics, epochPhaseMetrics...) 694 events = append(events, epochPhaseEvents...) 695 696 // Extract and validate version beacon events from the block seals. 697 versionBeacons, err := m.versionBeaconOnBlockFinalized(block) 698 if err != nil { 699 return fmt.Errorf("cannot process version beacon: %w", err) 700 } 701 702 // Persist updates in database 703 // * Add this block to the height-indexed set of finalized blocks. 704 // * Update the largest finalized height to this block's height. 705 // * Update the largest height of sealed and finalized block. 706 // This value could actually stay the same if it has no seals in 707 // its payload, in which case the parent's seal is the same. 708 // * set the epoch fallback flag, if it is triggered 709 err = operation.RetryOnConflict(m.db.Update, func(tx *badger.Txn) error { 710 err = operation.IndexBlockHeight(header.Height, blockID)(tx) 711 if err != nil { 712 return fmt.Errorf("could not insert number mapping: %w", err) 713 } 714 err = operation.UpdateFinalizedHeight(header.Height)(tx) 715 if err != nil { 716 return fmt.Errorf("could not update finalized height: %w", err) 717 } 718 err = operation.UpdateSealedHeight(sealed.Height)(tx) 719 if err != nil { 720 return fmt.Errorf("could not update sealed height: %w", err) 721 } 722 if epochFallbackTriggered { 723 err = operation.SetEpochEmergencyFallbackTriggered(blockID)(tx) 724 if err != nil { 725 return fmt.Errorf("could not set epoch fallback flag: %w", err) 726 } 727 } 728 // TODO(efm-recovery): we should be able to omit the `!epochFallbackTriggered` check here. 729 if isFirstBlockOfEpoch(parentPsSnapshot, finalizingPsSnapshot) && !epochFallbackTriggered { 730 err = operation.InsertEpochFirstHeight(currentEpochSetup.Counter, header.Height)(tx) 731 if err != nil { 732 return fmt.Errorf("could not insert epoch first block height: %w", err) 733 } 734 } 735 736 // When a block is finalized, we commit the result for each seal it contains. The sealing logic 737 // guarantees that only a single, continuous execution fork is sealed. Here, we index for 738 // each block ID the ID of its _finalized_ seal. 739 for _, seal := range block.Payload.Seals { 740 err = operation.IndexFinalizedSealByBlockID(seal.BlockID, seal.ID())(tx) 741 if err != nil { 742 return fmt.Errorf("could not index the seal by the sealed block ID: %w", err) 743 } 744 } 745 746 if len(versionBeacons) > 0 { 747 // only index the last version beacon as that is the relevant one. 748 // TODO: The other version beacons can be used for validation. 749 err := operation.IndexVersionBeaconByHeight(versionBeacons[len(versionBeacons)-1])(tx) 750 if err != nil { 751 return fmt.Errorf("could not index version beacon or height (%d): %w", header.Height, err) 752 } 753 } 754 755 return nil 756 }) 757 if err != nil { 758 return fmt.Errorf("could not persist finalization operations for block (%x): %w", blockID, err) 759 } 760 761 // update the cache 762 m.State.cachedLatestFinal.Store(&cachedHeader{blockID, header}) 763 if len(block.Payload.Seals) > 0 { 764 m.State.cachedLatestSealed.Store(&cachedHeader{lastSeal.BlockID, sealed}) 765 } 766 767 // Emit protocol events after database transaction succeeds. Event delivery is guaranteed, 768 // _except_ in case of a crash. Hence, when recovering from a crash, consumers need to deduce 769 // from the state whether they have missed events and re-execute the respective actions. 770 m.consumer.BlockFinalized(header) 771 for _, emit := range events { 772 emit() 773 } 774 775 // update sealed/finalized block metrics 776 m.metrics.FinalizedHeight(header.Height) 777 m.metrics.SealedHeight(sealed.Height) 778 m.metrics.BlockFinalized(block) 779 for _, seal := range block.Payload.Seals { 780 sealedBlock, err := m.blocks.ByID(seal.BlockID) 781 if err != nil { 782 return fmt.Errorf("could not retrieve sealed block (%x): %w", seal.BlockID, err) 783 } 784 m.metrics.BlockSealed(sealedBlock) 785 } 786 787 // apply all queued metrics 788 for _, updateMetric := range metrics { 789 updateMetric() 790 } 791 792 return nil 793 } 794 795 // isFirstBlockOfEpoch returns true if the given block is the first block of a new epoch 796 // by comparing the block's Protocol State Snapshot to that of its parent. 797 // NOTE: There can be multiple (un-finalized) blocks that qualify as the first block of epoch N. 798 func isFirstBlockOfEpoch(parentPsSnapshot, blockPsSnapshot protocol.DynamicProtocolState) bool { 799 return parentPsSnapshot.Epoch() < blockPsSnapshot.Epoch() 800 } 801 802 // epochMetricsAndEventsOnBlockFinalized determines metrics to update and protocol 803 // events to emit upon finalizing a block. 804 // - We notify about an epoch transition when the first block of the new epoch is finalized 805 // - We notify about an epoch phase transition when the first block within the new epoch phase is finalized 806 // 807 // This method must be called for each finalized block. 808 // No errors are expected during normal operation. 809 func (m *FollowerState) epochMetricsAndEventsOnBlockFinalized(parentPsSnapshot, finalizedPsSnapshot protocol.DynamicProtocolState, finalized *flow.Header) ( 810 metrics []func(), 811 events []func(), 812 err error, 813 ) { 814 if finalizedPsSnapshot.InvalidEpochTransitionAttempted() { 815 // No epoch state changes to notify on when EFM is triggered 816 return nil, nil, nil 817 } 818 819 parentEpochCounter := parentPsSnapshot.Epoch() 820 childEpochCounter := finalizedPsSnapshot.Epoch() 821 parentEpochPhase := parentPsSnapshot.EpochPhase() 822 childEpochPhase := finalizedPsSnapshot.EpochPhase() 823 824 // Same epoch phase -> nothing to do 825 if parentEpochPhase == childEpochPhase { 826 return 827 } 828 829 // Different counter -> must be an epoch transition 830 if parentEpochCounter != childEpochCounter { 831 childEpochSetup := finalizedPsSnapshot.EpochSetup() 832 events = append(events, func() { m.consumer.EpochTransition(childEpochSetup.Counter, finalized) }) 833 // set current epoch counter corresponding to new epoch 834 metrics = append(metrics, func() { m.metrics.CurrentEpochCounter(childEpochSetup.Counter) }) 835 // denote the most recent epoch transition height 836 metrics = append(metrics, func() { m.metrics.EpochTransitionHeight(finalized.Height) }) 837 // set epoch phase - since we are starting a new epoch we begin in the staking phase 838 metrics = append(metrics, func() { m.metrics.CurrentEpochPhase(flow.EpochPhaseStaking) }) 839 // set current epoch view values 840 metrics = append( 841 metrics, 842 func() { m.metrics.CurrentEpochFinalView(childEpochSetup.FinalView) }, 843 func() { m.metrics.CurrentDKGPhase1FinalView(childEpochSetup.DKGPhase1FinalView) }, 844 func() { m.metrics.CurrentDKGPhase2FinalView(childEpochSetup.DKGPhase2FinalView) }, 845 func() { m.metrics.CurrentDKGPhase3FinalView(childEpochSetup.DKGPhase3FinalView) }, 846 ) 847 return 848 } 849 // Transition from Staking phase to Setup phase. `finalized` is first block in Setup phase. 850 if parentEpochPhase == flow.EpochPhaseStaking && childEpochPhase == flow.EpochPhaseSetup { 851 events = append(events, func() { m.metrics.CurrentEpochPhase(flow.EpochPhaseSetup) }) 852 events = append(events, func() { m.consumer.EpochSetupPhaseStarted(childEpochCounter, finalized) }) 853 return 854 } 855 // Transition from Setup phase to Committed phase. `finalized` is first block in Committed phase. 856 if parentEpochPhase == flow.EpochPhaseSetup && childEpochPhase == flow.EpochPhaseCommitted { 857 events = append(events, func() { m.metrics.CurrentEpochPhase(flow.EpochPhaseCommitted) }) 858 events = append(events, func() { m.consumer.EpochCommittedPhaseStarted(childEpochCounter, finalized) }) 859 return 860 } 861 862 return nil, nil, fmt.Errorf("sanity check failed: invalid subsequent [epoch-phase] [%d-%s]->[%d-%s]", 863 parentEpochCounter, parentEpochPhase, childEpochCounter, childEpochPhase) 864 } 865 866 // versionBeaconOnBlockFinalized extracts and returns the VersionBeacons from the 867 // finalized block's seals. 868 // This could return multiple VersionBeacons if the parent block contains multiple Seals. 869 // The version beacons will be returned in the ascending height order of the seals. 870 // Technically only the last VersionBeacon is relevant. 871 func (m *FollowerState) versionBeaconOnBlockFinalized( 872 finalized *flow.Block, 873 ) ([]*flow.SealedVersionBeacon, error) { 874 var versionBeacons []*flow.SealedVersionBeacon 875 876 seals, err := protocol.OrderedSeals(finalized.Payload.Seals, m.headers) 877 if err != nil { 878 if errors.Is(err, storage.ErrNotFound) { 879 return nil, fmt.Errorf( 880 "ordering seals: parent payload contains"+ 881 " seals for unknown block: %w", err) 882 } 883 return nil, fmt.Errorf("unexpected error ordering seals: %w", err) 884 } 885 886 for _, seal := range seals { 887 result, err := m.results.ByID(seal.ResultID) 888 if err != nil { 889 return nil, fmt.Errorf( 890 "could not retrieve result (id=%x) for seal (id=%x): %w", 891 seal.ResultID, 892 seal.ID(), 893 err) 894 } 895 for _, event := range result.ServiceEvents { 896 897 ev, ok := event.Event.(*flow.VersionBeacon) 898 899 if !ok { 900 // skip other service event types. 901 // validation if this is a known service event type is done elsewhere. 902 continue 903 } 904 905 err := ev.Validate() 906 if err != nil { 907 m.logger.Warn(). 908 Err(err). 909 Str("block_id", finalized.ID().String()). 910 Interface("event", ev). 911 Msg("invalid VersionBeacon service event") 912 continue 913 } 914 915 // The version beacon only becomes actionable/valid/active once the block 916 // containing the version beacon has been sealed. That is why we set the 917 // Seal height to the current block height. 918 versionBeacons = append(versionBeacons, &flow.SealedVersionBeacon{ 919 VersionBeacon: ev, 920 SealHeight: finalized.Header.Height, 921 }) 922 } 923 } 924 925 return versionBeacons, nil 926 }