@@ -256,15 +256,7 @@ def _persist_header_chain(
256
256
)
257
257
score = cls ._set_hash_scores_to_db (db , curr_chain_head , score )
258
258
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 )
268
260
269
261
orig_headers_seq = concat ([(first_header ,), headers_iterator ])
270
262
for parent , child in sliding_window (2 , orig_headers_seq ):
@@ -283,8 +275,7 @@ def _persist_header_chain(
283
275
284
276
score = cls ._set_hash_scores_to_db (db , curr_chain_head , score )
285
277
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 )
288
279
try :
289
280
previous_canonical_head = cls ._get_canonical_head_hash (db )
290
281
head_score = cls ._get_score (db , previous_canonical_head )
@@ -296,6 +287,47 @@ def _persist_header_chain(
296
287
297
288
return tuple (), tuple ()
298
289
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
+
299
331
@classmethod
300
332
def _set_as_canonical_chain_head (
301
333
cls ,
0 commit comments