1
1
import enum
2
- from typing import Tuple
2
+ from typing import Iterable , Tuple
3
3
4
4
from eth_typing import BlockNumber
5
+ from eth_utils import (
6
+ ValidationError ,
7
+ to_tuple ,
8
+ )
5
9
6
10
from eth .exceptions import GapTrackingCorrupted
7
11
from eth .typing import BlockRange , ChainGaps
@@ -12,17 +16,87 @@ class GapChange(enum.Enum):
12
16
NewGap = enum .auto ()
13
17
GapFill = enum .auto ()
14
18
GapSplit = enum .auto ()
15
- GapShrink = enum .auto ()
19
+ GapLeftShrink = enum .auto ()
20
+ GapRightShrink = enum .auto ()
16
21
TailWrite = enum .auto ()
17
22
18
23
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
+ )
20
30
GENESIS_CHAIN_GAPS = ((), BlockNumber (1 ))
21
31
22
32
GapInfo = Tuple [GapChange , ChainGaps ]
23
33
24
34
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
+ """
26
100
27
101
current_gaps , tip_child = base_gaps
28
102
@@ -45,11 +119,12 @@ def calculate_gaps(newly_persisted: BlockNumber, base_gaps: ChainGaps) -> GapInf
45
119
]
46
120
47
121
if len (matching_gaps ) > 1 :
122
+ first_match , second_match , * _ = matching_gaps
48
123
raise GapTrackingCorrupted (
49
124
"Corrupted chain gap tracking" ,
50
125
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 } " ,
53
128
)
54
129
elif len (matching_gaps ) == 0 :
55
130
# Looks like we are just overwriting an existing header.
@@ -62,11 +137,11 @@ def calculate_gaps(newly_persisted: BlockNumber, base_gaps: ChainGaps) -> GapInf
62
137
elif newly_persisted == gap [0 ]:
63
138
# we are shrinking the gap at the start
64
139
updated_center = ((BlockNumber (gap [0 ] + 1 ), gap [1 ],),)
65
- gap_change = GapChange .GapShrink
140
+ gap_change = GapChange .GapLeftShrink
66
141
elif newly_persisted == gap [1 ]:
67
142
# we are shrinking the gap at the tail
68
143
updated_center = ((gap [0 ], BlockNumber (gap [1 ] - 1 ),),)
69
- gap_change = GapChange .GapShrink
144
+ gap_change = GapChange .GapRightShrink
70
145
elif gap [0 ] < newly_persisted < gap [1 ]:
71
146
# we are dividing the gap
72
147
first_new_gap = (gap [0 ], BlockNumber (newly_persisted - 1 ))
0 commit comments