import threading from evm.db.chain import BaseChainDB from evm.chains.ropsten import ROPSTEN_NETWORK_ID from evm.chains.mainnet import MAINNET_VM_CONFIGURATION from evm.p2p import ecies from evm.p2p.lightchain import LightChain from evm.db.backends.level import LevelDB parser = argparse.ArgumentParser() parser.add_argument('-db', type=str, required=True) args = parser.parse_args() DemoLightChain = LightChain.configure( name='Demo LightChain', privkey=ecies.generate_privkey(), vm_configuration=MAINNET_VM_CONFIGURATION, network_id=ROPSTEN_NETWORK_ID, ) chain = DemoLightChain(BaseChainDB(LevelDB(args.db))) loop = asyncio.get_event_loop() t = threading.Thread(target=loop.run_until_complete, args=(chain.run(), )) t.setDaemon(True) t.start() def wait_for_result(coroutine): future = asyncio.run_coroutine_threadsafe(coroutine, loop) return future.result()
def _test(): import argparse import signal from evm.chains.mainnet import (MAINNET_GENESIS_HEADER, MAINNET_VM_CONFIGURATION, MAINNET_NETWORK_ID) from evm.chains.ropsten import ROPSTEN_GENESIS_HEADER, ROPSTEN_NETWORK_ID from evm.db.backends.level import LevelDB from evm.exceptions import CanonicalHeadNotFound from evm.p2p import ecies from evm.p2p.integration_test_helpers import LocalGethPeerPool logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') logging.getLogger("evm.p2p.lightchain.LightChain").setLevel(logging.DEBUG) parser = argparse.ArgumentParser() parser.add_argument('-db', type=str, required=True) parser.add_argument('-mainnet', action="store_true") parser.add_argument('-local-geth', action="store_true") args = parser.parse_args() GENESIS_HEADER = ROPSTEN_GENESIS_HEADER NETWORK_ID = ROPSTEN_NETWORK_ID if args.mainnet: GENESIS_HEADER = MAINNET_GENESIS_HEADER NETWORK_ID = MAINNET_NETWORK_ID DemoLightChain = LightChain.configure( 'DemoLightChain', vm_configuration=MAINNET_VM_CONFIGURATION, network_id=NETWORK_ID, ) chaindb = BaseChainDB(LevelDB(args.db)) if args.local_geth: peer_pool = LocalGethPeerPool(LESPeer, chaindb, NETWORK_ID, ecies.generate_privkey()) else: peer_pool = PeerPool(LESPeer, chaindb, NETWORK_ID, ecies.generate_privkey()) try: chaindb.get_canonical_head() except CanonicalHeadNotFound: # We're starting with a fresh DB. chain = DemoLightChain.from_genesis_header(chaindb, GENESIS_HEADER, peer_pool) else: # We're reusing an existing db. chain = DemoLightChain(chaindb, peer_pool) asyncio.ensure_future(peer_pool.run()) loop = asyncio.get_event_loop() async def run(): # chain.run() will run in a loop until stop() (registered as SIGINT/SIGTERM handler) is # called, at which point it returns and we cleanly stop the pool and chain. await chain.run() await peer_pool.stop() await chain.stop() def stop(): chain._should_stop.set() for sig in [signal.SIGINT, signal.SIGTERM]: loop.add_signal_handler(sig, stop) loop.run_until_complete(run()) loop.close()
async def test_lightchain_integration(request, event_loop): """Test LightChain against a local geth instance. This test assumes a geth/ropsten instance is listening on 127.0.0.1:30303 and serving light clients. In order to achieve that, simply run it with the following command line: $ geth -nodekeyhex 45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8 \ -testnet -lightserv 90 """ # TODO: Implement a pytest fixture that runs geth as above, so that we don't need to run it # manually. if not pytest.config.getoption("--integration"): pytest.skip("Not asked to run integration tests") chaindb = ChainDB(MemoryDB()) chaindb.persist_header_to_db(ROPSTEN_GENESIS_HEADER) peer_pool = LocalGethPeerPool(LESPeer, chaindb, ROPSTEN_NETWORK_ID, ecies.generate_privkey()) chain = IntegrationTestLightChain(chaindb, peer_pool) asyncio.ensure_future(peer_pool.run()) asyncio.ensure_future(chain.run()) await asyncio.sleep( 0) # Yield control to give the LightChain a chance to start def finalizer(): event_loop.run_until_complete(peer_pool.stop()) event_loop.run_until_complete(chain.stop()) request.addfinalizer(finalizer) n = 11 # Wait for the chain to sync a few headers. async def wait_for_header_sync(block_number): while chaindb.get_canonical_head().block_number < block_number: await asyncio.sleep(0.1) await asyncio.wait_for(wait_for_header_sync(n), 2) # https://ropsten.etherscan.io/block/11 b = await chain.get_canonical_block_by_number(n) assert isinstance(b, FrontierBlock) assert b.number == 11 assert encode_hex(b.hash) == ( '0xda882aeff30f59eda9da2b3ace3023366ab9d4219b5a83cdd589347baae8678e') assert len(b.transactions) == 15 assert isinstance(b.transactions[0], b.transaction_class) receipts = await chain.get_receipts(b.hash) assert len(receipts) == 15 assert encode_hex(keccak(rlp.encode(receipts[0]))) == ( '0xf709ed2c57efc18a1675e8c740f3294c9e2cb36ba7bb3b89d3ab4c8fef9d8860') assert len(chain.peer_pool.peers) == 1 head_info = chain.peer_pool.peers[0].head_info head = await chain.get_block_by_hash(head_info.block_hash) assert head.number == head_info.block_number # In order to answer queries for contract code, geth needs the state trie entry for the block # we specify in the query, but because of fast sync we can only assume it has that for recent # blocks, so we use the current head to lookup the code for the contract below. # https://ropsten.etherscan.io/address/0x95a48dca999c89e4e284930d9b9af973a7481287 contract_addr = decode_hex('95a48dca999c89e4e284930d9b9af973a7481287') contract_code = await chain.get_contract_code(head.hash, keccak(contract_addr)) assert encode_hex(keccak(contract_code)) == ( '0x1e0b2ad970b365a217c40bcf3582cbb4fcc1642d7a5dd7a82ae1e278e010123e') account = await chain.get_account(head.hash, contract_addr) assert account.code_hash == keccak(contract_code) assert account.balance == 0
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') # The default remoteid can be used if you pass nodekeyhex as above to geth. nodekey = keys.PrivateKey( decode_hex( "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") ) remoteid = nodekey.public_key.to_hex() parser = argparse.ArgumentParser() parser.add_argument('-remoteid', type=str, default=remoteid) args = parser.parse_args() remote = Node(keys.PublicKey(decode_hex(args.remoteid)), Address('127.0.0.1', 30303, 30303)) chaindb = BaseChainDB(MemoryDB()) chaindb.persist_header_to_db(ROPSTEN_GENESIS_HEADER) network_id = RopstenChain.network_id loop = asyncio.get_event_loop() try: peer = loop.run_until_complete( asyncio.wait_for( handshake(remote, ecies.generate_privkey(), LESPeer, chaindb, network_id), HANDSHAKE_TIMEOUT)) loop.run_until_complete(peer.start()) except KeyboardInterrupt: pass loop.run_until_complete(peer.stop()) loop.close()
def _test(): """ Create a Peer instance connected to a local geth instance and log messages exchanged with it. Use the following command line to run geth: ./build/bin/geth -vmodule p2p=4,p2p/discv5=0,eth/*=0 \ -nodekeyhex 45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8 \ -testnet -lightserv 90 """ import argparse from evm.chains.ropsten import RopstenChain, ROPSTEN_GENESIS_HEADER from evm.db.backends.memory import MemoryDB logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') # The default remoteid can be used if you pass nodekeyhex as above to geth. nodekey = keys.PrivateKey( decode_hex( "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") ) remoteid = nodekey.public_key.to_hex() parser = argparse.ArgumentParser() parser.add_argument('-remoteid', type=str, default=remoteid) parser.add_argument('-light', action='store_true', help="Connect as a light node") args = parser.parse_args() peer_class = ETHPeer # type: ignore if args.light: peer_class = LESPeer # type: ignore remote = Node(keys.PublicKey(decode_hex(args.remoteid)), Address('127.0.0.1', 30303, 30303)) chaindb = BaseChainDB(MemoryDB()) chaindb.persist_header_to_db(ROPSTEN_GENESIS_HEADER) network_id = RopstenChain.network_id loop = asyncio.get_event_loop() try: peer = loop.run_until_complete( asyncio.wait_for( handshake(remote, ecies.generate_privkey(), peer_class, chaindb, network_id), HANDSHAKE_TIMEOUT)) asyncio.ensure_future(peer.do_sub_proto_handshake()) async def request_stuff(): # Request some stuff from ropsten's block 2440319 # (https://ropsten.etherscan.io/block/2440319), just as a basic test. nonlocal peer block_hash = decode_hex( '0x59af08ab31822c992bb3dad92ddb68d820aa4c69e9560f07081fa53f1009b152' ) if peer_class == ETHPeer: peer = cast(ETHPeer, peer) peer.sub_proto.send_get_block_headers(block_hash, 1) peer.sub_proto.send_get_block_bodies([block_hash]) peer.sub_proto.send_get_receipts([block_hash]) else: peer = cast(LESPeer, peer) request_id = 1 peer.sub_proto.send_get_block_headers(block_hash, 1, request_id) peer.sub_proto.send_get_block_bodies([block_hash], request_id + 1) peer.sub_proto.send_get_receipts(block_hash, request_id + 2) asyncio.ensure_future(request_stuff()) loop.run_until_complete(peer.run()) except KeyboardInterrupt: pass loop.run_until_complete(peer.stop()) loop.close()
async def get_directly_linked_peers(chaindb1=None, chaindb2=None): """Create two LESPeers with their readers/writers connected directly. The first peer's reader will write directly to the second's writer, and vice-versa. """ if chaindb1 is None: chaindb1 = BaseChainDB(MemoryDB()) chaindb1.persist_header_to_db(MAINNET_GENESIS_HEADER) if chaindb2 is None: chaindb2 = BaseChainDB(MemoryDB()) chaindb2.persist_header_to_db(MAINNET_GENESIS_HEADER) peer1_private_key = ecies.generate_privkey() peer2_private_key = ecies.generate_privkey() peer1_remote = kademlia.Node( peer2_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) peer2_remote = kademlia.Node( peer1_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = auth.HandshakeInitiator(peer1_remote, peer1_private_key) peer2_reader = asyncio.StreamReader() peer1_reader = asyncio.StreamReader() # Link the peer1's writer to the peer2's reader, and the peer2's writer to the # peer1's reader. peer2_writer = type( "mock-streamwriter", (object,), {"write": peer1_reader.feed_data, "close": lambda: None} ) peer1_writer = type( "mock-streamwriter", (object,), {"write": peer2_reader.feed_data, "close": lambda: None} ) peer1, peer2 = None, None handshake_finished = asyncio.Event() async def do_handshake(): nonlocal peer1, peer2 aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, peer1_reader, peer1_writer) # Need to copy those before we pass them on to the Peer constructor because they're # mutable. Also, the 2nd peer's ingress/egress MACs are reversed from the first peer's. peer2_ingress = egress_mac.copy() peer2_egress = ingress_mac.copy() peer1 = LESPeerServing( remote=peer1_remote, privkey=peer1_private_key, reader=peer1_reader, writer=peer1_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, chaindb=chaindb1, network_id=1) peer2 = LESPeerServing( remote=peer2_remote, privkey=peer2_private_key, reader=peer2_reader, writer=peer2_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=peer2_egress, ingress_mac=peer2_ingress, chaindb=chaindb2, network_id=1) handshake_finished.set() asyncio.ensure_future(do_handshake()) responder = auth.HandshakeResponder(peer2_remote, peer2_private_key) auth_msg = await peer2_reader.read(constants.ENCRYPTED_AUTH_MSG_LEN) peer1_ephemeral_pubkey, peer1_nonce = responder.decode_authentication(auth_msg) peer2_nonce = keccak(os.urandom(constants.HASH_LEN)) auth_ack_msg = responder.create_auth_ack_message(peer2_nonce) auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg) peer2_writer.write(auth_ack_ciphertext) await handshake_finished.wait() # Perform the base protocol (P2P) handshake. peer1.base_protocol.send_handshake() peer2.base_protocol.send_handshake() msg1 = await peer1.read_msg() peer1.process_msg(msg1) msg2 = await peer2.read_msg() peer2.process_msg(msg2) # Now send the handshake msg for each enabled sub-protocol. for proto in peer1.enabled_sub_protocols: proto.send_handshake(peer1.head_info) for proto in peer2.enabled_sub_protocols: proto.send_handshake(peer2.head_info) return peer1, peer2
def __init__(self, remote: kademlia.Node, privkey: datatypes.PrivateKey) -> None: self.remote = remote self.privkey = privkey self.ephemeral_privkey = ecies.generate_privkey()
async def _get_directly_linked_peers_without_handshake(peer1_class=LESPeer, peer1_chaindb=None, peer2_class=LESPeer, peer2_chaindb=None): """See get_directly_linked_peers(). Neither the P2P handshake nor the sub-protocol handshake will be performed here. """ if peer1_chaindb is None: peer1_chaindb = get_fresh_mainnet_chaindb() if peer2_chaindb is None: peer2_chaindb = get_fresh_mainnet_chaindb() peer1_private_key = ecies.generate_privkey() peer2_private_key = ecies.generate_privkey() peer1_remote = kademlia.Node(peer2_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) peer2_remote = kademlia.Node(peer1_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = auth.HandshakeInitiator(peer1_remote, peer1_private_key) peer2_reader = asyncio.StreamReader() peer1_reader = asyncio.StreamReader() # Link the peer1's writer to the peer2's reader, and the peer2's writer to the # peer1's reader. peer2_writer = type("mock-streamwriter", (object, ), { "write": peer1_reader.feed_data, "close": lambda: None }) peer1_writer = type("mock-streamwriter", (object, ), { "write": peer2_reader.feed_data, "close": lambda: None }) peer1, peer2 = None, None handshake_finished = asyncio.Event() async def do_handshake(): nonlocal peer1, peer2 aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, peer1_reader, peer1_writer) # Need to copy those before we pass them on to the Peer constructor because they're # mutable. Also, the 2nd peer's ingress/egress MACs are reversed from the first peer's. peer2_ingress = egress_mac.copy() peer2_egress = ingress_mac.copy() peer1 = peer1_class(remote=peer1_remote, privkey=peer1_private_key, reader=peer1_reader, writer=peer1_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, chaindb=peer1_chaindb, network_id=1) peer2 = peer2_class(remote=peer2_remote, privkey=peer2_private_key, reader=peer2_reader, writer=peer2_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=peer2_egress, ingress_mac=peer2_ingress, chaindb=peer2_chaindb, network_id=1) handshake_finished.set() asyncio.ensure_future(do_handshake()) responder = auth.HandshakeResponder(peer2_remote, peer2_private_key) auth_msg = await peer2_reader.read(constants.ENCRYPTED_AUTH_MSG_LEN) # Can't assert return values, but checking that the decoder doesn't raise # any exceptions at least. _, _ = responder.decode_authentication(auth_msg) peer2_nonce = keccak(os.urandom(constants.HASH_LEN)) auth_ack_msg = responder.create_auth_ack_message(peer2_nonce) auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg) peer2_writer.write(auth_ack_ciphertext) await handshake_finished.wait() return peer1, peer2
help="Connect as a light node") args = parser.parse_args() peer_class = ETHPeer # type: ignore if args.light: peer_class = LESPeer # type: ignore remote = Node(keys.PublicKey(decode_hex(args.remoteid)), Address('127.0.0.1', 30303, 30303)) chaindb = BaseChainDB(MemoryDB()) chaindb.persist_header_to_db(ROPSTEN_GENESIS_HEADER) network_id = RopstenChain.network_id loop = asyncio.get_event_loop() try: peer = loop.run_until_complete( asyncio.wait_for( handshake(remote, ecies.generate_privkey(), peer_class, chaindb, network_id), HANDSHAKE_TIMEOUT)) # Request some stuff from ropsten's block 2440319 # (https://ropsten.etherscan.io/block/2440319), just as a basic test. block_hash = decode_hex( '0x59af08ab31822c992bb3dad92ddb68d820aa4c69e9560f07081fa53f1009b152' ) if peer_class == ETHPeer: peer = cast(ETHPeer, peer) peer.eth_proto.send_get_block_headers(block_hash, 1) peer.eth_proto.send_get_block_bodies([block_hash]) peer.eth_proto.send_get_receipts([block_hash]) else: peer = cast(LESPeer, peer) request_id = 1 peer.les_proto.send_get_block_headers(block_hash, 1, request_id)
class LightChain(Chain): logger = logging.getLogger("evm.p2p.lightchain.LightChain") # FIXME: Should be passed in by callers privkey = ecies.generate_privkey() max_consecutive_timeouts = 5 peer_pool_class = PeerPool def __init__(self, chaindb: BaseChainDB) -> None: super(LightChain, self).__init__(chaindb) self.peer_pool = self.peer_pool_class(chaindb, self.network_id, self.privkey, self.msg_handler) self._announcement_queue = asyncio.Queue( ) # type: asyncio.Queue[Tuple[LESPeer, les.HeadInfo]] # noqa: E501 self._last_processed_announcements = { } # type: Dict[LESPeer, les.HeadInfo] self._latest_head_info = {} # type: Dict[LESPeer, les.HeadInfo] self._should_stop = asyncio.Event() self._finished = asyncio.Event() def msg_handler(self, peer: BasePeer, cmd: protocol.Command, announcement: protocol._DecodedMsgType) -> None: """The callback passed to BasePeer, called for every incoming message.""" peer = cast(LESPeer, peer) if isinstance(cmd, (les.Announce, les.Status)): head_info = cmd.as_head_info(announcement) self._latest_head_info[peer] = head_info self._announcement_queue.put_nowait((peer, head_info)) async def drop_peer(self, peer: LESPeer) -> None: self._last_processed_announcements.pop(peer, None) self._latest_head_info.pop(peer, None) await peer.stop_and_wait_until_finished() async def wait_for_announcement(self) -> Tuple[LESPeer, les.HeadInfo]: """Wait for a new announcement from any of our connected peers. Returns a tuple containing the LESPeer on which the announcement was received and the announcement info. Raises StopRequested when LightChain.stop() has been called. """ should_stop = False async def wait_for_stop_event(): nonlocal should_stop await self._should_stop.wait() should_stop = True # Wait for either a new announcement or the _should_stop event. done, pending = await asyncio.wait( [self._announcement_queue.get(), wait_for_stop_event()], return_when=asyncio.FIRST_COMPLETED) # The async call above returns as soon as one of our 2 coroutines complete, so we know # for sure we'll have one task in the <done> and one task in the <pending> set. pending.pop().cancel() if should_stop: raise StopRequested() return done.pop().result() async def run(self) -> None: """Run the LightChain, ensuring headers are in sync with connected peers. Run our PeerPool to ensure we are always connected to some peers and then loop forever, waiting for announcements from connected peers and fetching new headers. If .stop() is called, we'll disconnect from all peers and return. """ self.logger.info("Running LightChain...") asyncio.ensure_future(self.peer_pool.run()) while True: try: peer, head_info = await self.wait_for_announcement() except StopRequested: break try: await self.process_announcement(peer, head_info) self._last_processed_announcements[peer] = head_info except LESAnnouncementProcessingError as e: self.logger.warn(repr(e)) await self.drop_peer(peer) except Exception as e: self.logger.error( "Unexpected error when processing announcement: %s", repr(e)) await self.drop_peer(peer) self._finished.set() async def fetch_headers(self, start_block: int, peer: LESPeer) -> List[BlockHeader]: for i in range(self.max_consecutive_timeouts): try: return await peer.fetch_headers_starting_at(start_block) except asyncio.TimeoutError: self.logger.info( "Timeout when fetching headers from %s (attempt %d of %d)", peer, i + 1, self.max_consecutive_timeouts) # TODO: Figure out what's a good value to use here. await asyncio.sleep(0.5) raise TooManyTimeouts() async def get_sync_start_block(self, peer: LESPeer, head_info: les.HeadInfo) -> int: chain_head = self.chaindb.get_canonical_head() last_peer_announcement = self._last_processed_announcements.get(peer) if chain_head.block_number == GENESIS_BLOCK_NUMBER: start_block = GENESIS_BLOCK_NUMBER elif last_peer_announcement is None: # It's the first time we hear from this peer, need to figure out which headers to # get from it. We can't simply fetch headers starting from our current head # number because there may have been a chain reorg, so we fetch some headers prior # to our head from the peer, and insert any missing ones in our DB, essentially # making our canonical chain identical to the peer's up to # chain_head.block_number. oldest_ancestor_to_consider = max( 0, chain_head.block_number - peer.max_headers_fetch + 1) try: headers = await self.fetch_headers(oldest_ancestor_to_consider, peer) except EmptyGetBlockHeadersReply: raise LESAnnouncementProcessingError( "No common ancestors found between us and %s", peer) except TooManyTimeouts: raise LESAnnouncementProcessingError( "Too many timeouts when fetching headers from %s", peer) for header in headers: self.chaindb.persist_header_to_db(header) start_block = chain_head.block_number else: start_block = last_peer_announcement.block_number - head_info.reorg_depth return start_block # TODO: Distribute requests among our peers, ensuring the selected peer has the info we want # and respecting the flow control rules. async def process_announcement(self, peer: LESPeer, head_info: les.HeadInfo) -> None: if self.chaindb.header_exists(head_info.block_hash): self.logger.debug( "Skipping processing of %s from %s as head has already been fetched", head_info, peer) return start_block = await self.get_sync_start_block(peer, head_info) while start_block < head_info.block_number: try: # We should use "start_block + 1" here, but we always re-fetch the last synced # block to work around https://github.com/ethereum/go-ethereum/issues/15447 batch = await self.fetch_headers(start_block, peer) except TooManyTimeouts: raise LESAnnouncementProcessingError( "Too many timeouts when fetching headers from %s", peer) for header in batch: self.chaindb.persist_header_to_db(header) start_block = header.block_number self.logger.info("synced headers up to #%s", start_block) async def stop(self): self.logger.info("Stopping LightChain...") self._should_stop.set() await self.peer_pool.stop() await self._finished.wait() async def get_canonical_block_by_number(self, block_number: int) -> BaseBlock: try: block_hash = self.chaindb.lookup_block_hash(block_number) except KeyError: raise BlockNotFound( "No block with number {} found on local chain".format( block_number)) return await self.get_block_by_hash(block_hash) @alru_cache(maxsize=1024) async def get_block_by_hash(self, block_hash: bytes) -> BaseBlock: peer = await self.peer_pool.get_best_peer() self.logger.debug("Fetching block %s from %s", encode_hex(block_hash), peer) request_id = gen_request_id() peer.les_proto.send_get_block_bodies([block_hash], request_id) reply = await peer.wait_for_reply(request_id) if len(reply['bodies']) == 0: raise BlockNotFound( "No block with hash {} found".format(block_hash)) body = reply['bodies'][0] # This will raise a BlockNotFound if we don't have the header in our DB, which is correct # because it means our peer doesn't know about it. header = self.chaindb.get_block_header_by_hash(block_hash) block_class = self.get_vm_class_for_block_number( header.block_number).get_block_class() return block_class( header=header, transactions=body.transactions, uncles=body.uncles, chaindb=self.chaindb, )