github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/badger/snapshot.go (about) 1 package badger 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/dgraph-io/badger/v2" 8 9 "github.com/onflow/flow-go/consensus/hotstuff/model" 10 "github.com/onflow/flow-go/model/flow" 11 "github.com/onflow/flow-go/model/flow/filter" 12 "github.com/onflow/flow-go/state/fork" 13 "github.com/onflow/flow-go/state/protocol" 14 "github.com/onflow/flow-go/state/protocol/inmem" 15 "github.com/onflow/flow-go/state/protocol/invalid" 16 "github.com/onflow/flow-go/state/protocol/protocol_state/kvstore" 17 "github.com/onflow/flow-go/storage" 18 "github.com/onflow/flow-go/storage/badger/operation" 19 "github.com/onflow/flow-go/storage/badger/procedure" 20 ) 21 22 // Snapshot implements the protocol.Snapshot interface. 23 // It represents a read-only immutable snapshot of the protocol state at the 24 // block it is constructed with. It allows efficient access to data associated directly 25 // with blocks at a given state (finalized, sealed), such as the related header, commit, 26 // seed or descending blocks. A block snapshot can lazily convert to an epoch snapshot in 27 // order to make data associated directly with epochs accessible through its API. 28 type Snapshot struct { 29 state *State 30 blockID flow.Identifier // reference block for this snapshot 31 } 32 33 // FinalizedSnapshot represents a read-only immutable snapshot of the protocol state 34 // at a finalized block. It is guaranteed to have a header available. 35 type FinalizedSnapshot struct { 36 Snapshot 37 header *flow.Header 38 } 39 40 var _ protocol.Snapshot = (*Snapshot)(nil) 41 var _ protocol.Snapshot = (*FinalizedSnapshot)(nil) 42 43 // newSnapshotWithIncorporatedReferenceBlock creates a new state snapshot with the given reference block. 44 // CAUTION: The caller is responsible for ensuring that the reference block has been incorporated. 45 func newSnapshotWithIncorporatedReferenceBlock(state *State, blockID flow.Identifier) *Snapshot { 46 return &Snapshot{ 47 state: state, 48 blockID: blockID, 49 } 50 } 51 52 // NewFinalizedSnapshot instantiates a `FinalizedSnapshot`. 53 // CAUTION: the header's ID _must_ match `blockID` (not checked) 54 func NewFinalizedSnapshot(state *State, blockID flow.Identifier, header *flow.Header) *FinalizedSnapshot { 55 return &FinalizedSnapshot{ 56 Snapshot: Snapshot{ 57 state: state, 58 blockID: blockID, 59 }, 60 header: header, 61 } 62 } 63 64 func (s *FinalizedSnapshot) Head() (*flow.Header, error) { 65 return s.header, nil 66 } 67 68 func (s *Snapshot) Head() (*flow.Header, error) { 69 head, err := s.state.headers.ByBlockID(s.blockID) 70 return head, err 71 } 72 73 // QuorumCertificate (QC) returns a valid quorum certificate pointing to the 74 // header at this snapshot. 75 // The sentinel error storage.ErrNotFound is returned if the QC is unknown. 76 func (s *Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) { 77 qc, err := s.state.qcs.ByBlockID(s.blockID) 78 if err != nil { 79 return nil, fmt.Errorf("could not retrieve quorum certificate for (%x): %w", s.blockID, err) 80 } 81 return qc, nil 82 } 83 84 func (s *Snapshot) Phase() (flow.EpochPhase, error) { 85 psSnapshot, err := s.state.protocolState.AtBlockID(s.blockID) 86 if err != nil { 87 return flow.EpochPhaseUndefined, fmt.Errorf("could not retrieve protocol state snapshot: %w", err) 88 } 89 return psSnapshot.EpochPhase(), nil 90 } 91 92 func (s *Snapshot) Identities(selector flow.IdentityFilter[flow.Identity]) (flow.IdentityList, error) { 93 psSnapshot, err := s.state.protocolState.AtBlockID(s.blockID) 94 if err != nil { 95 return nil, err 96 } 97 98 // apply the filter to the participants 99 identities := psSnapshot.Identities().Filter(selector) 100 return identities, nil 101 } 102 103 func (s *Snapshot) Identity(nodeID flow.Identifier) (*flow.Identity, error) { 104 // filter identities at snapshot for node ID 105 identities, err := s.Identities(filter.HasNodeID[flow.Identity](nodeID)) 106 if err != nil { 107 return nil, fmt.Errorf("could not get identities: %w", err) 108 } 109 110 // check if node ID is part of identities 111 if len(identities) == 0 { 112 return nil, protocol.IdentityNotFoundError{NodeID: nodeID} 113 } 114 return identities[0], nil 115 } 116 117 // Commit retrieves the latest execution state commitment at the current block snapshot. This 118 // commitment represents the execution state as currently finalized. 119 func (s *Snapshot) Commit() (flow.StateCommitment, error) { 120 // get the ID of the sealed block 121 seal, err := s.state.seals.HighestInFork(s.blockID) 122 if err != nil { 123 return flow.DummyStateCommitment, fmt.Errorf("could not retrieve sealed state commit: %w", err) 124 } 125 return seal.FinalState, nil 126 } 127 128 func (s *Snapshot) SealedResult() (*flow.ExecutionResult, *flow.Seal, error) { 129 seal, err := s.state.seals.HighestInFork(s.blockID) 130 if err != nil { 131 return nil, nil, fmt.Errorf("could not look up latest seal: %w", err) 132 } 133 result, err := s.state.results.ByID(seal.ResultID) 134 if err != nil { 135 return nil, nil, fmt.Errorf("could not get latest result: %w", err) 136 } 137 return result, seal, nil 138 } 139 140 // SealingSegment will walk through the chain backward until we reach the block referenced 141 // by the latest seal and build a SealingSegment. As we visit each block we check each execution 142 // receipt in the block's payload to make sure we have a corresponding execution result, any 143 // execution results missing from blocks are stored in the `SealingSegment.ExecutionResults` field. 144 // See `model/flow/sealing_segment.md` for detailed technical specification of the Sealing Segment 145 // 146 // Expected errors during normal operations: 147 // - protocol.ErrSealingSegmentBelowRootBlock if sealing segment would stretch beyond the node's local history cut-off 148 // - protocol.UnfinalizedSealingSegmentError if sealing segment would contain unfinalized blocks (including orphaned blocks) 149 func (s *Snapshot) SealingSegment() (*flow.SealingSegment, error) { 150 // Lets denote the highest block in the sealing segment `head` (initialized below). 151 // Based on the tech spec `flow/sealing_segment.md`, the Sealing Segment must contain 152 // enough history to satisfy _all_ of the following conditions: 153 // (i) The highest sealed block as of `head` needs to be included in the sealing segment. 154 // This is relevant if `head` does not contain any seals. 155 // (ii) All blocks that are sealed by `head`. This is relevant if head` contains _multiple_ seals. 156 // (iii) The sealing segment should contain the history back to (including): 157 // limitHeight := max(blockSealedAtHead.Height - flow.DefaultTransactionExpiry, SporkRootBlockHeight) 158 // Per convention, we include the blocks for (i) in the `SealingSegment.Blocks`, while the 159 // additional blocks for (ii) and optionally (iii) are contained in as `SealingSegment.ExtraBlocks`. 160 head, err := s.state.blocks.ByID(s.blockID) 161 if err != nil { 162 return nil, fmt.Errorf("could not get snapshot's reference block: %w", err) 163 } 164 if head.Header.Height < s.state.finalizedRootHeight { 165 return nil, protocol.ErrSealingSegmentBelowRootBlock 166 } 167 168 // Verify that head of sealing segment is finalized. 169 finalizedBlockAtHeight, err := s.state.headers.BlockIDByHeight(head.Header.Height) 170 if err != nil { 171 if errors.Is(err, storage.ErrNotFound) { 172 return nil, protocol.NewUnfinalizedSealingSegmentErrorf("head of sealing segment at height %d is not finalized: %w", head.Header.Height, err) 173 } 174 return nil, fmt.Errorf("exception while retrieving finzalized bloc, by height: %w", err) 175 } 176 if finalizedBlockAtHeight != s.blockID { // comparison of fixed-length arrays 177 return nil, protocol.NewUnfinalizedSealingSegmentErrorf("head of sealing segment is orphaned, finalized block at height %d is %x", head.Header.Height, finalizedBlockAtHeight) 178 } 179 180 // STEP (i): highest sealed block as of `head` must be included. 181 seal, err := s.state.seals.HighestInFork(s.blockID) 182 if err != nil { 183 return nil, fmt.Errorf("could not get seal for sealing segment: %w", err) 184 } 185 blockSealedAtHead, err := s.state.headers.ByBlockID(seal.BlockID) 186 if err != nil { 187 return nil, fmt.Errorf("could not get block: %w", err) 188 } 189 190 // TODO this is a temporary measure resulting from epoch data being stored outside the 191 // protocol KV Store, once epoch data is in the KV Store, we can pass protocolKVStoreSnapshotsDB.ByID 192 // directly to NewSealingSegmentBuilder (similar to other getters) 193 getProtocolStateEntry := func(protocolStateID flow.Identifier) (*flow.ProtocolStateEntryWrapper, error) { 194 kvStoreEntry, err := s.state.protocolKVStoreSnapshotsDB.ByID(protocolStateID) 195 if err != nil { 196 return nil, fmt.Errorf("could not get kv store entry: %w", err) 197 } 198 kvStoreReader, err := kvstore.VersionedDecode(kvStoreEntry.Version, kvStoreEntry.Data) 199 if err != nil { 200 return nil, fmt.Errorf("could not decode kv store entry: %w", err) 201 } 202 epochDataEntry, err := s.state.protocolStateSnapshotsDB.ByID(kvStoreReader.GetEpochStateID()) 203 if err != nil { 204 return nil, fmt.Errorf("could not get epoch data: %w", err) 205 } 206 return &flow.ProtocolStateEntryWrapper{ 207 KVStore: flow.PSKeyValueStoreData{ 208 Version: kvStoreEntry.Version, 209 Data: kvStoreEntry.Data, 210 }, 211 EpochEntry: epochDataEntry, 212 }, nil 213 } 214 215 // walk through the chain backward until we reach the block referenced by 216 // the latest seal - the returned segment includes this block 217 builder := flow.NewSealingSegmentBuilder(s.state.results.ByID, s.state.seals.HighestInFork, getProtocolStateEntry) 218 scraper := func(header *flow.Header) error { 219 blockID := header.ID() 220 block, err := s.state.blocks.ByID(blockID) 221 if err != nil { 222 return fmt.Errorf("could not get block: %w", err) 223 } 224 225 err = builder.AddBlock(block) 226 if err != nil { 227 return fmt.Errorf("could not add block to sealing segment: %w", err) 228 } 229 230 return nil 231 } 232 err = fork.TraverseForward(s.state.headers, s.blockID, scraper, fork.IncludingBlock(seal.BlockID)) 233 if err != nil { 234 return nil, fmt.Errorf("could not traverse sealing segment: %w", err) 235 } 236 237 // STEP (ii): extend history down to the lowest block, whose seal is included in `head` 238 lowestSealedByHead := blockSealedAtHead 239 for _, sealInHead := range head.Payload.Seals { 240 h, e := s.state.headers.ByBlockID(sealInHead.BlockID) 241 if e != nil { 242 return nil, fmt.Errorf("could not get block (id=%x) for seal: %w", seal.BlockID, e) // storage.ErrNotFound or exception 243 } 244 if h.Height < lowestSealedByHead.Height { 245 lowestSealedByHead = h 246 } 247 } 248 249 // STEP (iii): extended history to allow checking for duplicated collections, i.e. 250 // limitHeight = max(blockSealedAtHead.Height - flow.DefaultTransactionExpiry, SporkRootBlockHeight) 251 limitHeight := s.state.sporkRootBlockHeight 252 if blockSealedAtHead.Height > s.state.sporkRootBlockHeight+flow.DefaultTransactionExpiry { 253 limitHeight = blockSealedAtHead.Height - flow.DefaultTransactionExpiry 254 } 255 256 // As we have to satisfy (ii) _and_ (iii), we have to take the longest history, i.e. the lowest height. 257 if lowestSealedByHead.Height < limitHeight { 258 limitHeight = lowestSealedByHead.Height 259 if limitHeight < s.state.sporkRootBlockHeight { // sanity check; should never happen 260 return nil, fmt.Errorf("unexpected internal error: calculated history-cutoff at height %d, which is lower than the spork's root height %d", limitHeight, s.state.sporkRootBlockHeight) 261 } 262 } 263 if limitHeight < blockSealedAtHead.Height { 264 // we need to include extra blocks in sealing segment 265 extraBlocksScraper := func(header *flow.Header) error { 266 blockID := header.ID() 267 block, err := s.state.blocks.ByID(blockID) 268 if err != nil { 269 return fmt.Errorf("could not get block: %w", err) 270 } 271 272 err = builder.AddExtraBlock(block) 273 if err != nil { 274 return fmt.Errorf("could not add block to sealing segment: %w", err) 275 } 276 277 return nil 278 } 279 280 err = fork.TraverseBackward(s.state.headers, blockSealedAtHead.ParentID, extraBlocksScraper, fork.IncludingHeight(limitHeight)) 281 if err != nil { 282 return nil, fmt.Errorf("could not traverse extra blocks for sealing segment: %w", err) 283 } 284 } 285 286 segment, err := builder.SealingSegment() 287 if err != nil { 288 return nil, fmt.Errorf("could not build sealing segment: %w", err) 289 } 290 291 return segment, nil 292 } 293 294 func (s *Snapshot) Descendants() ([]flow.Identifier, error) { 295 descendants, err := s.descendants(s.blockID) 296 if err != nil { 297 return nil, fmt.Errorf("failed to traverse the descendants tree of block %v: %w", s.blockID, err) 298 } 299 return descendants, nil 300 } 301 302 func (s *Snapshot) lookupChildren(blockID flow.Identifier) ([]flow.Identifier, error) { 303 var children flow.IdentifierList 304 err := s.state.db.View(procedure.LookupBlockChildren(blockID, &children)) 305 if err != nil { 306 return nil, fmt.Errorf("could not get children of block %v: %w", blockID, err) 307 } 308 return children, nil 309 } 310 311 func (s *Snapshot) descendants(blockID flow.Identifier) ([]flow.Identifier, error) { 312 descendantIDs, err := s.lookupChildren(blockID) 313 if err != nil { 314 return nil, err 315 } 316 317 for _, descendantID := range descendantIDs { 318 additionalIDs, err := s.descendants(descendantID) 319 if err != nil { 320 return nil, err 321 } 322 descendantIDs = append(descendantIDs, additionalIDs...) 323 } 324 return descendantIDs, nil 325 } 326 327 // RandomSource returns the seed for the current block's snapshot. 328 // Expected error returns: 329 // * storage.ErrNotFound is returned if the QC is unknown. 330 func (s *Snapshot) RandomSource() ([]byte, error) { 331 qc, err := s.QuorumCertificate() 332 if err != nil { 333 return nil, err 334 } 335 randomSource, err := model.BeaconSignature(qc) 336 if err != nil { 337 return nil, fmt.Errorf("could not create seed from QC's signature: %w", err) 338 } 339 return randomSource, nil 340 } 341 342 func (s *Snapshot) Epochs() protocol.EpochQuery { 343 return &EpochQuery{ 344 snap: s, 345 } 346 } 347 348 func (s *Snapshot) Params() protocol.GlobalParams { 349 return s.state.Params() 350 } 351 352 // EpochProtocolState returns the epoch part of dynamic protocol state that the Head block commits to. 353 // The compliance layer guarantees that only valid blocks are appended to the protocol state. 354 // Returns state.ErrUnknownSnapshotReference if snapshot reference block is unknown. 355 // All other errors should be treated as exceptions. 356 // For each block stored there should be a protocol state stored. 357 func (s *Snapshot) EpochProtocolState() (protocol.DynamicProtocolState, error) { 358 return s.state.protocolState.AtBlockID(s.blockID) 359 } 360 361 // ProtocolState returns the dynamic protocol state that the Head block commits to. 362 // The compliance layer guarantees that only valid blocks are appended to the protocol state. 363 // Returns state.ErrUnknownSnapshotReference if snapshot reference block is unknown. 364 // All other errors should be treated as exceptions. 365 // For each block stored there should be a protocol state stored. 366 func (s *Snapshot) ProtocolState() (protocol.KVStoreReader, error) { 367 return s.state.protocolState.KVStoreAtBlockID(s.blockID) 368 } 369 370 func (s *Snapshot) VersionBeacon() (*flow.SealedVersionBeacon, error) { 371 head, err := s.state.headers.ByBlockID(s.blockID) 372 if err != nil { 373 return nil, err 374 } 375 376 return s.state.versionBeacons.Highest(head.Height) 377 } 378 379 // EpochQuery encapsulates querying epochs w.r.t. a snapshot. 380 type EpochQuery struct { 381 snap *Snapshot 382 } 383 384 // Current returns the current epoch. 385 func (q *EpochQuery) Current() protocol.Epoch { 386 // all errors returned from storage reads here are unexpected, because all 387 // snapshots reside within a current epoch, which must be queryable 388 psSnapshot, err := q.snap.state.protocolState.AtBlockID(q.snap.blockID) 389 if err != nil { 390 return invalid.NewEpochf("could not get protocol state snapshot at block %x: %w", q.snap.blockID, err) 391 } 392 393 setup := psSnapshot.EpochSetup() 394 commit := psSnapshot.EpochCommit() 395 firstHeight, _, isFirstHeightKnown, _, err := q.retrieveEpochHeightBounds(setup.Counter) 396 if err != nil { 397 return invalid.NewEpochf("could not get current epoch height bounds: %s", err.Error()) 398 } 399 if isFirstHeightKnown { 400 return inmem.NewEpochWithStartBoundary(setup, commit, firstHeight) 401 } 402 return inmem.NewCommittedEpoch(setup, commit) 403 } 404 405 // Next returns the next epoch, if it is available. 406 func (q *EpochQuery) Next() protocol.Epoch { 407 408 psSnapshot, err := q.snap.state.protocolState.AtBlockID(q.snap.blockID) 409 if err != nil { 410 return invalid.NewEpochf("could not get protocol state snapshot at block %x: %w", q.snap.blockID, err) 411 } 412 phase := psSnapshot.EpochPhase() 413 entry := psSnapshot.Entry() 414 415 // if we are in the staking phase, the next epoch is not setup yet 416 if phase == flow.EpochPhaseStaking { 417 return invalid.NewEpoch(protocol.ErrNextEpochNotSetup) 418 } 419 // if we are in setup phase, return a SetupEpoch 420 nextSetup := entry.NextEpochSetup 421 if phase == flow.EpochPhaseSetup { 422 return inmem.NewSetupEpoch(nextSetup) 423 } 424 // if we are in committed phase, return a CommittedEpoch 425 nextCommit := entry.NextEpochCommit 426 if phase == flow.EpochPhaseCommitted { 427 return inmem.NewCommittedEpoch(nextSetup, nextCommit) 428 } 429 return invalid.NewEpochf("data corruption: unknown epoch phase implies malformed protocol state epoch data") 430 } 431 432 // Previous returns the previous epoch. During the first epoch after the root 433 // block, this returns a sentinel error (since there is no previous epoch). 434 // For all other epochs, returns the previous epoch. 435 func (q *EpochQuery) Previous() protocol.Epoch { 436 437 psSnapshot, err := q.snap.state.protocolState.AtBlockID(q.snap.blockID) 438 if err != nil { 439 return invalid.NewEpochf("could not get protocol state snapshot at block %x: %w", q.snap.blockID, err) 440 } 441 entry := psSnapshot.Entry() 442 443 // CASE 1: there is no previous epoch - this indicates we are in the first 444 // epoch after a spork root or genesis block 445 if !psSnapshot.PreviousEpochExists() { 446 return invalid.NewEpoch(protocol.ErrNoPreviousEpoch) 447 } 448 449 // CASE 2: we are in any other epoch - retrieve the setup and commit events 450 // for the previous epoch 451 setup := entry.PreviousEpochSetup 452 commit := entry.PreviousEpochCommit 453 454 firstHeight, finalHeight, firstHeightKnown, finalHeightKnown, err := q.retrieveEpochHeightBounds(setup.Counter) 455 if err != nil { 456 return invalid.NewEpochf("could not get epoch height bounds: %w", err) 457 } 458 if firstHeightKnown && finalHeightKnown { 459 // typical case - we usually know both boundaries for a past epoch 460 return inmem.NewEpochWithStartAndEndBoundaries(setup, commit, firstHeight, finalHeight) 461 } 462 if firstHeightKnown && !finalHeightKnown { 463 // this case is possible when the snapshot reference block is un-finalized 464 // and is past an un-finalized epoch boundary 465 return inmem.NewEpochWithStartBoundary(setup, commit, firstHeight) 466 } 467 if !firstHeightKnown && finalHeightKnown { 468 // this case is possible when this node's lowest known block is after 469 // the queried epoch's start boundary 470 return inmem.NewEpochWithEndBoundary(setup, commit, finalHeight) 471 } 472 if !firstHeightKnown && !finalHeightKnown { 473 // this case is possible when this node's lowest known block is after 474 // the queried epoch's end boundary 475 return inmem.NewCommittedEpoch(setup, commit) 476 } 477 return invalid.NewEpochf("sanity check failed: impossible combination of boundaries for previous epoch") 478 } 479 480 // retrieveEpochHeightBounds retrieves the height bounds for an epoch. 481 // Height bounds are NOT fork-aware, and are only determined upon finalization. 482 // 483 // Since the protocol state's API is fork-aware, we may be querying an 484 // un-finalized block, as in the following example: 485 // 486 // Epoch 1 Epoch 2 487 // A <- B <-|- C <- D 488 // 489 // Suppose block B is the latest finalized block and we have queried block D. 490 // Then, the transition from epoch 1 to 2 has not been committed, because the first block of epoch 2 has not been finalized. 491 // In this case, the final block of Epoch 1, from the perspective of block D, is unknown. 492 // There are edge-case scenarios, where a different fork could exist (as illustrated below) 493 // that still adds additional blocks to Epoch 1. 494 // 495 // Epoch 1 Epoch 2 496 // A <- B <---|-- C <- D 497 // ^ 498 // ╰ X <-|- X <- Y <- Z 499 // 500 // Returns: 501 // - (0, 0, false, false, nil) if neither boundary is known 502 // - (firstHeight, 0, true, false, nil) if epoch start boundary is known but end boundary is not known 503 // - (firstHeight, finalHeight, true, true, nil) if epoch start and end boundary are known 504 // - (0, finalHeight, false, true, nil) if epoch start boundary is known but end boundary is not known 505 // 506 // No errors are expected during normal operation. 507 func (q *EpochQuery) retrieveEpochHeightBounds(epoch uint64) ( 508 firstHeight, finalHeight uint64, 509 isFirstHeightKnown, isLastHeightKnown bool, 510 err error, 511 ) { 512 err = q.snap.state.db.View(func(tx *badger.Txn) error { 513 // Retrieve the epoch's first height 514 err = operation.RetrieveEpochFirstHeight(epoch, &firstHeight)(tx) 515 if err != nil { 516 if errors.Is(err, storage.ErrNotFound) { 517 isFirstHeightKnown = false // unknown boundary 518 } else { 519 return err // unexpected error 520 } 521 } else { 522 isFirstHeightKnown = true // known boundary 523 } 524 525 var subsequentEpochFirstHeight uint64 526 err = operation.RetrieveEpochFirstHeight(epoch+1, &subsequentEpochFirstHeight)(tx) 527 if err != nil { 528 if errors.Is(err, storage.ErrNotFound) { 529 isLastHeightKnown = false // unknown boundary 530 } else { 531 return err // unexpected error 532 } 533 } else { // known boundary 534 isLastHeightKnown = true 535 finalHeight = subsequentEpochFirstHeight - 1 536 } 537 538 return nil 539 }) 540 if err != nil { 541 return 0, 0, false, false, err 542 } 543 return firstHeight, finalHeight, isFirstHeightKnown, isLastHeightKnown, nil 544 }