def validate_chain(cls, root: BlockHeader, descendants: Tuple[BlockHeader, ...], seal_check_random_sample_rate: int = 1) -> None: """ Validate that all of the descendents are valid, given that the root header is valid. By default, check the seal validity (Proof-of-Work on Ethereum 1.x mainnet) of all headers. This can be expensive. Instead, check a random sample of seals using seal_check_random_sample_rate. """ all_indices = range(len(descendants)) if seal_check_random_sample_rate == 1: indices_to_check_seal = set(all_indices) else: sample_size = len(all_indices) // seal_check_random_sample_rate indices_to_check_seal = set(random.sample(all_indices, sample_size)) header_pairs = sliding_window(2, concatv([root], descendants)) for index, (parent, child) in enumerate(header_pairs): if child.parent_hash != parent.hash: raise ValidationError( "Invalid header chain; {} has parent {}, but expected {}". format(child, child.parent_hash, parent.hash)) should_check_seal = index in indices_to_check_seal vm_class = cls.get_vm_class_for_block_number(child.block_number) vm_class.validate_header(child, parent, check_seal=should_check_seal)
def validate_chain( self, root: BlockHeaderAPI, descendants: Tuple[BlockHeaderAPI, ...], seal_check_random_sample_rate: int = 1) -> None: all_indices = range(len(descendants)) if seal_check_random_sample_rate == 1: indices_to_check_seal = set(all_indices) elif seal_check_random_sample_rate == 0: indices_to_check_seal = set() else: sample_size = len(all_indices) // seal_check_random_sample_rate indices_to_check_seal = set(random.sample(all_indices, sample_size)) header_pairs = sliding_window(2, concatv([root], descendants)) for index, (parent, child) in enumerate(header_pairs): if child.parent_hash != parent.hash: raise ValidationError( f"Invalid header chain; {child} has parent {encode_hex(child.parent_hash)}," f" but expected {encode_hex(parent.hash)}" ) should_check_seal = index in indices_to_check_seal vm = self.get_vm(child) try: vm.validate_header(child, parent) except ValidationError as exc: raise ValidationError( f"{child} is not a valid child of {parent}: {exc}" ) from exc if should_check_seal: vm.validate_seal(child)
def serialize(self, value: TSerializable) -> bytes: self._validate_serializable(value) if not len(value): return b"" pairs = self._get_item_sedes_pairs(value) # slow element_sedes = tuple(sedes for element, sedes in pairs) has_fixed_size_section_length_cache = hasattr( value, "_fixed_size_section_length_cache") if has_fixed_size_section_length_cache: if value._fixed_size_section_length_cache is None: fixed_size_section_length = _compute_fixed_size_section_length( element_sedes) value._fixed_size_section_length_cache = fixed_size_section_length else: fixed_size_section_length = value._fixed_size_section_length_cache else: fixed_size_section_length = _compute_fixed_size_section_length( element_sedes) variable_size_section_parts = tuple( sedes.serialize(item) # slow for item, sedes in pairs if not sedes.is_fixed_sized) if variable_size_section_parts: offsets = tuple( accumulate( operator.add, map(len, variable_size_section_parts[:-1]), fixed_size_section_length, )) else: offsets = () offsets_iter = iter(offsets) fixed_size_section_parts = tuple( sedes.serialize(item) # slow if sedes.is_fixed_sized else encode_offset(next(offsets_iter)) for item, sedes in pairs) try: next(offsets_iter) except StopIteration: pass else: raise DeserializationError( "Did not consume all offsets while decoding value") return b"".join( concatv(fixed_size_section_parts, variable_size_section_parts))
async def _fill_in_gap( self, peer: TChainPeer, pairs: Tuple[Tuple[BlockHeader, ...], ...], gap_index: int) -> Tuple[Tuple[BlockHeader, ...], ...]: """ Fill headers into the specified gap in the middle of the header pairs using supplied peer. Validate the returned segment of headers against the surrounding header pairs. :param peer: to make the request to :param pairs: header pairs with gaps in between :param gap_index: 0-indexed gap number that should be filled in :return: segments just like the pairs input, but with one long segment that was filled in For example, if four pairs were input, and the gap_index set to 1, then the returned value would have three segments, like: :: segment 0: (parent, child) --formerly gap 0-- segment 1: (parent, child, ... all headers between ..., parent, child) --formerly gap 2-- segment 2: (parent, child) """ # validate gap value if not (0 <= gap_index < len(pairs) - 1): raise ValidationError( f"Tried to fill gap #{gap_index} in skeleton, with only {len(pairs) - 1} gaps" ) # find the headers just before and after the gap gap_parent = pairs[gap_index][-1] gap_child = pairs[gap_index + 1][0] # request the gap's headers from the skeleton peer start_num = gap_parent.block_number + 1 max_headers = gap_child.block_number - gap_parent.block_number - 1 gap_headers = await self._fetch_headers_from(peer, start_num, max_headers, skip=0) if len(gap_headers) == 0: self.logger.warning( "Skeleton %s could not fill header gap with headers at %s", peer, start_num, ) raise ValidationError( f"Skeleton {peer} could not return headers at {start_num}") # validate the filled headers filled_gap_children = tuple(concatv(gap_headers, pairs[gap_index + 1])) try: await self.wait( self._chain.coro_validate_chain( gap_parent, filled_gap_children, SEAL_CHECK_RANDOM_SAMPLE_RATE, )) except ValidationError: self.logger.warning( "%s returned an invalid gap for index %s, with pairs %s, filler %s", peer, gap_index, pairs, gap_headers, ) raise else: return tuple( concatv( # include all the leading pairs, through the pair that marks the start of the gap pairs[:gap_index + 1], # include the gap that has been filled in, which includes the pair after the gap # must convert to tuple of tuple of headers to match the other types ( filled_gap_children, ), # skip the pair following the gap, include all the following pairs pairs[gap_index + 2:], ))