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

Commit bfdcf12

Browse files
committed
Improve performance when extending the chain head
Also make an unrelated change which makes circleci happy, it uses an updated version of eth-utils.
1 parent e10fb92 commit bfdcf12

File tree

4 files changed

+61
-46
lines changed

4 files changed

+61
-46
lines changed

eth/db/chain.py

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,11 @@ def get_block_uncles(self, uncles_hash: Hash32) -> Tuple[BlockHeaderAPI, ...]:
9090
return tuple(rlp.decode(encoded_uncles, sedes=rlp.sedes.CountableList(BlockHeader)))
9191

9292
@classmethod
93-
def _set_as_canonical_chain_head(
94-
cls,
95-
db: DatabaseAPI,
96-
block_hash: Hash32,
97-
genesis_parent_hash: Hash32,
98-
) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]:
99-
try:
100-
header = cls._get_block_header_by_hash(db, block_hash)
101-
except HeaderNotFound:
102-
raise ValueError(
103-
f"Cannot use unknown block hash as canonical head: {header.hash}"
104-
)
105-
106-
new_canonical_headers = tuple(reversed(
107-
cls._find_new_ancestors(db, header, genesis_parent_hash)))
93+
def _decanonicalize_old_headers(
94+
cls,
95+
db: DatabaseAPI,
96+
new_canonical_headers: Tuple[BlockHeaderAPI, ...]
97+
) -> Tuple[BlockHeaderAPI, ...]:
10898
old_canonical_headers = []
10999

110100
# remove transaction lookups for blocks that are no longer canonical
@@ -118,19 +108,16 @@ def _set_as_canonical_chain_head(
118108
old_header = cls._get_block_header_by_hash(db, old_hash)
119109
old_canonical_headers.append(old_header)
120110
try:
121-
for transaction_hash in cls._get_block_transaction_hashes(db, old_header):
111+
transaction_hashes = cls._get_block_transaction_hashes(db, old_header)
112+
for transaction_hash in transaction_hashes:
122113
cls._remove_transaction_from_canonical_chain(db, transaction_hash)
123114
except MissingTrieNode:
124-
# If the transactions were never stored for the (now) non-canonical chain,
125-
# then you don't need to remove them from the canonical chain lookup.
115+
# If the transactions were never stored for the (now) non-canonical
116+
# chain, then you don't need to remove them from the canonical chain
117+
# lookup.
126118
pass
127119

128-
for h in new_canonical_headers:
129-
cls._add_block_number_to_hash_lookup(db, h)
130-
131-
db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash)
132-
133-
return new_canonical_headers, tuple(old_canonical_headers)
120+
return tuple(old_canonical_headers)
134121

135122
#
136123
# Block API

eth/db/header.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import functools
2-
from typing import Iterable, Tuple
2+
from typing import cast, Iterable, Tuple
33

44
import rlp
55

@@ -83,11 +83,15 @@ def get_canonical_head(self) -> BlockHeaderAPI:
8383

8484
@classmethod
8585
def _get_canonical_head(cls, db: DatabaseAPI) -> BlockHeaderAPI:
86+
canonical_head_hash = cls._get_canonical_head_hash(db)
87+
return cls._get_block_header_by_hash(db, canonical_head_hash)
88+
89+
@classmethod
90+
def _get_canonical_head_hash(cls, db: DatabaseAPI) -> Hash32:
8691
try:
87-
canonical_head_hash = db[SchemaV1.make_canonical_head_hash_lookup_key()]
92+
return Hash32(db[SchemaV1.make_canonical_head_hash_lookup_key()])
8893
except KeyError:
8994
raise CanonicalHeadNotFound("No canonical head set for this chain")
90-
return cls._get_block_header_by_hash(db, Hash32(canonical_head_hash))
9195

9296
#
9397
# Header API
@@ -173,7 +177,7 @@ def _persist_checkpoint_header(
173177
)
174178
previous_score = score - header.difficulty
175179
cls._set_hash_scores_to_db(db, header, previous_score)
176-
cls._set_as_canonical_chain_head(db, header.hash, header.parent_hash)
180+
cls._set_as_canonical_chain_head(db, header, header.parent_hash)
177181

178182
@classmethod
179183
def _persist_header_chain(
@@ -226,21 +230,21 @@ def _persist_header_chain(
226230
score = cls._set_hash_scores_to_db(db, curr_chain_head, score)
227231

228232
try:
229-
previous_canonical_head = cls._get_canonical_head(db).hash
233+
previous_canonical_head = cls._get_canonical_head_hash(db)
230234
head_score = cls._get_score(db, previous_canonical_head)
231235
except CanonicalHeadNotFound:
232-
return cls._set_as_canonical_chain_head(db, curr_chain_head.hash, genesis_parent_hash)
236+
return cls._set_as_canonical_chain_head(db, curr_chain_head, genesis_parent_hash)
233237

234238
if score > head_score:
235-
return cls._set_as_canonical_chain_head(db, curr_chain_head.hash, genesis_parent_hash)
239+
return cls._set_as_canonical_chain_head(db, curr_chain_head, genesis_parent_hash)
236240

237241
return tuple(), tuple()
238242

239243
@classmethod
240244
def _set_as_canonical_chain_head(
241245
cls,
242246
db: DatabaseAPI,
243-
block_hash: Hash32,
247+
header: BlockHeaderAPI,
244248
genesis_parent_hash: Hash32,
245249
) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]:
246250
"""
@@ -251,14 +255,41 @@ def _set_as_canonical_chain_head(
251255
are no longer in the canonical chain
252256
"""
253257
try:
254-
header = cls._get_block_header_by_hash(db, block_hash)
255-
except HeaderNotFound:
256-
raise ValueError(
257-
f"Cannot use unknown block hash as canonical head: {block_hash}"
258+
current_canonical_head = cls._get_canonical_head_hash(db)
259+
except CanonicalHeadNotFound:
260+
current_canonical_head = None
261+
262+
new_canonical_headers: Tuple[BlockHeaderAPI, ...]
263+
old_canonical_headers: Tuple[BlockHeaderAPI, ...]
264+
265+
if current_canonical_head and header.parent_hash == current_canonical_head:
266+
# the calls to _find_new_ancestors and _decanonicalize_old_headers are
267+
# relatively expensive, it's better to skip them in this case, where we're
268+
# extending the canonical chain by a header
269+
new_canonical_headers = (header,)
270+
old_canonical_headers = ()
271+
else:
272+
new_canonical_headers = cast(
273+
Tuple[BlockHeaderAPI, ...],
274+
tuple(reversed(cls._find_new_ancestors(db, header, genesis_parent_hash)))
275+
)
276+
old_canonical_headers = cls._decanonicalize_old_headers(
277+
db, new_canonical_headers
258278
)
259279

260-
new_canonical_headers = tuple(reversed(
261-
cls._find_new_ancestors(db, header, genesis_parent_hash)))
280+
for h in new_canonical_headers:
281+
cls._add_block_number_to_hash_lookup(db, h)
282+
283+
db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash)
284+
285+
return new_canonical_headers, old_canonical_headers
286+
287+
@classmethod
288+
def _decanonicalize_old_headers(
289+
cls,
290+
db: DatabaseAPI,
291+
new_canonical_headers: Tuple[BlockHeaderAPI, ...]
292+
) -> Tuple[BlockHeaderAPI, ...]:
262293
old_canonical_headers = []
263294

264295
for h in new_canonical_headers:
@@ -271,12 +302,7 @@ def _set_as_canonical_chain_head(
271302
old_canonical_header = cls._get_block_header_by_hash(db, old_canonical_hash)
272303
old_canonical_headers.append(old_canonical_header)
273304

274-
for h in new_canonical_headers:
275-
cls._add_block_number_to_hash_lookup(db, h)
276-
277-
db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash)
278-
279-
return new_canonical_headers, tuple(old_canonical_headers)
305+
return tuple(old_canonical_headers)
280306

281307
@classmethod
282308
@to_tuple

eth/tools/_utils/normalization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def normalize_to_address(value: AnyStr) -> Address:
128128
return CREATE_CONTRACT_ADDRESS
129129

130130

131-
robust_decode_hex = eth_utils.curried.hexstr_if_str(to_bytes) # type: ignore # https://github.com/ethereum/eth-utils/issues/156 # noqa: E501
131+
robust_decode_hex = eth_utils.curried.hexstr_if_str(to_bytes)
132132

133133

134134
#
@@ -247,7 +247,7 @@ def state_definition_to_dict(state_definition: GeneralState) -> AccountState:
247247
"nonce": normalize_int,
248248
"storage": normalize_storage
249249
}, required=[])),
250-
eth_utils.curried.apply_formatter_if( # type: ignore # https://github.com/ethereum/eth-utils/issues/156 # noqa: E501
250+
eth_utils.curried.apply_formatter_if(
251251
lambda s: isinstance(s, Iterable) and not isinstance(s, Mapping),
252252
state_definition_to_dict
253253
),

newsfragments/1891.performance.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve performance when importing a header which is a child of the current canonical
2+
chain tip.

0 commit comments

Comments
 (0)