def add_transaction(self, transaction, computation, block): """ Add a transaction to the given block and return `trie_data` to store the transaction data in chaindb in VM layer. Update the bloom_filter, transaction trie and receipt trie roots, bloom_filter, bloom, and used_gas of the block. :param transaction: the executed transaction :param computation: the Computation object with executed result :param block: the Block which the transaction is added in :type transaction: Transaction :type computation: Computation :type block: Block :return: the block and the trie_data :rtype: (Block, dict[bytes, bytes]) """ receipt = self.make_receipt(transaction, computation) self.add_receipt(receipt) # Create a new Block object block_header = block.header.clone() transactions = list(block.transactions) block = self.block_class(block_header, transactions) block.transactions.append(transaction) # Calculate transaction fee transaction_fee, _ = computation.compute_transaction_fee_and_refund() # Bookkeep this transaction fee block.transaction_fee_sum += transaction_fee # Get trie roots and changed key-values. tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes( block.transactions, trie_class=BinaryTrie, ) receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes( self.receipts, trie_class=BinaryTrie, ) trie_data = merge(tx_kv_nodes, receipt_kv_nodes) block.header.transaction_root = tx_root_hash block.header.receipt_root = receipt_root_hash block.header.gas_used = receipt.gas_used return block, trie_data
def set_block_transactions(self, base_block, new_header, transactions, receipts): tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(transactions) self.chaindb.persist_trie_data_dict(tx_kv_nodes) receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes(receipts) self.chaindb.persist_trie_data_dict(receipt_kv_nodes) return base_block.copy( transactions=transactions, header=new_header.copy( transaction_root=tx_root_hash, receipt_root=receipt_root_hash, ), )
async def handle_msg(self, peer: ETHPeer, cmd: protocol.Command, msg: protocol._DecodedMsgType) -> None: if isinstance(cmd, eth.BlockHeaders): msg = cast(List[BlockHeader], msg) self.logger.debug("Got BlockHeaders from %d to %d", msg[0].block_number, msg[-1].block_number) self._new_headers.put_nowait(msg) elif isinstance(cmd, eth.BlockBodies): msg = cast(List[eth.BlockBody], msg) self.logger.debug("Got %d BlockBodies", len(msg)) for body in msg: tx_root, trie_dict_data = make_trie_root_and_nodes( body.transactions) await self.chaindb.coro_persist_trie_data_dict(trie_dict_data) # TODO: Add transactions to canonical chain; blocked by # https://github.com/ethereum/py-evm/issues/337 uncles_hash = await self.chaindb.coro_persist_uncles( body.uncles) self._pending_bodies.pop((tx_root, uncles_hash), None) elif isinstance(cmd, eth.Receipts): msg = cast(List[List[eth.Receipt]], msg) self.logger.debug("Got Receipts for %d blocks", len(msg)) for block_receipts in msg: receipt_root, trie_dict_data = make_trie_root_and_nodes( block_receipts) await self.chaindb.coro_persist_trie_data_dict(trie_dict_data) self._pending_receipts.pop(receipt_root, None) elif isinstance(cmd, eth.NewBlock): msg = cast(Dict[str, Any], msg) header = msg['block'][0] actual_head = header.parent_hash actual_td = msg['total_difficulty'] - header.difficulty if actual_td > peer.head_td: peer.head_hash = actual_head peer.head_td = actual_td self._sync_requests.put_nowait(peer) elif isinstance(cmd, eth.Transactions): # TODO: Figure out what to do with those. pass elif isinstance(cmd, eth.NewBlockHashes): # TODO: Figure out what to do with those. pass else: # TODO: There are other msg types we'll want to handle here, but for now just log them # as a warning so we don't forget about it. self.logger.warn("Got unexpected msg: %s (%s)", cmd, msg)
def import_block(self, block): self.block = self.block.copy( header=self.configure_header( coinbase=block.header.coinbase, gas_limit=block.header.gas_limit, timestamp=block.header.timestamp, extra_data=block.header.extra_data, mix_hash=block.header.mix_hash, nonce=block.header.nonce, uncles_hash=keccak(rlp.encode(block.uncles)), ), uncles=block.uncles, ) # we need to re-initialize the `state` to update the execution context. self.state = self.get_state_class()( db=self.chaindb.db, execution_context=self.block.header.create_execution_context(self.previous_hashes), state_root=self.block.header.state_root, gas_used=self.block.header.gas_used, ) # run all of the transactions. execution_data = [ self.apply_transaction(transaction) for transaction in block.transactions ] if execution_data: _, receipts, _ = zip(*execution_data) else: receipts = tuple() tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(block.transactions) receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes(receipts) self.chaindb.persist_trie_data_dict(tx_kv_nodes) self.chaindb.persist_trie_data_dict(receipt_kv_nodes) self.block = self.block.copy( header=self.block.header.copy( transaction_root=tx_root_hash, receipt_root=receipt_root_hash, ), ) return self.mine_block()
def validate_block(self, block): if not block.is_genesis: parent_header = get_parent_header(block.header, self.chaindb) validate_gas_limit(block.header.gas_limit, parent_header.gas_limit) validate_length_lte(block.header.extra_data, 32, title="BlockHeader.extra_data") # timestamp if block.header.timestamp < parent_header.timestamp: raise ValidationError( "`timestamp` is before the parent block's timestamp.\n" "- block : {0}\n" "- parent : {1}. ".format( block.header.timestamp, parent_header.timestamp, )) elif block.header.timestamp == parent_header.timestamp: raise ValidationError( "`timestamp` is equal to the parent block's timestamp\n" "- block : {0}\n" "- parent: {1}. ".format( block.header.timestamp, parent_header.timestamp, )) tx_root_hash, _ = make_trie_root_and_nodes(block.transactions) if tx_root_hash != block.header.transaction_root: raise ValidationError( "Block's transaction_root ({0}) does not match expected value: {1}" .format(block.header.transaction_root, tx_root_hash)) if len(block.uncles) > MAX_UNCLES: raise ValidationError( "Blocks may have a maximum of {0} uncles. Found " "{1}.".format(MAX_UNCLES, len(block.uncles))) for uncle in block.uncles: self.validate_uncle(block, uncle) if not self.state.is_key_exists(block.header.state_root): raise ValidationError("`state_root` was not found in the db.\n" "- state_root: {0}".format( block.header.state_root, )) local_uncle_hash = keccak(rlp.encode(block.uncles)) if local_uncle_hash != block.header.uncles_hash: raise ValidationError( "`uncles_hash` and block `uncles` do not match.\n" " - num_uncles : {0}\n" " - block uncle_hash : {1}\n" " - header uncle_hash: {2}".format( len(block.uncles), local_uncle_hash, block.header.uncle_hash, ))
def apply_transaction(self, transaction): """ Applies the transaction to the current tip block. WARNING: Receipt and Transaction trie generation is computationally heavy and incurs significant perferomance overhead. """ vm = self.get_vm() block, receipt, computation = vm.apply_transaction(transaction) receipts = block.get_receipts(self.chaindb) + [receipt] tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(block.transactions) receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes(receipts) self.chaindb.persist_trie_data_dict(tx_kv_nodes) self.chaindb.persist_trie_data_dict(receipt_kv_nodes) self.header = block.header.copy( transaction_root=tx_root_hash, receipt_root=receipt_root_hash, ) return block.copy(header=self.header), receipt, computation
def mine_block(self, *args, **kwargs): """ Mine the current block. Proxies to self.pack_block method. """ block = self.block tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes( block.transactions) receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes( self.receipts) self.chaindb.persist_trie_data_dict(tx_kv_nodes) self.chaindb.persist_trie_data_dict(receipt_kv_nodes) self.block.header.transaction_root = tx_root_hash self.block.header.receipt_root = receipt_root_hash self.pack_block(block, *args, **kwargs) if block.number == 0: return block block = self.state.finalize_block(block) return block
def validate_block(self, block): """ Validate the the given block. """ if not isinstance(block, self.get_block_class()): raise ValidationError( "This vm ({0!r}) is not equipped to validate a block of type {1!r}".format( self, block, ) ) if block.is_genesis: validate_length_lte(block.header.extra_data, 32, title="BlockHeader.extra_data") else: parent_header = get_parent_header(block.header, self.chaindb) self.validate_header(block.header, parent_header) tx_root_hash, _ = make_trie_root_and_nodes(block.transactions) if tx_root_hash != block.header.transaction_root: raise ValidationError( "Block's transaction_root ({0}) does not match expected value: {1}".format( block.header.transaction_root, tx_root_hash)) if len(block.uncles) > MAX_UNCLES: raise ValidationError( "Blocks may have a maximum of {0} uncles. Found " "{1}.".format(MAX_UNCLES, len(block.uncles)) ) if not self.chaindb.exists(block.header.state_root): raise ValidationError( "`state_root` was not found in the db.\n" "- state_root: {0}".format( block.header.state_root, ) ) local_uncle_hash = keccak(rlp.encode(block.uncles)) if local_uncle_hash != block.header.uncles_hash: raise ValidationError( "`uncles_hash` and block `uncles` do not match.\n" " - num_uncles : {0}\n" " - block uncle_hash : {1}\n" " - header uncle_hash: {2}".format( len(block.uncles), local_uncle_hash, block.header.uncle_hash, ) )