github.com/onflow/flow-go@v0.33.17/model/flow/sealing_segment.go (about) 1 package flow 2 3 import ( 4 "fmt" 5 6 "golang.org/x/exp/slices" 7 ) 8 9 // SealingSegment is a continuous segment of recently finalized blocks that contains enough history 10 // for a new node to execute its business logic normally. It is part of the data need to initialize 11 // a new node to join the network. 12 // DETAILED SPECIFICATION: ./sealing_segment.md 13 // 14 // ├═══════════┤ ├───────────────────────┤ 15 // ExtraBlocks ^ Blocks ^ 16 // B head 17 // 18 // Lets denote the highest block in the sealing segment as `head`. Per convention, `head` must be a 19 // finalized block. Consider the chain of blocks leading up to `head` (included). The highest block 20 // in chain leading up to `head` that is sealed, we denote as B. 21 // In other words, head is the last finalized block, and B is the last sealed block, 22 // block at height (B.Height + 1) is not sealed. 23 type SealingSegment struct { 24 // Blocks contain the chain `B <- ... <- Head` in ascending height order. 25 // Formally, Blocks contains exactly (not more!) the history to satisfy condition 26 // (see sealing_segment.md for details): 27 // (i) The highest sealed block as of `head` needs to be included in the sealing segment. 28 // This is relevant if `head` does not contain any seals. 29 Blocks []*Block 30 31 // ExtraBlocks [optional] holds ancestors of `Blocks` in ascending height order. 32 // Formally, ExtraBlocks contains at least the additional history to satisfy conditions 33 // (see sealing_segment.md for details): 34 // (ii) All blocks that are sealed by `head`. This is relevant if `head` contains _multiple_ seals. 35 // (iii) The sealing segment holds the history of all non-expired collection guarantees, i.e. 36 // limitHeight := max(blockSealedAtHead.Height - flow.ExtraBlocksInRootSealingSegment, SporkRootBlockHeight) 37 // where blockSealedAtHead is the block sealed by `head` block. 38 // (Potentially longer history is permitted) 39 ExtraBlocks []*Block 40 41 // ExecutionResults contain any results which are referenced by receipts 42 // or seals in the sealing segment, but not included in any segment block 43 // payloads. 44 // 45 // Due to decoupling of execution receipts from execution results, 46 // it's possible that blocks from the sealing segment will be referring to 47 // execution results incorporated in blocks that aren't part of the segment. 48 ExecutionResults ExecutionResultList 49 50 // LatestSeals is a mapping from block ID to the ID of the latest seal 51 // incorporated as of that block. Note: we store the seals' IDs here 52 // (instead of the full seals), because the seals for all blocks, except for 53 // the lowest one, are contained in the blocks of the sealing segment. 54 LatestSeals map[Identifier]Identifier 55 56 // FirstSeal contains the latest seal as of the first block in the segment. 57 // Per convention, this field holds a seal that was included _prior_ to the 58 // first block of the sealing segment. If the first block in the segment 59 // contains a seal, then this field is `nil`. 60 // This information is needed for the `Commit` method of protocol snapshot 61 // to return the sealed state, when the first block contains no seal. 62 FirstSeal *Seal 63 } 64 65 // Highest is the highest block in the sealing segment and the reference block from snapshot that was 66 // used to produce this sealing segment. 67 func (segment *SealingSegment) Highest() *Block { 68 return segment.Blocks[len(segment.Blocks)-1] 69 } 70 71 // Finalized returns the last finalized block, which is an alias of Highest 72 func (segment *SealingSegment) Finalized() *Block { 73 return segment.Highest() 74 } 75 76 // Sealed returns the most recently sealed block based on head of sealing segment(highest block). 77 func (segment *SealingSegment) Sealed() *Block { 78 return segment.Blocks[0] 79 } 80 81 // AllBlocks returns all blocks within the sealing segment, including extra blocks, in ascending height order. 82 func (segment *SealingSegment) AllBlocks() []*Block { 83 return append(segment.ExtraBlocks, segment.Blocks...) 84 } 85 86 // FinalizedSeal returns the seal that seals the lowest block. 87 // Per specification, this seal must be included in a SealingSegment. 88 // The SealingSegment must be validated. 89 // No errors are expected during normal operation. 90 func (segment *SealingSegment) FinalizedSeal() (*Seal, error) { 91 if isRootSegment(segment.LatestSeals) { 92 return segment.FirstSeal, nil 93 } 94 95 seal, err := findLatestSealForLowestBlock(segment.Blocks, segment.LatestSeals) 96 if err != nil { 97 return nil, err 98 } 99 100 // sanity check 101 if seal.BlockID != segment.Sealed().ID() { 102 return nil, fmt.Errorf("finalized seal should seal the lowest block %v, but actually is to seal %v", 103 segment.Sealed().ID(), seal.BlockID) 104 } 105 return seal, nil 106 } 107 108 // Validate validates the sealing segment structure and returns an error if 109 // the segment isn't valid. This is done by re-building the segment from scratch, 110 // re-using the validation logic already present in the SealingSegmentBuilder. 111 // The node logic requires a valid sealing segment to bootstrap. 112 // No errors are expected during normal operation. 113 func (segment *SealingSegment) Validate() error { 114 115 // populate lookup of seals and results in the segment to satisfy builder 116 seals := make(map[Identifier]*Seal) 117 results := segment.ExecutionResults.Lookup() 118 119 if segment.FirstSeal != nil { 120 seals[segment.FirstSeal.ID()] = segment.FirstSeal 121 } 122 for _, block := range segment.Blocks { 123 for _, result := range block.Payload.Results { 124 results[result.ID()] = result 125 } 126 for _, seal := range block.Payload.Seals { 127 seals[seal.ID()] = seal 128 } 129 } 130 131 getResult := func(resultID Identifier) (*ExecutionResult, error) { 132 result, ok := results[resultID] 133 if !ok { 134 return nil, fmt.Errorf("result (id=%x) not found in segment", resultID) 135 } 136 return result, nil 137 } 138 getSeal := func(blockID Identifier) (*Seal, error) { 139 sealID, ok := segment.LatestSeals[blockID] 140 if !ok { 141 return nil, fmt.Errorf("seal for block (id=%x) not found in segment", blockID) 142 } 143 seal, ok := seals[sealID] 144 if !ok { 145 return nil, fmt.Errorf("seal (id=%x) not found in segment at block %x", sealID, blockID) 146 } 147 return seal, nil 148 } 149 150 builder := NewSealingSegmentBuilder(getResult, getSeal) 151 for _, block := range segment.Blocks { 152 err := builder.AddBlock(block) 153 if err != nil { 154 return fmt.Errorf("invalid segment: %w", err) 155 } 156 } 157 // extra blocks should be added in reverse order, starting from the highest one since they are sorted 158 // in ascending order. 159 for i := len(segment.ExtraBlocks) - 1; i >= 0; i-- { 160 block := segment.ExtraBlocks[i] 161 err := builder.AddExtraBlock(block) 162 if err != nil { 163 return fmt.Errorf("invalid segment: %w", err) 164 } 165 } 166 _, err := builder.SealingSegment() 167 if err != nil { 168 return fmt.Errorf("invalid segment: %w", err) 169 } 170 return nil 171 } 172 173 var ( 174 ErrSegmentMissingSeal = fmt.Errorf("sealing segment failed sanity check: missing seal referenced by segment") 175 ErrSegmentBlocksWrongLen = fmt.Errorf("sealing segment failed sanity check: non-root sealing segment must have at least 2 blocks") 176 ErrSegmentInvalidBlockHeight = fmt.Errorf("sealing segment failed sanity check: blocks must be in ascending order") 177 ErrSegmentResultLookup = fmt.Errorf("failed to lookup execution result") 178 ErrSegmentSealLookup = fmt.Errorf("failed to lookup seal") 179 ) 180 181 // GetResultFunc is a getter function for results by ID. 182 // No errors are expected during normal operation. 183 type GetResultFunc func(resultID Identifier) (*ExecutionResult, error) 184 185 // GetSealByBlockIDFunc is a getter function for seals by block ID, returning 186 // the latest seals incorporated as of the given block. 187 // No errors are expected during normal operation. 188 type GetSealByBlockIDFunc func(blockID Identifier) (*Seal, error) 189 190 // SealingSegmentBuilder is a utility for incrementally building a sealing segment. 191 type SealingSegmentBuilder struct { 192 // access to storage to read referenced by not included resources 193 resultLookup GetResultFunc 194 sealByBlockIDLookup GetSealByBlockIDFunc 195 // keep track of resources included in payloads 196 includedResults map[Identifier]struct{} 197 // resources to include in the sealing segment 198 blocks []*Block 199 results []*ExecutionResult 200 latestSeals map[Identifier]Identifier 201 firstSeal *Seal 202 // extraBlocks included in sealing segment, must connect to the lowest block of segment 203 // stored in descending order for simpler population logic 204 extraBlocks []*Block 205 } 206 207 // AddBlock appends a block to the sealing segment under construction. 208 // No errors are expected during normal operation. 209 func (builder *SealingSegmentBuilder) AddBlock(block *Block) error { 210 // sanity check: all blocks have to be added before adding extra blocks 211 if len(builder.extraBlocks) > 0 { 212 return fmt.Errorf("cannot add sealing segment block after extra block is added") 213 } 214 215 // sanity check: block should be 1 height higher than current highest 216 if !builder.isValidHeight(block) { 217 return fmt.Errorf("invalid block height (%d): %w", block.Header.Height, ErrSegmentInvalidBlockHeight) 218 } 219 blockID := block.ID() 220 221 // a block might contain receipts or seals that refer to results that are included in blocks 222 // whose height is below the first block of the segment. 223 // In order to include those missing results into the segment, we construct a list of those 224 // missing result IDs referenced by this block 225 missingResultIDs := make(map[Identifier]struct{}) 226 227 // for the first (lowest) block, if it contains no seal, store the latest 228 // seal incorporated prior to the first block 229 if len(builder.blocks) == 0 { 230 if len(block.Payload.Seals) == 0 { 231 seal, err := builder.sealByBlockIDLookup(blockID) 232 if err != nil { 233 return fmt.Errorf("%w: %v", ErrSegmentSealLookup, err) 234 } 235 builder.firstSeal = seal 236 // add first seal result ID here, since it isn't in payload 237 missingResultIDs[seal.ResultID] = struct{}{} 238 } 239 } 240 241 // index the latest seal for this block 242 latestSeal, err := builder.sealByBlockIDLookup(blockID) 243 if err != nil { 244 return fmt.Errorf("%w: %v", ErrSegmentSealLookup, err) 245 } 246 builder.latestSeals[blockID] = latestSeal.ID() 247 248 // cache included results and seals 249 // they could be referenced in a future block in the segment 250 for _, result := range block.Payload.Results { 251 builder.includedResults[result.ID()] = struct{}{} 252 } 253 254 for _, receipt := range block.Payload.Receipts { 255 if _, ok := builder.includedResults[receipt.ResultID]; !ok { 256 missingResultIDs[receipt.ResultID] = struct{}{} 257 } 258 } 259 for _, seal := range block.Payload.Seals { 260 if _, ok := builder.includedResults[seal.ResultID]; !ok { 261 missingResultIDs[seal.ResultID] = struct{}{} 262 } 263 } 264 265 // add the missing results 266 for resultID := range missingResultIDs { 267 result, err := builder.resultLookup(resultID) 268 269 if err != nil { 270 return fmt.Errorf("%w: (%x) %v", ErrSegmentResultLookup, resultID, err) 271 } 272 builder.addExecutionResult(result) 273 builder.includedResults[resultID] = struct{}{} 274 } 275 276 builder.blocks = append(builder.blocks, block) 277 return nil 278 } 279 280 // AddExtraBlock appends an extra block to sealing segment under construction. 281 // Extra blocks needs to be added in descending order and the first block must connect to the lowest block 282 // of sealing segment, this way they form a continuous chain. 283 // No errors are expected during normal operation. 284 func (builder *SealingSegmentBuilder) AddExtraBlock(block *Block) error { 285 if len(builder.extraBlocks) == 0 { 286 if len(builder.blocks) == 0 { 287 return fmt.Errorf("cannot add extra blocks before adding lowest sealing segment block") 288 } 289 // first extra block has to match the lowest block of sealing segment 290 if (block.Header.Height + 1) != builder.lowest().Header.Height { 291 return fmt.Errorf("invalid extra block height (%d), doesn't connect to sealing segment: %w", block.Header.Height, ErrSegmentInvalidBlockHeight) 292 } 293 } else if (block.Header.Height + 1) != builder.extraBlocks[len(builder.extraBlocks)-1].Header.Height { 294 return fmt.Errorf("invalid extra block height (%d), doesn't connect to last extra block: %w", block.Header.Height, ErrSegmentInvalidBlockHeight) 295 } 296 297 builder.extraBlocks = append(builder.extraBlocks, block) 298 return nil 299 } 300 301 // AddExecutionResult adds result to executionResults 302 func (builder *SealingSegmentBuilder) addExecutionResult(result *ExecutionResult) { 303 builder.results = append(builder.results, result) 304 } 305 306 // SealingSegment completes building the sealing segment, validating the segment 307 // constructed so far, and returning it as a SealingSegment if it is valid. 308 // 309 // All errors indicate the SealingSegmentBuilder internal state does not represent 310 // a valid sealing segment. 311 // No errors are expected during normal operation. 312 func (builder *SealingSegmentBuilder) SealingSegment() (*SealingSegment, error) { 313 if err := builder.validateSegment(); err != nil { 314 return nil, fmt.Errorf("failed to validate sealing segment: %w", err) 315 } 316 317 // SealingSegment must store extra blocks in ascending order, builder stores them in descending. 318 // Apply a sort to reverse the slice and use correct ordering. 319 slices.SortFunc(builder.extraBlocks, func(lhs, rhs *Block) int { 320 return int(lhs.Header.Height) - int(rhs.Header.Height) 321 }) 322 323 return &SealingSegment{ 324 Blocks: builder.blocks, 325 ExtraBlocks: builder.extraBlocks, 326 ExecutionResults: builder.results, 327 LatestSeals: builder.latestSeals, 328 FirstSeal: builder.firstSeal, 329 }, nil 330 } 331 332 // isValidHeight returns true iff block is exactly 1 height higher than the current highest block in the segment. 333 func (builder *SealingSegmentBuilder) isValidHeight(block *Block) bool { 334 if builder.highest() == nil { 335 return true 336 } 337 338 return block.Header.Height == builder.highest().Header.Height+1 339 } 340 341 // validateRootSegment will check that the current builder state represents a valid 342 // root sealing segment. In particular: 343 // * the root block must be the first block (least height) in the segment 344 // * no blocks in the segment may contain any seals (by the minimality requirement) 345 // 346 // All errors indicate an invalid root segment, and either a bug in SealingSegmentBuilder 347 // or a corrupted underlying protocol state. 348 // No errors are expected during normal operation. 349 func (builder *SealingSegmentBuilder) validateRootSegment() error { 350 if len(builder.blocks) == 0 { 351 return fmt.Errorf("root segment must have at least 1 block") 352 } 353 if len(builder.extraBlocks) > 0 { 354 return fmt.Errorf("root segment cannot have extra blocks") 355 } 356 if builder.lowest().Header.View != 0 { 357 return fmt.Errorf("root block has unexpected view (%d != 0)", builder.lowest().Header.View) 358 } 359 if len(builder.results) != 1 { 360 return fmt.Errorf("expected %d results, got %d", 1, len(builder.results)) 361 } 362 if builder.firstSeal == nil { 363 return fmt.Errorf("firstSeal must not be nil for root segment") 364 } 365 if builder.results[0].BlockID != builder.lowest().ID() { 366 return fmt.Errorf("result (block_id=%x) is not for root block (id=%x)", builder.results[0].BlockID, builder.lowest().ID()) 367 } 368 if builder.results[0].ID() != builder.firstSeal.ResultID { 369 return fmt.Errorf("firstSeal (result_id=%x) is not for root result (id=%x)", builder.firstSeal.ResultID, builder.results[0].ID()) 370 } 371 if builder.results[0].BlockID != builder.firstSeal.BlockID { 372 return fmt.Errorf("root seal (block_id=%x) references different block than root result (block_id=%x)", builder.firstSeal.BlockID, builder.results[0].BlockID) 373 } 374 for _, block := range builder.blocks { 375 if len(block.Payload.Seals) > 0 { 376 return fmt.Errorf("root segment cannot contain blocks with seals (minimality requirement) - block (height=%d,id=%x) has %d seals", 377 block.Header.Height, block.ID(), len(block.Payload.Seals)) 378 } 379 } 380 return nil 381 } 382 383 // validateSegment will validate if builder satisfies conditions for a valid sealing segment. 384 // No errors are expected during normal operation. 385 func (builder *SealingSegmentBuilder) validateSegment() error { 386 // sealing cannot be empty 387 if len(builder.blocks) == 0 { 388 return fmt.Errorf("expect at least 2 blocks in a sealing segment or 1 block in the case of root segments, but got an empty sealing segment: %w", ErrSegmentBlocksWrongLen) 389 } 390 391 if len(builder.extraBlocks) > 0 { 392 if builder.extraBlocks[0].Header.Height+1 != builder.lowest().Header.Height { 393 return fmt.Errorf("extra blocks don't connect to lowest block in segment") 394 } 395 } 396 397 // if root sealing segment, use different validation 398 if isRootSegment(builder.latestSeals) { 399 err := builder.validateRootSegment() 400 if err != nil { 401 return fmt.Errorf("invalid root segment: %w", err) 402 } 403 return nil 404 } 405 406 // validate the latest seal is for the lowest block 407 _, err := findLatestSealForLowestBlock(builder.blocks, builder.latestSeals) 408 if err != nil { 409 return fmt.Errorf("sealing segment missing seal (lowest block id: %x) (highest block id: %x) %v: %w", builder.lowest().ID(), builder.highest().ID(), err, ErrSegmentMissingSeal) 410 } 411 412 return nil 413 } 414 415 // highest returns the highest block in segment. 416 func (builder *SealingSegmentBuilder) highest() *Block { 417 if len(builder.blocks) == 0 { 418 return nil 419 } 420 421 return builder.blocks[len(builder.blocks)-1] 422 } 423 424 // lowest returns the lowest block in segment. 425 func (builder *SealingSegmentBuilder) lowest() *Block { 426 return builder.blocks[0] 427 } 428 429 // NewSealingSegmentBuilder returns *SealingSegmentBuilder 430 func NewSealingSegmentBuilder(resultLookup GetResultFunc, sealLookup GetSealByBlockIDFunc) *SealingSegmentBuilder { 431 return &SealingSegmentBuilder{ 432 resultLookup: resultLookup, 433 sealByBlockIDLookup: sealLookup, 434 includedResults: make(map[Identifier]struct{}), 435 latestSeals: make(map[Identifier]Identifier), 436 blocks: make([]*Block, 0), 437 extraBlocks: make([]*Block, 0), 438 results: make(ExecutionResultList, 0), 439 } 440 } 441 442 // findLatestSealForLowestBlock finds the seal for the lowest block. 443 // As a sanity check, the method confirms that this seal is the latest seal as of the highest block. 444 // In other words, this function checks that the sealing segment's history is minimal. 445 // Inputs: 446 // - `blocks` is the continuous sequence of blocks that form the sealing segment 447 // - `latestSeals` holds for each block the identifier of the latest seal included in the fork as of this block 448 // 449 // CAUTION: this method is only applicable for non-root sealing segments, where at least one block 450 // was sealed after the root block. 451 // Examples: 452 // 453 // A <- B <- C <- D(seal_A) ==> valid 454 // A <- B <- C <- D(seal_A) <- E() ==> valid 455 // A <- B <- C <- D(seal_A,seal_B) ==> invalid, because latest seal is B, but lowest block is A 456 // A <- B <- C <- D(seal_X,seal_A) ==> valid, because it's OK for block X to be unknown 457 // A <- B <- C <- D(seal_A) <- E(seal_B) ==> invalid, because latest seal is B, but lowest block is A 458 // A(seal_A) ==> invalid, because this is impossible for non-root sealing segments 459 // 460 // The node logic requires a valid sealing segment to bootstrap. There are no 461 // errors expected during normal operations. 462 func findLatestSealForLowestBlock(blocks []*Block, latestSeals map[Identifier]Identifier) (*Seal, error) { 463 lowestBlockID := blocks[0].ID() 464 highestBlockID := blocks[len(blocks)-1].ID() 465 466 // get the ID of the latest seal for highest block 467 latestSealID := latestSeals[highestBlockID] 468 469 // find the seal within the block payloads 470 for i := len(blocks) - 1; i >= 0; i-- { 471 block := blocks[i] 472 // look for latestSealID in the payload 473 for _, seal := range block.Payload.Seals { 474 // if we found the latest seal, confirm it seals lowest 475 if seal.ID() == latestSealID { 476 if seal.BlockID == lowestBlockID { 477 return seal, nil 478 } 479 return nil, fmt.Errorf("invalid segment: segment contain seal for block %v, but doesn't match lowest block %v", 480 seal.BlockID, lowestBlockID) 481 } 482 } 483 484 // the latest seal must be found in a block that has a seal when traversing blocks 485 // backwards from higher height to lower height. 486 // otherwise, the sealing segment is invalid 487 if len(block.Payload.Seals) > 0 { 488 return nil, fmt.Errorf("invalid segment: segment's last block contain seal %v, but doesn't match latestSealID: %v", 489 block.Payload.Seals[0].ID(), latestSealID) 490 } 491 } 492 493 return nil, fmt.Errorf("invalid segment: seal %v not found", latestSealID) 494 } 495 496 // isRootSegment returns true if the input latestSeals map represents a root segment. 497 // The implementation makes use of the fact that root sealing segments uniquely 498 // have the same latest seal, for all blocks in the segment. 499 func isRootSegment(latestSeals map[Identifier]Identifier) bool { 500 var rootSealID Identifier 501 // set root seal ID to the latest seal value for any block in the segment 502 for _, sealID := range latestSeals { 503 rootSealID = sealID 504 break 505 } 506 // then, verify all other blocks have the same latest seal 507 for _, sealID := range latestSeals { 508 if sealID != rootSealID { 509 return false 510 } 511 } 512 return true 513 }