11import enum
2- from typing import Tuple
2+ from typing import Iterable , Tuple
33
44from eth_typing import BlockNumber
5+ from eth_utils import (
6+ ValidationError ,
7+ to_tuple ,
8+ )
59
610from eth .exceptions import GapTrackingCorrupted
711from eth .typing import BlockRange , ChainGaps
@@ -12,17 +16,87 @@ class GapChange(enum.Enum):
1216 NewGap = enum .auto ()
1317 GapFill = enum .auto ()
1418 GapSplit = enum .auto ()
15- GapShrink = enum .auto ()
19+ GapLeftShrink = enum .auto ()
20+ GapRightShrink = enum .auto ()
1621 TailWrite = enum .auto ()
1722
1823
19- GAP_WRITES = (GapChange .GapFill , GapChange .GapSplit , GapChange .GapShrink )
24+ GAP_WRITES = (
25+ GapChange .GapFill ,
26+ GapChange .GapSplit ,
27+ GapChange .GapLeftShrink ,
28+ GapChange .GapRightShrink ,
29+ )
2030GENESIS_CHAIN_GAPS = ((), BlockNumber (1 ))
2131
2232GapInfo = Tuple [GapChange , ChainGaps ]
2333
2434
25- def calculate_gaps (newly_persisted : BlockNumber , base_gaps : ChainGaps ) -> GapInfo :
35+ @to_tuple
36+ def _join_overlapping_gaps (unjoined_gaps : Tuple [BlockRange , ...]) -> Iterable [BlockRange ]:
37+ """
38+ After introducing a new gap, join any that overlap.
39+ Input must already be sorted.
40+ """
41+ unyielded_low = None
42+ unyielded_high = None
43+ for low , high in unjoined_gaps :
44+ if unyielded_high is not None :
45+ if low < unyielded_low :
46+ raise ValidationError (f"Unsorted input! { unjoined_gaps !r} " )
47+ elif unyielded_low <= low <= unyielded_high + 1 :
48+ unyielded_high = max (high , unyielded_high )
49+ continue
50+ else :
51+ yield unyielded_low , unyielded_high
52+
53+ unyielded_low = low
54+ unyielded_high = high
55+
56+ if unyielded_high is not None :
57+ yield unyielded_low , unyielded_high
58+
59+
60+ def reopen_gap (decanonicalized : BlockNumber , base_gaps : ChainGaps ) -> ChainGaps :
61+ """
62+ Add a new gap, for a header that was decanonicalized.
63+ """
64+ current_gaps , tip_child = base_gaps
65+
66+ if tip_child <= decanonicalized :
67+ return base_gaps
68+
69+ new_raw_gaps = current_gaps + ((decanonicalized , decanonicalized ), )
70+
71+ # join overlapping gaps
72+ joined_gaps = _join_overlapping_gaps (sorted (new_raw_gaps ))
73+
74+ # is the last gap overlapping with the tip child? if so, merge it
75+ if joined_gaps [- 1 ][1 ] + 1 >= tip_child :
76+ return joined_gaps [:- 1 ], joined_gaps [- 1 ][0 ]
77+ else :
78+ return joined_gaps , tip_child
79+
80+
81+ def is_block_number_in_gap (block_number : BlockNumber , gaps : ChainGaps ) -> bool :
82+ """
83+ Check if a block number is found in the given gaps
84+ """
85+ gap_ranges , tip_child = gaps
86+ for low , high in gap_ranges :
87+ if low > block_number :
88+ return False
89+ elif high >= block_number :
90+ return True
91+ # this range was below the block number, continue looking at the next range
92+
93+ return block_number >= tip_child
94+
95+
96+ def fill_gap (newly_persisted : BlockNumber , base_gaps : ChainGaps ) -> GapInfo :
97+ """
98+ Remove a gap, for a new header that was canonicalized.
99+ """
26100
27101 current_gaps , tip_child = base_gaps
28102
@@ -45,11 +119,12 @@ def calculate_gaps(newly_persisted: BlockNumber, base_gaps: ChainGaps) -> GapInf
45119 ]
46120
47121 if len (matching_gaps ) > 1 :
122+ first_match , second_match , * _ = matching_gaps
48123 raise GapTrackingCorrupted (
49124 "Corrupted chain gap tracking" ,
50125 f"No. { newly_persisted } appears to be missing in multiple gaps" ,
51- f"1st gap goes from { matching_gaps [ 0 ][ 1 ][ 0 ] } to { matching_gaps [ 0 ][ 1 ][ 1 ] } "
52- f"2nd gap goes from { matching_gaps [ 1 ][ 1 ][ 0 ] } to { matching_gaps [ 1 ][ 1 ][ 1 ] } "
126+ f"1st gap is { first_match [ 1 ] } , 2nd gap is { second_match [ 1 ] } " ,
127+ f"all matching gaps: { matching_gaps } " ,
53128 )
54129 elif len (matching_gaps ) == 0 :
55130 # Looks like we are just overwriting an existing header.
@@ -62,11 +137,11 @@ def calculate_gaps(newly_persisted: BlockNumber, base_gaps: ChainGaps) -> GapInf
62137 elif newly_persisted == gap [0 ]:
63138 # we are shrinking the gap at the start
64139 updated_center = ((BlockNumber (gap [0 ] + 1 ), gap [1 ],),)
65- gap_change = GapChange .GapShrink
140+ gap_change = GapChange .GapLeftShrink
66141 elif newly_persisted == gap [1 ]:
67142 # we are shrinking the gap at the tail
68143 updated_center = ((gap [0 ], BlockNumber (gap [1 ] - 1 ),),)
69- gap_change = GapChange .GapShrink
144+ gap_change = GapChange .GapRightShrink
70145 elif gap [0 ] < newly_persisted < gap [1 ]:
71146 # we are dividing the gap
72147 first_new_gap = (gap [0 ], BlockNumber (newly_persisted - 1 ))
0 commit comments