Skip to content

Commit 7508476

Browse files
eddybenkhinpipermerriam
authored andcommitted
WIP changes chain.import_block to return new blocks and blocks evicte… (#1151)
* WIP changes chain.import_block to return new blocks and blocks evicted issue #772 * added test for import_block
1 parent 3593d42 commit 7508476

File tree

11 files changed

+219
-43
lines changed

11 files changed

+219
-43
lines changed

eth/chains/base.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import random
99
from typing import ( # noqa: F401
1010
Any,
11-
Optional,
1211
Callable,
1312
cast,
1413
Dict,
1514
Generator,
1615
Iterator,
16+
List,
17+
Optional,
1718
Tuple,
1819
Type,
1920
TYPE_CHECKING,
@@ -265,7 +266,10 @@ def estimate_gas(
265266
raise NotImplementedError("Chain classes must implement this method")
266267

267268
@abstractmethod
268-
def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseBlock:
269+
def import_block(self,
270+
block: BaseBlock,
271+
perform_validation: bool=True,
272+
) -> Tuple[Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]:
269273
raise NotImplementedError("Chain classes must implement this method")
270274

271275
#
@@ -584,10 +588,14 @@ def estimate_gas(
584588
with self.get_vm(at_header).state_in_temp_block() as state:
585589
return self.gas_estimator(state, transaction)
586590

587-
def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseBlock:
591+
def import_block(self,
592+
block: BaseBlock,
593+
perform_validation: bool=True
594+
) -> Tuple[Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]:
588595
"""
589-
Imports a complete block.
596+
Imports a complete block and returns new_blocks and orphaned_canonical_blocks.
590597
"""
598+
591599
try:
592600
parent_header = self.get_block_header_by_hash(block.header.parent_hash)
593601
except HeaderNotFound:
@@ -608,13 +616,26 @@ def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseB
608616
ensure_imported_block_unchanged(imported_block, block)
609617
self.validate_block(imported_block)
610618

611-
self.chaindb.persist_block(imported_block)
619+
(
620+
new_canonical_header_hashes,
621+
orphaned_header_hashes,
622+
) = self.chaindb.persist_block(imported_block)
623+
612624
self.logger.debug(
613625
'IMPORTED_BLOCK: number %s | hash %s',
614626
imported_block.number,
615627
encode_hex(imported_block.hash),
616628
)
617-
return imported_block
629+
630+
new_blocks = tuple(
631+
self.get_block_by_hash(header_hash) for header_hash in new_canonical_header_hashes)
632+
orphaned_canonical_blocks = tuple(
633+
self.get_block_by_hash(header_hash) for header_hash in orphaned_header_hashes)
634+
# add imported block to new block when no chain re-org
635+
if not new_blocks:
636+
new_blocks = (imported_block, )
637+
638+
return new_blocks, orphaned_canonical_blocks
618639

619640
#
620641
# Validation API
@@ -780,10 +801,15 @@ def apply_transaction(self, transaction):
780801

781802
return new_block, receipt, computation
782803

783-
def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseBlock:
784-
result_block = super().import_block(block, perform_validation)
804+
def import_block(self,
805+
block: BaseBlock,
806+
perform_validation: bool=True
807+
) -> Tuple[Tuple[BaseBlock, ...], Tuple[BaseBlock, ...]]:
808+
new_canonical_blocks, orphaned_canonical_blocks = super().import_block(
809+
block, perform_validation)
810+
785811
self.header = self.ensure_header()
786-
return result_block
812+
return new_canonical_blocks, orphaned_canonical_blocks
787813

788814
def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock:
789815
"""

eth/db/chain.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ def get_block_uncles(self, uncles_hash: Hash32) -> List[BlockHeader]:
9494
# Block API
9595
#
9696
@abstractmethod
97-
def persist_block(self, block: 'BaseBlock') -> None:
97+
def persist_block(self,
98+
block: 'BaseBlock'
99+
) -> Tuple[Tuple[bytes, ...], Tuple[bytes, ...]]:
98100
raise NotImplementedError("ChainDB classes must implement this method")
99101

100102
@abstractmethod
@@ -186,7 +188,9 @@ def get_block_uncles(self, uncles_hash: Hash32) -> List[BlockHeader]:
186188

187189
# TODO: This method should take a chain of headers as that's the most common use case
188190
# and it'd be much faster than inserting each header individually.
189-
def persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
191+
def persist_header(self,
192+
header: BlockHeader
193+
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
190194
"""
191195
Returns iterable of headers newly on the canonical chain
192196
"""
@@ -214,17 +218,26 @@ def persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
214218
try:
215219
head_score = self.get_score(self.get_canonical_head().hash)
216220
except CanonicalHeadNotFound:
217-
new_headers = self._set_as_canonical_chain_head(header)
221+
(
222+
new_canonical_headers,
223+
orphaned_canonical_headers
224+
) = self._set_as_canonical_chain_head(header)
218225
else:
219226
if score > head_score:
220-
new_headers = self._set_as_canonical_chain_head(header)
227+
(
228+
new_canonical_headers,
229+
orphaned_canonical_headers
230+
) = self._set_as_canonical_chain_head(header)
221231
else:
222-
new_headers = tuple()
232+
new_canonical_headers = tuple()
233+
orphaned_canonical_headers = tuple()
223234

224-
return new_headers
235+
return new_canonical_headers, orphaned_canonical_headers
225236

226237
# TODO: update this to take a `hash` rather than a full header object.
227-
def _set_as_canonical_chain_head(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
238+
def _set_as_canonical_chain_head(self,
239+
header: BlockHeader
240+
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
228241
"""
229242
Returns iterable of headers newly on the canonical head
230243
"""
@@ -235,7 +248,7 @@ def _set_as_canonical_chain_head(self, header: BlockHeader) -> Tuple[BlockHeader
235248
header.hash))
236249

237250
new_canonical_headers = tuple(reversed(self._find_new_ancestors(header)))
238-
251+
orphaned_canonical_headers = []
239252
# remove transaction lookups for blocks that are no longer canonical
240253
for h in new_canonical_headers:
241254
try:
@@ -244,29 +257,30 @@ def _set_as_canonical_chain_head(self, header: BlockHeader) -> Tuple[BlockHeader
244257
# no old block, and no more possible
245258
break
246259
else:
247-
old_header = self.get_block_header_by_hash(old_hash)
248-
for transaction_hash in self.get_block_transaction_hashes(old_header):
260+
orphaned_header = self.get_block_header_by_hash(old_hash)
261+
orphaned_canonical_headers.append(orphaned_header)
262+
for transaction_hash in self.get_block_transaction_hashes(orphaned_header):
249263
self._remove_transaction_from_canonical_chain(transaction_hash)
250-
# TODO re-add txn to internal pending pool (only if local sender)
251-
pass
252264

253265
for h in new_canonical_headers:
254266
self._add_block_number_to_hash_lookup(h)
255267

256268
self.db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash)
257269

258-
return new_canonical_headers
270+
return new_canonical_headers, tuple(orphaned_canonical_headers)
259271

260272
#
261273
# Block API
262274
#
263-
def persist_block(self, block: 'BaseBlock') -> None:
275+
def persist_block(self,
276+
block: 'BaseBlock'
277+
) -> Tuple[Tuple[bytes, ...], Tuple[bytes, ...]]:
264278
'''
265279
Persist the given block's header and uncles.
266280
267281
Assumes all block transactions have been persisted already.
268282
'''
269-
new_canonical_headers = self.persist_header(block.header)
283+
new_canonical_headers, orphaned_canonical_headers = self.persist_header(block.header)
270284

271285
for header in new_canonical_headers:
272286
if header.hash == block.hash:
@@ -288,6 +302,11 @@ def persist_block(self, block: 'BaseBlock') -> None:
288302
raise ValidationError(
289303
"Block's uncles_hash (%s) does not match actual uncles' hash (%s)",
290304
block.header.uncles_hash, uncles_hash)
305+
new_canonical_header_hashes = tuple(header.hash for header in new_canonical_headers)
306+
orphaned_canonical_header_hashes = tuple(
307+
header.hash for header in orphaned_canonical_headers)
308+
309+
return new_canonical_header_hashes, orphaned_canonical_header_hashes
291310

292311
def persist_uncles(self, uncles: Tuple[BlockHeader]) -> Hash32:
293312
"""

eth/db/header.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def header_exists(self, block_hash: Hash32) -> bool:
6868
raise NotImplementedError("ChainDB classes must implement this method")
6969

7070
@abstractmethod
71-
def persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
71+
def persist_header(self,
72+
header: BlockHeader
73+
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
7274
raise NotImplementedError("ChainDB classes must implement this method")
7375

7476

@@ -144,7 +146,9 @@ def header_exists(self, block_hash: Hash32) -> bool:
144146
validate_word(block_hash, title="Block Hash")
145147
return block_hash in self.db
146148

147-
def persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
149+
def persist_header(self,
150+
header: BlockHeader
151+
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
148152
"""
149153
:returns: iterable of headers newly on the canonical chain
150154
"""
@@ -174,16 +178,18 @@ def persist_header(self, header: BlockHeader) -> Tuple[BlockHeader, ...]:
174178
try:
175179
head_score = self.get_score(self.get_canonical_head().hash)
176180
except CanonicalHeadNotFound:
177-
new_canonical_headers = self._set_as_canonical_chain_head(header.hash)
181+
new_canonical_headers, old_headers = self._set_as_canonical_chain_head(header.hash)
178182
else:
179183
if score > head_score:
180-
new_canonical_headers = self._set_as_canonical_chain_head(header.hash)
184+
new_canonical_headers, old_headers = self._set_as_canonical_chain_head(header.hash)
181185
else:
182186
new_canonical_headers = tuple()
187+
old_headers = tuple()
183188

184-
return new_canonical_headers
189+
return new_canonical_headers, old_headers
185190

186-
def _set_as_canonical_chain_head(self, block_hash: Hash32) -> Tuple[BlockHeader, ...]:
191+
def _set_as_canonical_chain_head(self, block_hash: Hash32
192+
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
187193
"""
188194
Sets the canonical chain HEAD to the block header as specified by the
189195
given block hash.
@@ -198,13 +204,24 @@ def _set_as_canonical_chain_head(self, block_hash: Hash32) -> Tuple[BlockHeader,
198204
)
199205

200206
new_canonical_headers = tuple(reversed(self._find_new_ancestors(header)))
207+
orphaned_headers = []
208+
209+
for h in new_canonical_headers:
210+
try:
211+
orphaned_hash = self.get_canonical_block_hash(h.block_number)
212+
except HeaderNotFound:
213+
# no orphaned block, and no more possible
214+
break
215+
else:
216+
orphaned_header = self.get_block_header_by_hash(orphaned_hash)
217+
orphaned_headers.append(orphaned_header)
201218

202219
for h in new_canonical_headers:
203220
self._add_block_number_to_hash_lookup(h)
204221

205222
self.db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash)
206223

207-
return new_canonical_headers
224+
return new_canonical_headers, tuple(orphaned_headers)
208225

209226
@to_tuple
210227
def _find_new_ancestors(self, header: BlockHeader) -> Iterable[BlockHeader]:

eth/tools/fixture_tests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ def apply_fixture_block_to_chain(block_fixture, chain):
641641

642642
block = rlp.decode(block_fixture['rlp'], sedes=block_class)
643643

644-
mined_block = chain.import_block(block)
644+
imported_blocks, _ = chain.import_block(block)
645+
mined_block = imported_blocks[-1]
645646

646647
rlp_encoded_mined_block = rlp.encode(mined_block, sedes=block_class)
647648

scripts/benchmark/checks/import_empty_blocks.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def import_empty_blocks(self, chain: Chain, number_blocks: int) -> int:
4747

4848
total_gas_used = 0
4949
for _ in range(1, number_blocks + 1):
50-
block = chain.import_block(chain.get_vm().block, False)
50+
imported_blocks, _ = chain.import_block(chain.get_vm().block, False)
51+
block = imported_blocks[-1]
52+
5153
total_gas_used = total_gas_used + block.header.gas_used
5254
logging.debug(format_block(block))
5355

tests/core/chain-object/test_chain.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ def tx(chain, funded_address, funded_address_private_key):
4141
@pytest.mark.xfail(reason="modification to initial allocation made the block fixture invalid")
4242
def test_import_block_validation(valid_chain, funded_address, funded_address_initial_balance):
4343
block = rlp.decode(valid_block_rlp, sedes=FrontierBlock)
44-
imported_block = valid_chain.import_block(block)
44+
imported_blocks, _ = valid_chain.import_block(block)
45+
imported_block = imported_blocks[-1]
46+
4547
assert len(imported_block.transactions) == 1
4648
tx = imported_block.transactions[0]
4749
assert tx.value == 10
@@ -64,7 +66,9 @@ def test_import_block(chain, tx):
6466
new_block, receipts, computations = chain.build_block_with_transactions([tx])
6567
assert computations[0].is_success
6668

67-
block = chain.import_block(new_block)
69+
imported_blocks, _ = chain.import_block(new_block)
70+
block = imported_blocks[-1]
71+
6872
assert block.transactions == (tx,)
6973
assert chain.get_block_by_hash(block.hash) == block
7074
assert chain.get_canonical_block_by_number(block.number) == block

tests/core/chain-object/test_header_chain.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,17 @@ def test_header_chain_import_block(header_chain, genesis_header):
106106
chain_c = mk_header_chain(genesis_header, 5)
107107

108108
for header in chain_a:
109-
res = header_chain.import_header(header)
109+
res, _ = header_chain.import_header(header)
110110
assert res == (header,)
111111
assert_headers_eq(header_chain.header, header)
112112

113113
for header in chain_b:
114-
res = header_chain.import_header(header)
114+
res, _ = header_chain.import_header(header)
115115
assert res == tuple()
116116
assert_headers_eq(header_chain.header, chain_a[-1])
117117

118118
for idx, header in enumerate(chain_c, 1):
119-
res = header_chain.import_header(header)
119+
res, _ = header_chain.import_header(header)
120120
if idx <= 3:
121121
# prior to passing up `chain_a` each import should not return new
122122
# canonical headers.

0 commit comments

Comments
 (0)