Skip to content

Commit b688ada

Browse files
onybpipermerriam
authored andcommitted
[WIP] Refactor DB persistence logic to work with iterators (#1701)
* Allow persistence of iterable of chain headers Handle chain validation and setting of scores inside a single loop. * Add missing typing information to the _set_hash_scores_to_db method * Move code to persist the header outside the _set_hash_scores_to_db function * Implement tests for HeaderDB.persist_header_chain to work with iterators * Fix flake8 errors * Allow persistence of iterable of chain blocks
1 parent ee8f72d commit b688ada

File tree

3 files changed

+126
-64
lines changed

3 files changed

+126
-64
lines changed

eth/beacon/db/chain.py

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Type,
88
)
99
from cytoolz import (
10+
concat,
1011
first,
1112
sliding_window,
1213
)
@@ -289,61 +290,79 @@ def persist_block_chain(
289290
with self.db.atomic_batch() as db:
290291
return self._persist_block_chain(db, blocks, self.block_class)
291292

293+
@classmethod
294+
def _set_block_scores_to_db(
295+
cls,
296+
db: BaseDB,
297+
block: BaseBeaconBlock
298+
) -> None:
299+
# TODO: It's a stub before we implement fork choice rule
300+
score = block.slot
301+
302+
db.set(
303+
SchemaV1.make_block_root_to_score_lookup_key(block.root),
304+
rlp.encode(score, sedes=rlp.sedes.big_endian_int),
305+
)
306+
292307
@classmethod
293308
def _persist_block_chain(
294309
cls,
295310
db: BaseDB,
296311
blocks: Iterable[BaseBeaconBlock],
297312
block_class: Type[BaseBeaconBlock]
298313
) -> Tuple[Tuple[BaseBeaconBlock, ...], Tuple[BaseBeaconBlock, ...]]:
314+
blocks_iterator = iter(blocks)
315+
299316
try:
300-
first_block = first(blocks)
317+
first_block = first(blocks_iterator)
301318
except StopIteration:
302319
return tuple(), tuple()
303-
else:
304-
for parent, child in sliding_window(2, blocks):
305-
if parent.root != child.parent_root:
306-
raise ValidationError(
307-
"Non-contiguous chain. Expected {} to have {} as parent but was {}".format(
308-
encode_hex(child.root),
309-
encode_hex(parent.root),
310-
encode_hex(child.parent_root),
311-
)
312-
)
313320

314-
is_genesis = first_block.parent_root == GENESIS_PARENT_HASH
315-
if not is_genesis and not cls._block_exists(db, first_block.parent_root):
316-
raise ParentNotFound(
317-
"Cannot persist block ({}) with unknown parent ({})".format(
318-
encode_hex(first_block.root), encode_hex(first_block.parent_root)))
321+
is_genesis = first_block.parent_root == GENESIS_PARENT_HASH
322+
if not is_genesis and not cls._block_exists(db, first_block.parent_root):
323+
raise ParentNotFound(
324+
"Cannot persist block ({}) with unknown parent ({})".format(
325+
encode_hex(first_block.root), encode_hex(first_block.parent_root)))
319326

320-
if is_genesis:
321-
score = 0
322-
else:
323-
score = cls._get_score(db, first_block.parent_root)
327+
if is_genesis:
328+
score = 0
329+
else:
330+
score = cls._get_score(db, first_block.parent_root)
324331

325-
for block in blocks:
326-
db.set(
327-
block.root,
328-
rlp.encode(block),
329-
)
332+
curr_block_head = first_block
333+
db.set(
334+
curr_block_head.root,
335+
rlp.encode(curr_block_head),
336+
)
337+
cls._set_block_scores_to_db(db, curr_block_head)
330338

331-
# TODO: It's a stub before we implement fork choice rule
332-
score = block.slot
339+
orig_blocks_seq = concat([(first_block,), blocks_iterator])
340+
341+
for parent, child in sliding_window(2, orig_blocks_seq):
342+
if parent.root != child.parent_root:
343+
raise ValidationError(
344+
"Non-contiguous chain. Expected {} to have {} as parent but was {}".format(
345+
encode_hex(child.root),
346+
encode_hex(parent.root),
347+
encode_hex(child.parent_root),
348+
)
349+
)
333350

351+
curr_block_head = child
334352
db.set(
335-
SchemaV1.make_block_root_to_score_lookup_key(block.root),
336-
rlp.encode(score, sedes=rlp.sedes.big_endian_int),
353+
curr_block_head.root,
354+
rlp.encode(curr_block_head),
337355
)
356+
cls._set_block_scores_to_db(db, curr_block_head)
338357

339358
try:
340359
previous_canonical_head = cls._get_canonical_head(db, block_class).root
341360
head_score = cls._get_score(db, previous_canonical_head)
342361
except CanonicalHeadNotFound:
343-
return cls._set_as_canonical_chain_head(db, block.root, block_class)
362+
return cls._set_as_canonical_chain_head(db, curr_block_head.root, block_class)
344363

345364
if score > head_score:
346-
return cls._set_as_canonical_chain_head(db, block.root, block_class)
365+
return cls._set_as_canonical_chain_head(db, curr_block_head.root, block_class)
347366
else:
348367
return tuple(), tuple()
349368

eth/db/header.py

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import rlp
66

77
from cytoolz import (
8+
concat,
89
first,
910
sliding_window,
1011
)
@@ -204,62 +205,82 @@ def persist_header_chain(self,
204205
with self.db.atomic_batch() as db:
205206
return self._persist_header_chain(db, headers)
206207

208+
@classmethod
209+
def _set_hash_scores_to_db(
210+
cls,
211+
db: BaseDB,
212+
header: BlockHeader,
213+
score: int
214+
) -> int:
215+
new_score = score + header.difficulty
216+
217+
db.set(
218+
SchemaV1.make_block_hash_to_score_lookup_key(header.hash),
219+
rlp.encode(new_score, sedes=rlp.sedes.big_endian_int),
220+
)
221+
222+
return new_score
223+
207224
@classmethod
208225
def _persist_header_chain(
209226
cls,
210227
db: BaseDB,
211228
headers: Iterable[BlockHeader]
212229
) -> Tuple[Tuple[BlockHeader, ...], Tuple[BlockHeader, ...]]:
230+
headers_iterator = iter(headers)
231+
213232
try:
214-
first_header = first(headers)
233+
first_header = first(headers_iterator)
215234
except StopIteration:
216235
return tuple(), tuple()
217-
else:
218236

219-
for parent, child in sliding_window(2, headers):
220-
if parent.hash != child.parent_hash:
221-
raise ValidationError(
222-
"Non-contiguous chain. Expected {} to have {} as parent but was {}".format(
223-
encode_hex(child.hash),
224-
encode_hex(parent.hash),
225-
encode_hex(child.parent_hash),
226-
)
227-
)
237+
is_genesis = first_header.parent_hash == GENESIS_PARENT_HASH
238+
if not is_genesis and not cls._header_exists(db, first_header.parent_hash):
239+
raise ParentNotFound(
240+
"Cannot persist block header ({}) with unknown parent ({})".format(
241+
encode_hex(first_header.hash), encode_hex(first_header.parent_hash)))
228242

229-
is_genesis = first_header.parent_hash == GENESIS_PARENT_HASH
230-
if not is_genesis and not cls._header_exists(db, first_header.parent_hash):
231-
raise ParentNotFound(
232-
"Cannot persist block header ({}) with unknown parent ({})".format(
233-
encode_hex(first_header.hash), encode_hex(first_header.parent_hash)))
243+
if is_genesis:
244+
score = 0
245+
else:
246+
score = cls._get_score(db, first_header.parent_hash)
234247

235-
if is_genesis:
236-
score = 0
237-
else:
238-
score = cls._get_score(db, first_header.parent_hash)
248+
curr_chain_head = first_header
249+
db.set(
250+
curr_chain_head.hash,
251+
rlp.encode(curr_chain_head),
252+
)
253+
score = cls._set_hash_scores_to_db(db, curr_chain_head, score)
254+
255+
orig_headers_seq = concat([(first_header,), headers_iterator])
256+
for parent, child in sliding_window(2, orig_headers_seq):
257+
if parent.hash != child.parent_hash:
258+
raise ValidationError(
259+
"Non-contiguous chain. Expected {} to have {} as parent but was {}".format(
260+
encode_hex(child.hash),
261+
encode_hex(parent.hash),
262+
encode_hex(child.parent_hash),
263+
)
264+
)
239265

240-
for header in headers:
266+
curr_chain_head = child
241267
db.set(
242-
header.hash,
243-
rlp.encode(header),
268+
curr_chain_head.hash,
269+
rlp.encode(curr_chain_head),
244270
)
245271

246-
score += header.difficulty
247-
248-
db.set(
249-
SchemaV1.make_block_hash_to_score_lookup_key(header.hash),
250-
rlp.encode(score, sedes=rlp.sedes.big_endian_int),
251-
)
272+
score = cls._set_hash_scores_to_db(db, curr_chain_head, score)
252273

253274
try:
254275
previous_canonical_head = cls._get_canonical_head(db).hash
255276
head_score = cls._get_score(db, previous_canonical_head)
256277
except CanonicalHeadNotFound:
257-
return cls._set_as_canonical_chain_head(db, header.hash)
278+
return cls._set_as_canonical_chain_head(db, curr_chain_head.hash)
258279

259280
if score > head_score:
260-
return cls._set_as_canonical_chain_head(db, header.hash)
261-
else:
262-
return tuple(), tuple()
281+
return cls._set_as_canonical_chain_head(db, curr_chain_head.hash)
282+
283+
return tuple(), tuple()
263284

264285
@classmethod
265286
def _set_as_canonical_chain_head(cls, db: BaseDB, block_hash: Hash32

tests/database/test_header_db.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,28 @@ def test_headerdb_get_canonical_head_with_header_chain(headerdb, genesis_header)
8181
assert_headers_eq(head, headers[-1])
8282

8383

84+
@pytest.mark.parametrize(
85+
'chain_length',
86+
(0, 1, 2, 3),
87+
)
88+
def test_headerdb_get_canonical_head_with_header_chain_iterator(
89+
headerdb,
90+
genesis_header,
91+
chain_length):
92+
93+
headerdb.persist_header(genesis_header)
94+
headers = mk_header_chain(genesis_header, length=chain_length)
95+
96+
headerdb.persist_header_chain(header for header in headers)
97+
98+
head = headerdb.get_canonical_head()
99+
100+
if chain_length == 0:
101+
assert_headers_eq(head, genesis_header)
102+
else:
103+
assert_headers_eq(head, headers[-1])
104+
105+
84106
def test_headerdb_persist_header_disallows_unknown_parent(headerdb):
85107
header = BlockHeader(
86108
difficulty=GENESIS_DIFFICULTY,

0 commit comments

Comments
 (0)