def _persist_header_chain( cls, db: DatabaseAPI, headers: Iterable[BlockHeaderAPI], genesis_parent_hash: Hash32, ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: headers_iterator = iter(headers) try: first_header = first(headers_iterator) except StopIteration: return tuple(), tuple() is_genesis = first_header.parent_hash == genesis_parent_hash if not is_genesis and not cls._header_exists(db, first_header.parent_hash): raise ParentNotFound( f"Cannot persist block header ({encode_hex(first_header.hash)}) " f"with unknown parent ({encode_hex(first_header.parent_hash)})" ) if is_genesis: score = 0 else: score = cls._get_score(db, first_header.parent_hash) curr_chain_head = first_header db.set( curr_chain_head.hash, rlp.encode(curr_chain_head), ) score = cls._set_hash_scores_to_db(db, curr_chain_head, score) orig_headers_seq = concat([(first_header,), headers_iterator]) for parent, child in sliding_window(2, orig_headers_seq): if parent.hash != child.parent_hash: raise ValidationError( f"Non-contiguous chain. Expected {encode_hex(child.hash)} " f"to have {encode_hex(parent.hash)} as parent " f"but was {encode_hex(child.parent_hash)}" ) curr_chain_head = child db.set( curr_chain_head.hash, rlp.encode(curr_chain_head), ) score = cls._set_hash_scores_to_db(db, curr_chain_head, score) try: previous_canonical_head = cls._get_canonical_head_hash(db) head_score = cls._get_score(db, previous_canonical_head) except CanonicalHeadNotFound: return cls._set_as_canonical_chain_head(db, curr_chain_head, genesis_parent_hash) if score > head_score: return cls._set_as_canonical_chain_head(db, curr_chain_head, genesis_parent_hash) return tuple(), tuple()
def _remove_transaction_from_canonical_chain( db: DatabaseAPI, transaction_hash: Hash32) -> None: """ Removes the transaction specified by the given hash from the canonical chain. """ db.delete( SchemaV1.make_transaction_hash_to_block_lookup_key( transaction_hash))
def test_database_api_delete(self, db: DatabaseAPI) -> None: db[b'key-1'] = b'value-1' assert b'key-1' in db db.delete(b'key-1') assert not db.exists(b'key-1') assert b'key-1' not in db
def _add_block_number_to_hash_lookup(db: DatabaseAPI, header: BlockHeaderAPI) -> None: """ Sets a record in the database to allow looking up this header by its block number. """ block_number_to_hash_key = SchemaV1.make_block_number_to_hash_lookup_key( header.block_number ) db.set( block_number_to_hash_key, rlp.encode(header.hash, sedes=rlp.sedes.binary), )
def _persist_checkpoint_header( cls, db: DatabaseAPI, header: BlockHeaderAPI, score: int ) -> None: db.set( header.hash, rlp.encode(header), ) previous_score = score - header.difficulty cls._set_hash_scores_to_db(db, header, previous_score) cls._set_as_canonical_chain_head(db, header, header.parent_hash)
def _set_hash_scores_to_db( cls, db: DatabaseAPI, header: BlockHeaderAPI, score: int ) -> int: new_score = score + header.difficulty db.set( SchemaV1.make_block_hash_to_score_lookup_key(header.hash), rlp.encode(new_score, sedes=rlp.sedes.big_endian_int), ) return new_score
def _add_transaction_to_canonical_chain(db: DatabaseAPI, transaction_hash: Hash32, block_header: BlockHeaderAPI, index: int) -> None: """ :param bytes transaction_hash: the hash of the transaction to add the lookup for :param block_header: The header of the block with the txn that is in the canonical chain :param int index: the position of the transaction in the block - add lookup from transaction hash to the block number and index that the body is stored at - remove transaction hash to body lookup in the pending pool """ transaction_key = TransactionKey(block_header.block_number, index) db.set( SchemaV1.make_transaction_hash_to_block_lookup_key( transaction_hash), rlp.encode(transaction_key), )
def _set_as_canonical_chain_head( cls, db: DatabaseAPI, header: BlockHeaderAPI, genesis_parent_hash: Hash32, ) -> Tuple[Tuple[BlockHeaderAPI, ...], Tuple[BlockHeaderAPI, ...]]: """ Sets the canonical chain HEAD to the block header as specified by the given block hash. :return: a tuple of the headers that are newly in the canonical chain, and the headers that are no longer in the canonical chain """ try: current_canonical_head = cls._get_canonical_head_hash(db) except CanonicalHeadNotFound: current_canonical_head = None new_canonical_headers: Tuple[BlockHeaderAPI, ...] old_canonical_headers: Tuple[BlockHeaderAPI, ...] if current_canonical_head and header.parent_hash == current_canonical_head: # the calls to _find_new_ancestors and _decanonicalize_old_headers are # relatively expensive, it's better to skip them in this case, where we're # extending the canonical chain by a header new_canonical_headers = (header,) old_canonical_headers = () else: new_canonical_headers = cast( Tuple[BlockHeaderAPI, ...], tuple(reversed(cls._find_new_ancestors(db, header, genesis_parent_hash))) ) old_canonical_headers = cls._decanonicalize_old_headers( db, new_canonical_headers ) for h in new_canonical_headers: cls._add_block_number_to_hash_lookup(db, h) db.set(SchemaV1.make_canonical_head_hash_lookup_key(), header.hash) return new_canonical_headers, old_canonical_headers
def _persist_uncles(db: DatabaseAPI, uncles: Tuple[BlockHeaderAPI]) -> Hash32: uncles_hash = keccak(rlp.encode(uncles)) db.set(uncles_hash, rlp.encode(uncles, sedes=rlp.sedes.CountableList(BlockHeader))) return uncles_hash
def test_database_api_delete_missing_key(self, db: DatabaseAPI) -> None: assert b'key-1' not in db db.delete(b'key-1')
def test_database_api_get(self, db: DatabaseAPI) -> None: db[b'key-1'] = b'value-1' assert db.get(b'key-1') == b'value-1'
def test_database_api_exists(self, db: DatabaseAPI) -> None: assert db.exists(b'key-1') is False db[b'key-1'] = b'value-1' assert db.exists(b'key-1') is True
def test_database_api_item_setter(self, db: DatabaseAPI) -> None: db.set(b'key-1', b'value-1') assert db[b'key-1'] == b'value-1' db.set(b'key-1', b'value-2') assert db[b'key-1'] == b'value-2'
def test_database_api_get_missing_key(self, db: DatabaseAPI) -> None: assert b'key-1' not in db assert db.get(b'key-1') is None