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