@@ -256,15 +256,7 @@ def _persist_header_chain(
256256 )
257257 score = cls ._set_hash_scores_to_db (db , curr_chain_head , score )
258258 gap_change , gaps = cls ._update_header_chain_gaps (db , curr_chain_head )
259- if gap_change in GAP_WRITES :
260- # The caller implicitly asserts that persisted headers are canonical here.
261- # This assertion is made when persisting headers that are known to be part of a gap
262- # in the canonical chain. While we do validate that the headers are valid, we can not
263- # be 100 % sure that they will eventually lead to the expected child that fills the gap.
264- # E.g. there is nothing preventing one from persisting an uncle as the final header to
265- # close the gap, even if that will not be the parent of the next consecutive header.
266- # Py-EVM makes the assumption that client code will check and prevent such a scenario.
267- cls ._add_block_number_to_hash_lookup (db , curr_chain_head )
259+ cls ._handle_gap_change (db , gap_change , curr_chain_head , genesis_parent_hash )
268260
269261 orig_headers_seq = concat ([(first_header ,), headers_iterator ])
270262 for parent , child in sliding_window (2 , orig_headers_seq ):
@@ -283,8 +275,7 @@ def _persist_header_chain(
283275
284276 score = cls ._set_hash_scores_to_db (db , curr_chain_head , score )
285277 gap_change , gaps = cls ._update_header_chain_gaps (db , curr_chain_head , gaps )
286- if gap_change in GAP_WRITES :
287- cls ._add_block_number_to_hash_lookup (db , curr_chain_head )
278+ cls ._handle_gap_change (db , gap_change , curr_chain_head , genesis_parent_hash )
288279 try :
289280 previous_canonical_head = cls ._get_canonical_head_hash (db )
290281 head_score = cls ._get_score (db , previous_canonical_head )
@@ -296,6 +287,47 @@ def _persist_header_chain(
296287
297288 return tuple (), tuple ()
298289
290+ @classmethod
291+ def _handle_gap_change (cls ,
292+ db : DatabaseAPI ,
293+ gap_change : GapChange ,
294+ header : BlockHeaderAPI ,
295+ genesis_parent_hash : Hash32 ) -> None :
296+
297+ if gap_change not in GAP_WRITES :
298+ return
299+
300+ if gap_change is GapChange .GapFill :
301+ expected_child = cls ._get_canonical_head (db )
302+ if header .hash != expected_child .parent_hash :
303+ # We are trying to close a gap with an uncle. Reject!
304+ raise ValidationError (f"{ header } is not the parent of { expected_child } " )
305+
306+ # We implicitly assert that persisted headers are canonical here.
307+ # This assertion is made when persisting headers that are known to be part of a gap
308+ # in the canonical chain.
309+ # What we don't know is if this header will eventually lead up to the upper end of the
310+ # gap (the checkpoint header) or if this is an alternative history. But we do ensure the
311+ # integrity eventually. For one, we check the final header that would close the gap and if
312+ # it does not match our expected child (the checkpoint header), we will reject it.
313+ # Additionally, if we have persisted a chain of alternative history under the wrong
314+ # assumption that it would be the canonical chain, but then at a later point it turns out it
315+ # isn't and we overwrite it with the correct canonical chain, we make sure to
316+ # recanonicalize all affected headers.
317+ # IMPORTANT: Not only do we have to take this into account when we fill a gap, but also
318+ # every time we write into a gap.
319+ # Suppose we have a gap of 10 headers, then we fill 1 -5 with uncles from chain B.
320+ # Now suppose we find the original chain A and attempt to write headers 1 - 10. At that
321+ # point, we are under the assumption our current gap spans from just 6 - 10 because we have
322+ # previously written headers 1 - 5 from chain B *believing* they are canonical which they
323+ # turn out not to be. That means by the time we write headers 1 -5 again (but from chain A)
324+ # we do not recognize it as writing to a known gap. Therefore by the time we write header 6,
325+ # when we finally realize that we are attempting to fill in a gap, we have to backtrack
326+ # the ancestors to add them to the canonical chain.
327+
328+ for ancestor in cls ._find_new_ancestors (db , header , genesis_parent_hash ):
329+ cls ._add_block_number_to_hash_lookup (db , ancestor )
330+
299331 @classmethod
300332 def _set_as_canonical_chain_head (
301333 cls ,
0 commit comments