def _update_header_chain_gaps(cls, db: DatabaseAPI, persisting_header: BlockHeaderAPI, base_gaps: ChainGaps = None) -> GapInfo: # The only reason we overwrite this here is to be able to detect when the HeaderDB # de-canonicalizes an uncle that should cause us to re-open a block gap. gap_change, gaps = super()._update_header_chain_gaps( db, persisting_header, base_gaps) if gap_change is not GapChange.NoChange or persisting_header.block_number == 0: return gap_change, gaps # We have written a header for which block number we've already had a header. # This might be a sign of a de-canonicalized uncle. current_gaps = cls._get_chain_gaps(db) if not is_block_number_in_gap(persisting_header.block_number, current_gaps): # ChainDB believes we have that block. If the header has changed, we need to re-open # a gap for the corresponding block. old_canonical_header = cls._get_canonical_block_header_by_number( db, persisting_header.block_number) if old_canonical_header != persisting_header: updated_gaps = reopen_gap(persisting_header.block_number, current_gaps) db.set(SchemaV1.make_chain_gaps_lookup_key(), rlp.encode(updated_gaps, sedes=chain_gaps)) return gap_change, gaps
def test_gap_continuity(changes): MAX_BLOCK_NUM = 21 # method to get all the block numbers that are in a gap right now _all_missing = compose( set, partial(_all_gap_numbers, highest_block_number=MAX_BLOCK_NUM)) @to_set def _all_inserted(chain_gaps): """List all the inserted headers, the block numbers not in gaps""" missing = _all_missing(chain_gaps) for block_num in range(MAX_BLOCK_NUM + 1): if block_num not in missing: yield block_num chain_gaps = GENESIS_CHAIN_GAPS for do_insert, block_num in changes: starts_inserted = _all_inserted(chain_gaps) starts_missing = _all_missing(chain_gaps) if do_insert: to_insert = block_num _, chain_gaps = fill_gap(to_insert, chain_gaps) assert not is_block_number_in_gap(to_insert, chain_gaps) # Make sure that at most this one block number was filled finished_inserted = _all_inserted(chain_gaps) assert to_insert in finished_inserted new_inserts = finished_inserted - starts_inserted if block_num in starts_inserted: assert new_inserts == set() else: assert new_inserts == {block_num} # Make sure that no new gaps were created finished_missing = _all_missing(chain_gaps) assert to_insert not in finished_missing new_missing = finished_missing - starts_missing assert new_missing == set() else: to_remove = block_num # Note that removing a header is inserting a gap chain_gaps = reopen_gap(to_remove, chain_gaps) assert is_block_number_in_gap(to_remove, chain_gaps) # Make sure that no gaps were filled finished_inserted = _all_inserted(chain_gaps) new_inserts = finished_inserted - starts_inserted assert new_inserts == set() # Make sure that at most this one block number gap was reopened finished_missing = _all_missing(chain_gaps) new_missing = finished_missing - starts_missing if block_num in starts_missing: assert new_missing == set() else: assert new_missing == {block_num} _validate_gap_invariants(chain_gaps)
def _decanonicalize_descendant_orphans( cls, db: DatabaseAPI, header: BlockHeaderAPI, checkpoints: Tuple[Hash32, ...]) -> None: # Determine if any children need to be de-canonicalized because they are not children of # the new chain head new_gaps = starting_gaps = cls._get_header_chain_gaps(db) child_number = BlockNumber(header.block_number + 1) try: child = cls._get_canonical_block_header_by_number(db, child_number) except HeaderNotFound: # There is no canonical block here next_invalid_child = None else: if child.parent_hash != header.hash: if child.hash in checkpoints: raise CheckpointsMustBeCanonical( f"Trying to decanonicalize {child} while making {header} the chain tip" ) else: next_invalid_child = child else: next_invalid_child = None while next_invalid_child: # decanonicalize, and add gap for tracking db.delete(SchemaV1.make_block_number_to_hash_lookup_key(child_number)) new_gaps = reopen_gap(child_number, new_gaps) # find next child child_number = BlockNumber(child_number + 1) try: # All contiguous children must now be made invalid next_invalid_child = cls._get_canonical_block_header_by_number(db, child_number) except HeaderNotFound: # Found the end of this streak of canonical blocks break else: if next_invalid_child.hash in checkpoints: raise CheckpointsMustBeCanonical( f"Trying to decanonicalize {next_invalid_child} while making {header} the" " chain tip" ) if new_gaps != starting_gaps: db.set( SchemaV1.make_header_chain_gaps_lookup_key(), rlp.encode(new_gaps, sedes=chain_gaps) )
def _decanonicalize_single(cls, db: DatabaseAPI, block_num: BlockNumber, base_gaps: ChainGaps) -> ChainGaps: """ A single block number was found to no longer be canonical. At doc-time, this only happens because it does not link up with a checkpoint header. So de-canonicalize this block number and insert a gap in the tracked chain gaps. """ db.delete(SchemaV1.make_block_number_to_hash_lookup_key(block_num)) new_gaps = reopen_gap(block_num, base_gaps) if new_gaps != base_gaps: db.set(SchemaV1.make_header_chain_gaps_lookup_key(), rlp.encode(new_gaps, sedes=chain_gaps)) return new_gaps