Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit d458644

Browse files
committed
PR feedback
1 parent 8f7cb5f commit d458644

File tree

8 files changed

+116
-123
lines changed

8 files changed

+116
-123
lines changed

eth/abc.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
Type,
1919
TypeVar,
2020
Union,
21-
NamedTuple)
21+
NamedTuple,
22+
)
2223
from uuid import UUID
2324

2425
import rlp
@@ -38,10 +39,11 @@
3839
from eth.constants import (
3940
BLANK_ROOT_HASH,
4041
)
42+
4143
from eth.exceptions import VMError
4244
from eth.typing import (
43-
BlockRange,
4445
BytesOrView,
46+
ChainGaps,
4547
JournalDBCheckpoint,
4648
AccountState,
4749
HeaderParams,
@@ -494,15 +496,16 @@ def __init__(self, db: AtomicDatabaseAPI) -> None:
494496
...
495497

496498
@abstractmethod
497-
def get_header_chain_gaps(self) -> Tuple[BlockRange, ...]:
499+
def get_header_chain_gaps(self) -> ChainGaps:
498500
"""
499-
Return an ordered sequence of block ranges describing the integrity of the chain of
500-
headers. Each block range describes a missing segment in the chain and each range is defined
501-
with inclusive boundaries, meaning the first value describes the first missing block of that
502-
segment and the second value describes the last missing block of the segment.
501+
Return information about gaps in the chain of headers. This consists of an ordered sequence
502+
of block ranges describing the integrity of the chain. Each block range describes a missing
503+
segment in the chain and each range is defined with inclusive boundaries, meaning the first
504+
value describes the first missing block of that segment and the second value describes the
505+
last missing block of the segment.
503506
504-
The last block range in the sequence is expected to have a block number of `-1` as the
505-
right-hand value which is to say the gap is open-ended.
507+
In addition to the sequences of block ranges a block number is included that indicates the
508+
number of the first header that is known to be missing at the very tip of the chain.
506509
"""
507510

508511
#

eth/db/chain_gaps.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,50 @@
44
from eth_typing import BlockNumber
55

66
from eth.exceptions import GapTrackingCorrupted
7-
from eth.typing import BlockRange
7+
from eth.typing import BlockRange, ChainGaps
88

99

1010
class GapChange(enum.Enum):
11-
1211
NoChange = enum.auto()
1312
NewGap = enum.auto()
13+
GapFill = enum.auto()
1414
GapSplit = enum.auto()
1515
GapShrink = enum.auto()
1616
TailWrite = enum.auto()
1717

1818

19-
GapInfo = Tuple[GapChange, Tuple[BlockRange, ...]]
19+
GAP_WRITES = (GapChange.GapFill, GapChange.GapSplit, GapChange.GapShrink)
20+
GENESIS_CHAIN_GAPS = ((), BlockNumber(1))
21+
22+
GapInfo = Tuple[GapChange, ChainGaps]
2023

2124

22-
def calculate_gaps(newly_persisted: BlockNumber, base_gaps: Tuple[BlockRange, ...]) -> GapInfo:
25+
def calculate_gaps(newly_persisted: BlockNumber, base_gaps: ChainGaps) -> GapInfo:
2326

24-
# If we have a fresh chain, our highest missing number can only be 1
25-
highest_missing_number = 1 if base_gaps == () else base_gaps[-1][0]
27+
current_gaps, known_missing_tip = base_gaps
2628

27-
if newly_persisted == highest_missing_number:
29+
if newly_persisted == known_missing_tip:
2830
# This is adding a consecutive header at the very tail
29-
new_last_marker = (newly_persisted + 1, -1)
30-
new_gaps = base_gaps[:-1] + (new_last_marker,)
31+
new_gaps = (current_gaps, BlockNumber(newly_persisted + 1))
3132
gap_change = GapChange.TailWrite
32-
elif newly_persisted > highest_missing_number:
33+
elif newly_persisted > known_missing_tip:
3334
# We are creating a gap in the chain
34-
gap_end = newly_persisted - 1
35-
new_tail = ((highest_missing_number, gap_end), (newly_persisted + 1, -1),)
36-
new_gaps = base_gaps[:-1] + new_tail
35+
gap_end = BlockNumber(newly_persisted - 1)
36+
new_gaps = (
37+
current_gaps + ((known_missing_tip, gap_end),), BlockNumber(newly_persisted + 1)
38+
)
3739
gap_change = GapChange.NewGap
38-
elif newly_persisted < highest_missing_number:
40+
elif newly_persisted < known_missing_tip:
3941
# We are patching a gap which may either shrink an existing gap or divide it
4042
matching_gaps = [
41-
(index, pair) for index, pair in enumerate(base_gaps)
43+
(index, pair) for index, pair in enumerate(current_gaps)
4244
if newly_persisted >= pair[0] and newly_persisted <= pair[1]
4345
]
4446

4547
if len(matching_gaps) > 1:
4648
raise GapTrackingCorrupted(
4749
"Corrupted chain gap tracking",
48-
f"No {newly_persisted} appears to be missing in multiple gaps",
50+
f"No. {newly_persisted} appears to be missing in multiple gaps",
4951
f"1st gap goes from {matching_gaps[0][1][0]} to {matching_gaps[0][1][1]}"
5052
f"2nd gap goes from {matching_gaps[1][1][0]} to {matching_gaps[1][1][1]}"
5153
)
@@ -55,26 +57,28 @@ def calculate_gaps(newly_persisted: BlockNumber, base_gaps: Tuple[BlockRange, ..
5557
elif len(matching_gaps) == 1:
5658
gap_index, gap = matching_gaps[0]
5759
if newly_persisted == gap[0] and newly_persisted == gap[1]:
58-
updated_center: Tuple[Tuple[int, int], ...] = ()
59-
gap_change = GapChange.GapShrink
60+
updated_center: Tuple[BlockRange, ...] = ()
61+
gap_change = GapChange.GapFill
6062
elif newly_persisted == gap[0]:
6163
# we are shrinking the gap at the start
62-
updated_center = ((gap[0] + 1, gap[1],),)
64+
updated_center = ((BlockNumber(gap[0] + 1), gap[1],),)
6365
gap_change = GapChange.GapShrink
6466
elif newly_persisted == gap[1]:
6567
# we are shrinking the gap at the tail
66-
updated_center = ((gap[0], gap[1] - 1,),)
68+
updated_center = ((gap[0], BlockNumber(gap[1] - 1),),)
6769
gap_change = GapChange.GapShrink
68-
else:
70+
elif gap[0] < newly_persisted < gap[1]:
6971
# we are dividing the gap
70-
first_new_gap = (gap[0], newly_persisted - 1)
71-
second_new_gap = (newly_persisted + 1, gap[1])
72+
first_new_gap = (gap[0], BlockNumber(newly_persisted - 1))
73+
second_new_gap = (BlockNumber(newly_persisted + 1), gap[1])
7274
updated_center = (first_new_gap, second_new_gap,)
7375
gap_change = GapChange.GapSplit
76+
else:
77+
raise Exception("Invariant")
7478

75-
before_gap = base_gaps[:gap_index]
76-
after_gap = base_gaps[gap_index + 1:]
77-
new_gaps = before_gap + updated_center + after_gap
79+
before_gap = current_gaps[:gap_index]
80+
after_gap = current_gaps[gap_index + 1:]
81+
new_gaps = (before_gap + updated_center + after_gap, known_missing_tip)
7882

7983
else:
8084
raise Exception("Invariant")

eth/db/header.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
calculate_gaps,
3434
GapChange,
3535
GapInfo,
36+
GAP_WRITES,
37+
GENESIS_CHAIN_GAPS,
3638
)
3739
from eth.exceptions import (
3840
CanonicalHeadNotFound,
@@ -41,11 +43,8 @@
4143
)
4244
from eth.db.schema import SchemaV1
4345
from eth.rlp.headers import BlockHeader
44-
from eth.rlp.chain_gaps import (
45-
decode_chain_gaps,
46-
encode_chain_gaps,
47-
)
48-
from eth.typing import BlockRange
46+
from eth.rlp.sedes import chain_gaps
47+
from eth.typing import ChainGaps
4948
from eth.validation import (
5049
validate_block_number,
5150
validate_word,
@@ -56,24 +55,24 @@ class HeaderDB(HeaderDatabaseAPI):
5655
def __init__(self, db: AtomicDatabaseAPI) -> None:
5756
self.db = db
5857

59-
def get_header_chain_gaps(self) -> Tuple[BlockRange, ...]:
58+
def get_header_chain_gaps(self) -> ChainGaps:
6059
return self._get_header_chain_gaps(self.db)
6160

6261
@classmethod
63-
def _get_header_chain_gaps(cls, db: DatabaseAPI) -> Tuple[BlockRange, ...]:
62+
def _get_header_chain_gaps(cls, db: DatabaseAPI) -> ChainGaps:
6463
try:
6564
encoded_gaps = db[SchemaV1.make_header_chain_gaps_lookup_key()]
6665
except KeyError:
67-
return ()
66+
return GENESIS_CHAIN_GAPS
6867
else:
69-
return decode_chain_gaps(encoded_gaps)
68+
return rlp.decode(encoded_gaps, sedes=chain_gaps)
7069

7170
@classmethod
7271
def _update_header_chain_gaps(
7372
cls,
7473
db: DatabaseAPI,
7574
persisted_header: BlockHeaderAPI,
76-
base_gaps: Tuple[BlockRange, ...] = None) -> GapInfo:
75+
base_gaps: ChainGaps = None) -> GapInfo:
7776

7877
# If we make many updates in a row, we can avoid reloading the integrity info by
7978
# continuously caching it and providing it as a parameter to this API
@@ -85,7 +84,7 @@ def _update_header_chain_gaps(
8584
if gap_change is not GapChange.NoChange:
8685
db.set(
8786
SchemaV1.make_header_chain_gaps_lookup_key(),
88-
encode_chain_gaps(gaps)
87+
rlp.encode(gaps, sedes=chain_gaps)
8988
)
9089

9190
return gap_change, gaps
@@ -257,7 +256,14 @@ def _persist_header_chain(
257256
)
258257
score = cls._set_hash_scores_to_db(db, curr_chain_head, score)
259258
gap_change, gaps = cls._update_header_chain_gaps(db, curr_chain_head)
260-
if gap_change is GapChange.GapShrink or gap_change is GapChange.GapSplit:
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.
261267
cls._add_block_number_to_hash_lookup(db, curr_chain_head)
262268

263269
orig_headers_seq = concat([(first_header,), headers_iterator])
@@ -277,7 +283,7 @@ def _persist_header_chain(
277283

278284
score = cls._set_hash_scores_to_db(db, curr_chain_head, score)
279285
gap_change, gaps = cls._update_header_chain_gaps(db, curr_chain_head, gaps)
280-
if gap_change is GapChange.GapShrink or gap_change is GapChange.GapSplit:
286+
if gap_change in GAP_WRITES:
281287
cls._add_block_number_to_hash_lookup(db, curr_chain_head)
282288
try:
283289
previous_canonical_head = cls._get_canonical_head_hash(db)

eth/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class CanonicalHeadNotFound(PyEVMError):
6767

6868
class GapTrackingCorrupted(PyEVMError):
6969
"""
70-
Raised when the tracking of chain gaps appears to be corrupted
70+
Raised when the tracking of chain gaps appears to be corrupted (e.g. overlapping gaps)
7171
"""
7272
pass
7373

eth/rlp/chain_gaps.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

eth/rlp/sedes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import rlp
12
from rlp.sedes import (
23
BigEndianInt,
34
Binary,
@@ -9,3 +10,7 @@
910
uint32 = BigEndianInt(32)
1011
uint256 = BigEndianInt(256)
1112
trie_root = Binary.fixed_length(32, allow_empty=True)
13+
chain_gaps = rlp.sedes.List((
14+
rlp.sedes.CountableList(rlp.sedes.List((uint32, uint32))),
15+
uint32,
16+
))

eth/typing.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@
4141

4242
AccountDiff = Iterable[Tuple[Address, str, Union[int, bytes], Union[int, bytes]]]
4343

44-
BlockRange = Tuple[int, int]
44+
BlockRange = Tuple[BlockNumber, BlockNumber]
45+
46+
ChainGaps = Tuple[
47+
# series of gaps before the chain head
48+
Tuple[BlockRange, ...],
49+
# the first missing block number at the tip of the chain
50+
BlockNumber,
51+
]
4552

4653
GeneralState = Union[
4754
AccountState,

0 commit comments

Comments
 (0)