def build_computation(self, message, transaction): """Apply the message to the VM.""" transaction_context = self.get_transaction_context(transaction) if message.is_create: is_collision = self.vm_state.account_db.account_has_code_or_nonce( message.storage_address) if is_collision: # The address of the newly created contract has *somehow* collided # with an existing contract address. computation = self.vm_state.get_computation( message, transaction_context) computation._error = ContractCreationCollision( "Address collision while creating contract: {0}".format( encode_hex(message.storage_address), )) self.vm_state.logger.debug( "Address collision while creating contract: %s", encode_hex(message.storage_address), ) else: computation = self.vm_state.get_computation( message, transaction_context, ).apply_create_message() else: computation = self.vm_state.get_computation( message, transaction_context).apply_message() return computation
def _apply_frontier_create_message(vm, message): computation = vm.apply_message(message) if computation.is_error: return computation else: contract_code = computation.output if contract_code: contract_code_gas_fee = len(contract_code) * constants.GAS_CODEDEPOSIT try: computation.gas_meter.consume_gas( contract_code_gas_fee, reason="Write contract code for CREATE", ) except OutOfGas: computation.output = b'' else: vm.logger.debug( "SETTING CODE: %s -> length: %s | hash: %s", encode_hex(message.storage_address), len(contract_code), encode_hex(keccak(contract_code)) ) with vm.state_db() as state_db: state_db.set_code(message.storage_address, contract_code) return computation
def run_computation(self, transaction, message): """Apply the message to the VM.""" transaction_context = self.get_transaction_context_class()( gas_price=transaction.gas_price, origin=transaction.sender, ) if message.is_create: is_collision = self.read_only_state_db.account_has_code_or_nonce( message.storage_address) if is_collision: # The address of the newly created contract has *somehow* collided # with an existing contract address. computation = self.get_computation(message, transaction_context) computation._error = ContractCreationCollision( "Address collision while creating contract: {0}".format( encode_hex(message.storage_address), )) self.logger.debug( "Address collision while creating contract: %s", encode_hex(message.storage_address), ) else: computation = self.get_computation( message, transaction_context, ).apply_create_message() else: computation = self.get_computation( message, transaction_context).apply_message() return computation
def validate_uncles(self, block): recent_ancestors = dict( (ancestor.hash, ancestor) for ancestor in self.get_ancestors(MAX_UNCLE_DEPTH + 1), ) recent_uncles = [] for ancestor in recent_ancestors.values(): recent_uncles.extend([uncle.hash for uncle in ancestor.uncles]) recent_ancestors[block.hash] = block recent_uncles.append(block.hash) for uncle in block.uncles: if uncle.hash in recent_ancestors: raise ValidationError( "Duplicate uncle: {0}".format(encode_hex(uncle.hash))) recent_uncles.append(uncle.hash) if uncle.hash in recent_ancestors: raise ValidationError( "Uncle {0} cannot be an ancestor of {1}".format( encode_hex(uncle.hash), encode_hex(block.hash))) if uncle.parent_hash not in recent_ancestors or ( uncle.parent_hash == block.header.parent_hash): raise ValidationError( "Uncle's parent {0} is not an ancestor of {1}".format( encode_hex(uncle.parent_hash), encode_hex(block.hash))) self.validate_seal(uncle)
def apply_create_message(self): computation = self.apply_message() if computation.is_error: return computation else: contract_code = computation.output if contract_code: contract_code_gas_fee = len( contract_code) * constants.GAS_CODEDEPOSIT try: computation.consume_gas( contract_code_gas_fee, reason="Write contract code for CREATE", ) except OutOfGas: computation.output = b'' else: self.logger.debug( "SETTING CODE: %s -> length: %s | hash: %s", encode_hex(self.msg.storage_address), len(contract_code), encode_hex(keccak(contract_code))) self.state.account_db.set_code(self.msg.storage_address, contract_code) return computation
def get_canonical_transaction(self, transaction_hash: Hash32) -> BaseTransaction: """ Returns the requested transaction as specified by the transaction hash from the canonical chain. Raises TransactionNotFound if no transaction with the specified hash is found in the main chain. """ (block_num, index) = self.chaindb.get_transaction_index(transaction_hash) VM = self.get_vm_class_for_block_number(block_num) transaction = self.chaindb.get_transaction_by_index( block_num, index, VM.get_transaction_class(), ) if transaction.hash == transaction_hash: return transaction else: raise TransactionNotFound( "Found transaction {} instead of {} in block {} at {}".format( encode_hex(transaction.hash), encode_hex(transaction_hash), block_num, index, ))
def _apply_frontier_message(vm, message): snapshot = vm.snapshot() if message.depth > constants.STACK_DEPTH_LIMIT: raise StackDepthLimit("Stack depth limit reached") if message.should_transfer_value and message.value: with vm.state_db() as state_db: sender_balance = state_db.get_balance(message.sender) if sender_balance < message.value: raise InsufficientFunds("Insufficient funds: {0} < {1}".format( sender_balance, message.value)) state_db.delta_balance(message.sender, -1 * message.value) state_db.delta_balance(message.storage_address, message.value) vm.logger.debug( "TRANSFERRED: %s from %s -> %s", message.value, encode_hex(message.sender), encode_hex(message.storage_address), ) with vm.state_db() as state_db: state_db.touch_account(message.storage_address) computation = vm.apply_computation(message) if computation.error: vm.revert(snapshot) else: vm.commit(snapshot) return computation
def persist_header(self, header): """ :returns: iterable of headers newly on the canonical chain """ if header.parent_hash != GENESIS_PARENT_HASH and not self.header_exists(header.parent_hash): raise ParentNotFound( "Cannot persist block header ({}) with unknown parent ({})".format( encode_hex(header.hash), encode_hex(header.parent_hash))) self.db.set( header.hash, rlp.encode(header), ) if header.parent_hash == GENESIS_PARENT_HASH: score = header.difficulty else: score = self.get_score(header.parent_hash) + header.difficulty self.db.set( make_block_hash_to_score_lookup_key(header.hash), rlp.encode(score, sedes=rlp.sedes.big_endian_int)) try: head_score = self.get_score(self.get_canonical_head().hash) except CanonicalHeadNotFound: new_headers = self._set_as_canonical_chain_head(header) else: if score > head_score: new_headers = self._set_as_canonical_chain_head(header) else: new_headers = [] return new_headers
def persist_header_to_db(self, header): """ :returns: iterable of headers newly on the canonical chain """ if header.parent_hash != GENESIS_PARENT_HASH and not self.header_exists(header.parent_hash): raise ParentNotFound( "Cannot persist block header ({}) with unknown parent ({})".format( encode_hex(header.hash), encode_hex(header.parent_hash))) self.db.set( header.hash, rlp.encode(header), ) if header.parent_hash == GENESIS_PARENT_HASH: score = header.difficulty else: score = self.get_score(header.parent_hash) + header.difficulty self.db.set( make_block_hash_to_score_lookup_key(header.hash), rlp.encode(score, sedes=rlp.sedes.big_endian_int)) try: head_score = self.get_score(self.get_canonical_head().hash) except CanonicalHeadNotFound: new_headers = self._set_as_canonical_chain_head(header) else: if score > head_score: new_headers = self._set_as_canonical_chain_head(header) else: new_headers = [] return new_headers
def validate_uncles(self, block: BaseBlock) -> None: """ Validate the uncles for the given block. """ # Check for duplicates uncle_groups = groupby(operator.attrgetter('hash'), block.uncles) duplicate_uncles = tuple(sorted( hash for hash, twins in uncle_groups.items() if len(twins) > 1 )) if duplicate_uncles: raise ValidationError( "Block contains duplicate uncles:\n" " - {0}".format(' - '.join(duplicate_uncles)) ) recent_ancestors = tuple( ancestor for ancestor in self.get_ancestors(MAX_UNCLE_DEPTH + 1, header=block.header) ) recent_ancestor_hashes = {ancestor.hash for ancestor in recent_ancestors} recent_uncle_hashes = _extract_uncle_hashes(recent_ancestors) for uncle in block.uncles: if uncle.hash == block.hash: raise ValidationError("Uncle has same hash as block") # ensure the uncle has not already been included. if uncle.hash in recent_uncle_hashes: raise ValidationError( "Duplicate uncle: {0}".format(encode_hex(uncle.hash)) ) # ensure that the uncle is not one of the canonical chain blocks. if uncle.hash in recent_ancestor_hashes: raise ValidationError( "Uncle {0} cannot be an ancestor of {1}".format( encode_hex(uncle.hash), encode_hex(block.hash))) # ensure that the uncle was built off of one of the canonical chain # blocks. if uncle.parent_hash not in recent_ancestor_hashes or ( uncle.parent_hash == block.header.parent_hash): raise ValidationError( "Uncle's parent {0} is not an ancestor of {1}".format( encode_hex(uncle.parent_hash), encode_hex(block.hash))) # Now perform VM level validation of the uncle self.validate_seal(uncle) try: uncle_parent = self.get_block_header_by_hash(uncle.parent_hash) except HeaderNotFound: raise ValidationError( "Uncle ancestor not found: {0}".format(uncle.parent_hash) ) uncle_vm_class = self.get_vm_class_for_block_number(uncle.block_number) uncle_vm_class.validate_uncle(block, uncle, uncle_parent)
def validate_gaslimit(self, header): parent_header = self.get_block_header_by_hash(header.parent_hash) low_bound, high_bound = compute_gas_limit_bounds(parent_header) if header.gas_limit < low_bound: raise ValidationError( "The gas limit on block {0} is too low: {1}. It must be at least {2}".format( encode_hex(header.hash), header.gas_limit, low_bound)) elif header.gas_limit > high_bound: raise ValidationError( "The gas limit on block {0} is too high: {1}. It must be at most {2}".format( encode_hex(header.hash), header.gas_limit, high_bound))
def _get_new_logs(self): shard_id_topic_hex = encode_hex( self.shard_id.to_bytes(32, byteorder='big')) new_logs = self.log_handler.get_new_logs( address=self.vmc_address, topics=[ encode_hex(self.COLLATION_ADDED_TOPIC), shard_id_topic_hex, ], ) for log in new_logs: yield parse_collation_added_log(log)
def check_pow(block_number, mining_hash, mix_hash, nonce, difficulty): validate_length(mix_hash, 32, title="Mix Hash") validate_length(mining_hash, 32, title="Mining Hash") validate_length(nonce, 8, title="POW Nonce") cache = get_cache(block_number) mining_output = hashimoto_light( block_number, cache, mining_hash, big_endian_to_int(nonce)) if mining_output[b'mix digest'] != mix_hash: raise ValidationError("mix hash mismatch; {0} != {1}".format( encode_hex(mining_output[b'mix digest']), encode_hex(mix_hash))) result = big_endian_to_int(mining_output[b'result']) validate_lte(result, 2**256 // difficulty, title="POW Difficulty")
def run_pre_computation(self, transaction): # Validate the transaction transaction.validate() self.validate_transaction(transaction) gas_fee = transaction.gas * transaction.gas_price with self.mutable_state_db() as state_db: # Buy Gas state_db.delta_balance(transaction.sender, -1 * gas_fee) # Increment Nonce state_db.increment_nonce(transaction.sender) # Setup VM Message message_gas = transaction.gas - transaction.intrinsic_gas if transaction.to == constants.CREATE_CONTRACT_ADDRESS: contract_address = generate_contract_address( transaction.sender, state_db.get_nonce(transaction.sender) - 1, ) data = b'' code = transaction.data else: contract_address = None data = transaction.data code = state_db.get_code(transaction.to) self.logger.info( ("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | " "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s"), encode_hex(transaction.sender), encode_hex(transaction.to), transaction.value, transaction.gas, transaction.gas_price, transaction.s, transaction.r, transaction.v, encode_hex(keccak(transaction.data)), ) return Message( gas=message_gas, to=transaction.to, sender=transaction.sender, value=transaction.value, data=data, code=code, create_address=contract_address, )
def __enter__(self): self.logger.debug( ("COMPUTATION STARTING: gas: %s | from: %s | to: %s | value: %s " "| depth %s | static: %s"), self.msg.gas, encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", ) return self
def build_evm_message(self, transaction): transaction_context = self.get_transaction_context(transaction) gas_fee = transaction.gas * transaction_context.gas_price # Buy Gas self.vm_state.account_db.delta_balance(transaction.sender, -1 * gas_fee) # Increment Nonce self.vm_state.account_db.increment_nonce(transaction.sender) # Setup VM Message message_gas = transaction.gas - transaction.intrinsic_gas if transaction.to == constants.CREATE_CONTRACT_ADDRESS: contract_address = generate_contract_address( transaction.sender, self.vm_state.account_db.get_nonce(transaction.sender) - 1, ) data = b'' code = transaction.data else: contract_address = None data = transaction.data code = self.vm_state.account_db.get_code(transaction.to) self.vm_state.logger.debug( ("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | " "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s"), encode_hex(transaction.sender), encode_hex(transaction.to), transaction.value, transaction.gas, transaction.gas_price, transaction.s, transaction.r, transaction.v, encode_hex(keccak(transaction.data)), ) message = Message( gas=message_gas, to=transaction.to, sender=transaction.sender, value=transaction.value, data=data, code=code, create_address=contract_address, ) return message
def apply_create_message(self): snapshot = self.vm_state.snapshot() # EIP161 nonce incrementation with self.vm_state.state_db() as state_db: state_db.increment_nonce(self.msg.storage_address) computation = self.apply_message() if computation.is_error: self.vm_state.revert(snapshot) return computation else: contract_code = computation.output if contract_code and len(contract_code) >= EIP170_CODE_SIZE_LIMIT: computation._error = OutOfGas( "Contract code size exceeds EIP170 limit of {0}. Got code of " "size: {1}".format( EIP170_CODE_SIZE_LIMIT, len(contract_code), )) self.vm_state.revert(snapshot) elif contract_code: contract_code_gas_cost = len( contract_code) * constants.GAS_CODEDEPOSIT try: computation.gas_meter.consume_gas( contract_code_gas_cost, reason="Write contract code for CREATE", ) except OutOfGas as err: # Different from Frontier: reverts state on gas failure while # writing contract code. computation._error = err self.vm_state.revert(snapshot) else: if self.logger: self.logger.debug( "SETTING CODE: %s -> length: %s | hash: %s", encode_hex(self.msg.storage_address), len(contract_code), encode_hex(keccak(contract_code))) with self.vm_state.state_db() as state_db: state_db.set_code(self.msg.storage_address, contract_code) self.vm_state.commit(snapshot) else: self.vm_state.commit(snapshot) return computation
def run_post_computation(self, transaction, computation): # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.gas_meter.refund_gas(REFUND_SELFDESTRUCT * num_deletions) # Gas Refunds gas_remaining = computation.get_gas_remaining() gas_refunded = computation.get_gas_refund() gas_used = transaction.gas - gas_remaining gas_refund = min(gas_refunded, gas_used // 2) gas_refund_amount = (gas_refund + gas_remaining) * transaction.gas_price if gas_refund_amount: self.logger.debug( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(computation.msg.sender), ) with self.mutable_state_db() as state_db: state_db.delta_balance(computation.msg.sender, gas_refund_amount) # Miner Fees transaction_fee = (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price self.logger.debug( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(self.coinbase), ) with self.mutable_state_db() as state_db: state_db.delta_balance(self.coinbase, transaction_fee) # Process Self Destructs with self.mutable_state_db() as state_db: for account, beneficiary in computation.get_accounts_for_deletion( ): # TODO: need to figure out how we prevent multiple selfdestructs from # the same account and if this is the right place to put this. self.logger.debug('DELETING ACCOUNT: %s', encode_hex(account)) # TODO: this balance setting is likely superflous and can be # removed since `delete_account` does this. state_db.set_balance(account, 0) state_db.delete_account(account) return computation
def apply_create_message(self): snapshot = self.vm_state.snapshot() # EIP161 nonce incrementation with self.vm_state.state_db() as state_db: state_db.increment_nonce(self.msg.storage_address) computation = self.apply_message() if computation.is_error: self.vm_state.revert(snapshot) return computation else: contract_code = computation.output if contract_code and len(contract_code) >= EIP170_CODE_SIZE_LIMIT: computation._error = OutOfGas( "Contract code size exceeds EIP170 limit of {0}. Got code of " "size: {1}".format( EIP170_CODE_SIZE_LIMIT, len(contract_code), ) ) self.vm_state.revert(snapshot) elif contract_code: contract_code_gas_cost = len(contract_code) * constants.GAS_CODEDEPOSIT try: computation.gas_meter.consume_gas( contract_code_gas_cost, reason="Write contract code for CREATE", ) except OutOfGas as err: # Different from Frontier: reverts state on gas failure while # writing contract code. computation._error = err self.vm_state.revert(snapshot) else: if self.logger: self.logger.debug( "SETTING CODE: %s -> length: %s | hash: %s", encode_hex(self.msg.storage_address), len(contract_code), encode_hex(keccak(contract_code)) ) with self.vm_state.state_db() as state_db: state_db.set_code(self.msg.storage_address, contract_code) self.vm_state.commit(snapshot) else: self.vm_state.commit(snapshot) return computation
def __enter__(self): self.logger.debug( ( "COMPUTATION STARTING: gas: %s | from: %s | to: %s | value: %s " "| depth %s | static: %s" ), self.msg.gas, encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", ) return self
def format_block(block: BaseBlock) -> str: return ( "\n\n" "------------------------Block------------------------------------\n" "Number #{b.number:>12} Hash {hash}\n" "-----------------------------------------------------------------\n" ).format(b=block, hash=encode_hex(block.hash))
def get_pending_transaction(self, transaction_hash, transaction_class): try: data = self.db.get(make_transaction_hash_to_data_lookup_key(transaction_hash)) return rlp.decode(data, sedes=transaction_class) except KeyError: raise TransactionNotFound( "Transaction with hash {} not found".format(encode_hex(transaction_hash)))
def __call__(self, computation): computation.consume_gas(self.gas_cost, reason=self.mnemonic) value, start_position, size = computation.stack_pop( num_items=3, type_hint=constants.UINT256, ) computation.extend_memory(start_position, size) insufficient_funds = computation.state.account_db.get_balance( computation.msg.storage_address ) < value stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT if insufficient_funds or stack_too_deep: computation.stack_push(0) return call_data = computation.memory_read(start_position, size) create_msg_gas = self.max_child_gas_modifier( computation.get_gas_remaining() ) computation.consume_gas(create_msg_gas, reason="CREATE") creation_nonce = computation.state.account_db.get_nonce(computation.msg.storage_address) computation.state.account_db.increment_nonce(computation.msg.storage_address) contract_address = generate_contract_address( computation.msg.storage_address, creation_nonce, ) is_collision = computation.state.account_db.account_has_code_or_nonce(contract_address) if is_collision: self.logger.debug( "Address collision while creating contract: %s", encode_hex(contract_address), ) computation.stack_push(0) return child_msg = computation.prepare_child_message( gas=create_msg_gas, to=constants.CREATE_CONTRACT_ADDRESS, value=value, data=b'', code=call_data, create_address=contract_address, ) child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack_push(0) else: computation.stack_push(contract_address) computation.return_gas(child_computation.get_gas_remaining())
def import_block(self, block: BaseBlock, perform_validation: bool = True) -> BaseBlock: """ Imports a complete block. """ try: parent_header = self.get_block_header_by_hash( block.header.parent_hash) except HeaderNotFound: raise ValidationError( "Attempt to import block #{}. Cannot import block {} before importing " "its parent block at {}".format( block.number, block.hash, block.header.parent_hash, )) base_header_for_import = self.create_header_from_parent(parent_header) imported_block = self.get_vm(base_header_for_import).import_block( block) # Validate the imported block. if perform_validation: ensure_imported_block_unchanged(imported_block, block) self.validate_block(imported_block) self.chaindb.persist_block(imported_block) self.logger.debug( 'IMPORTED_BLOCK: number %s | hash %s', imported_block.number, encode_hex(imported_block.hash), ) return imported_block
def import_block(self, block, perform_validation=True): """ Imports a complete block. """ if block.number > self.header.block_number: raise ValidationError( "Attempt to import block #{0}. Cannot import block with number " "greater than current block #{1}.".format( block.number, self.header.block_number, ) ) parent_chain = self.get_chain_at_block_parent(block) imported_block = parent_chain.get_vm().import_block(block) # Validate the imported block. if perform_validation: ensure_imported_block_unchanged(imported_block, block) self.validate_block(imported_block) self.chaindb.persist_block(imported_block) self.header = self.create_header_from_parent(self.get_canonical_head()) self.logger.debug( 'IMPORTED_BLOCK: number %s | hash %s', imported_block.number, encode_hex(imported_block.hash), ) return imported_block
def _decode_header_to_dict( cls, encoded_header: bytes) -> Iterator[Tuple[str, Any]]: if len(encoded_header) != cls.smc_encoded_size: raise ValidationError( "Expected encoded header to be of size: {0}. Got size {1} instead.\n- {2}" .format( cls.smc_encoded_size, len(encoded_header), encode_hex(encoded_header), )) start_indices = accumulate(lambda i, field: i + field[2], cls.fields_with_sizes, 0) field_bounds = sliding_window(2, start_indices) for byte_range, field in zip(field_bounds, cls._meta.fields): start_index, end_index = byte_range field_name, field_type = field field_bytes = encoded_header[start_index:end_index] if field_type == rlp.sedes.big_endian_int: # remove the leading zeros, to avoid `not minimal length` error in deserialization formatted_field_bytes = field_bytes.lstrip(b'\x00') elif field_type == address: formatted_field_bytes = field_bytes[-20:] else: formatted_field_bytes = field_bytes yield field_name, field_type.deserialize(formatted_field_bytes)
def import_block(self, block, perform_validation=True): """ Imports a complete block. """ if block.number > self.header.block_number: raise ValidationError( "Attempt to import block #{0}. Cannot import block with number " "greater than current block #{1}.".format( block.number, self.header.block_number, ) ) parent_chain = self.get_chain_at_block_parent(block) imported_block = parent_chain.get_vm().import_block(block) # Validate the imported block. if perform_validation: ensure_imported_block_unchanged(imported_block, block) self.validate_block(imported_block) self.chaindb.persist_block_to_db(imported_block) self.header = self.create_header_from_parent(self.get_canonical_head()) self.logger.debug( 'IMPORTED_BLOCK: number %s | hash %s', imported_block.number, encode_hex(imported_block.hash), ) return imported_block
def get_transaction_index(self, transaction_hash): try: encoded_key = self.db.get(make_transaction_hash_to_block_lookup_key(transaction_hash)) except KeyError: raise TransactionNotFound( "Transaction {} not found in canonical chain".format(encode_hex(transaction_hash))) transaction_key = rlp.decode(encoded_key, sedes=TransactionKey) return (transaction_key.block_number, transaction_key.index)
def run_post_computation(self, transaction, computation): state_db_cm = functools.partial(self.state_db, access_list=transaction.prefix_list) # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.refund_gas(REFUND_SELFDESTRUCT * num_deletions) # Gas Refunds transaction_fee, gas_refund_amount = computation.compute_transaction_fee_and_refund( ) if gas_refund_amount: self.logger.debug( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(computation.msg.to), ) with state_db_cm() as state_db: state_db.delta_balance(computation.msg.to, gas_refund_amount) # Miner Fees self.logger.debug( 'TRANSACTION FEE: %s', transaction_fee, ) # Process Self Destructs with state_db_cm() as state_db: for account, beneficiary in computation.get_accounts_for_deletion( ): # TODO: need to figure out how we prevent multiple selfdestructs from # the same account and if this is the right place to put this. self.logger.debug('DELETING ACCOUNT: %s', encode_hex(account)) # TODO: this balance setting is likely superflous and can be # removed since `delete_account` does this. state_db.set_balance(account, 0) state_db.delete_account(account) return computation
def get_canonical_transaction(self, transaction_hash): (block_num, index) = self.chaindb.get_transaction_index(transaction_hash) VM = self.get_vm_class_for_block_number(block_num) transaction = self.chaindb.get_transaction_by_index( block_num, index, VM.get_transaction_class(), ) if transaction.hash == transaction_hash: return transaction else: raise TransactionNotFound("Found transaction {} instead of {} in block {} at {}".format( encode_hex(transaction.hash), encode_hex(transaction_hash), block_num, index, ))
def run_pre_computation(self, transaction): state_db_cm = functools.partial(self.state_db, access_list=transaction.prefix_list) # Validate the transaction transaction.validate() self.validate_transaction(transaction) with state_db_cm() as state_db: # Setup VM Message message_gas = transaction.gas - transaction.intrinsic_gas if transaction.code: data = b'' code = transaction.code is_create = True else: data = transaction.data code = state_db.get_code(transaction.to) is_create = False self.logger.info( ("TRANSACTION: to: %s | gas: %s | " "data-hash: %s | code-hash: %s | salt: %s"), encode_hex(transaction.to), transaction.gas, encode_hex(keccak(transaction.data)), encode_hex(keccak(transaction.code)), encode_hex(transaction.salt), ) return ShardingMessage( gas=message_gas, to=transaction.to, sender=ENTRY_POINT, value=0, data=data, code=code, is_create=is_create, access_list=transaction.prefix_list, )
def apply_message(self): snapshot = self.vm_state.snapshot() if self.msg.depth > constants.STACK_DEPTH_LIMIT: raise StackDepthLimit("Stack depth limit reached") if self.msg.should_transfer_value and self.msg.value: with self.vm_state.state_db() as state_db: sender_balance = state_db.get_balance(self.msg.sender) if sender_balance < self.msg.value: raise InsufficientFunds( "Insufficient funds: {0} < {1}".format( sender_balance, self.msg.value)) state_db.delta_balance(self.msg.sender, -1 * self.msg.value) state_db.delta_balance(self.msg.storage_address, self.msg.value) self.logger.debug( "TRANSFERRED: %s from %s -> %s", self.msg.value, encode_hex(self.msg.sender), encode_hex(self.msg.storage_address), ) with self.vm_state.state_db() as state_db: state_db.touch_account(self.msg.storage_address) computation = self.apply_computation( self.vm_state, self.msg, self.opcodes, self.precompiles, ) if computation.is_error: self.vm_state.revert(snapshot) else: self.vm_state.commit(snapshot) return computation
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: if exc_value and isinstance(exc_value, VMError): self.logger.debug( ( "COMPUTATION ERROR: gas: %s | from: %s | to: %s | value: %s | " "depth: %s | static: %s | error: %s" ), self.msg.gas, encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", exc_value, ) self._error = exc_value if self.should_burn_gas: self.consume_gas( self._gas_meter.gas_remaining, reason=" ".join(( "Zeroing gas due to VM Exception:", str(exc_value), )), ) # suppress VM exceptions return True elif exc_type is None: self.logger.debug( ( "COMPUTATION SUCCESS: from: %s | to: %s | value: %s | " "depth: %s | static: %s | gas-used: %s | gas-remaining: %s" ), encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", self.msg.gas - self._gas_meter.gas_remaining, self._gas_meter.gas_remaining, )
def __exit__(self, exc_type, exc_value, traceback): if exc_value and isinstance(exc_value, VMError): self.logger.debug( ( "COMPUTATION ERROR: gas: %s | from: %s | to: %s | value: %s | " "depth: %s | static: %s | error: %s" ), self.msg.gas, encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", exc_value, ) self._error = exc_value if self.should_burn_gas: self.gas_meter.consume_gas( self.gas_meter.gas_remaining, reason=" ".join(( "Zeroing gas due to VM Exception:", str(exc_value), )), ) # suppress VM exceptions return True elif exc_type is None: self.logger.debug( ( "COMPUTATION SUCCESS: from: %s | to: %s | value: %s | " "depth: %s | static: %s | gas-used: %s | gas-remaining: %s" ), encode_hex(self.msg.sender), encode_hex(self.msg.to), self.msg.value, self.msg.depth, "y" if self.msg.is_static else "n", self.msg.gas - self.gas_meter.gas_remaining, self.gas_meter.gas_remaining, )
def _selfdestruct(computation, beneficiary): local_balance = computation.state.account_db.get_balance(computation.msg.storage_address) beneficiary_balance = computation.state.account_db.get_balance(beneficiary) # 1st: Transfer to beneficiary computation.state.account_db.set_balance(beneficiary, local_balance + beneficiary_balance) # 2nd: Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary. computation.state.account_db.set_balance(computation.msg.storage_address, 0) computation.logger.debug( "SELFDESTRUCT: %s (%s) -> %s", encode_hex(computation.msg.storage_address), local_balance, encode_hex(beneficiary), ) # 3rd: Register the account to be deleted computation.register_account_for_deletion(beneficiary) raise Halt('SELFDESTRUCT')
def get_block_header_by_hash(self, block_hash): """ Returns the requested block header as specified by block hash. Raises BlockNotFound if it is not present in the db. """ validate_word(block_hash, title="Block Hash") try: block = self.db.get(block_hash) except KeyError: raise BlockNotFound("No block with hash {0} found".format( encode_hex(block_hash))) return rlp.decode(block, sedes=BlockHeader)
def _selfdestruct(computation, beneficiary): with computation.vm_state.state_db() as state_db: local_balance = state_db.get_balance(computation.msg.storage_address) beneficiary_balance = state_db.get_balance(beneficiary) # 1st: Transfer to beneficiary state_db.set_balance(beneficiary, local_balance + beneficiary_balance) # 2nd: Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary. state_db.set_balance(computation.msg.storage_address, 0) computation.vm_state.logger.debug( "SELFDESTRUCT: %s (%s) -> %s", encode_hex(computation.msg.storage_address), local_balance, encode_hex(beneficiary), ) # 3rd: Register the account to be deleted computation.register_account_for_deletion(beneficiary) raise Halt('SELFDESTRUCT')
def apply_create_message(self): snapshot = self.vm_state.snapshot() computation = self.apply_message() if computation.is_error: self.vm_state.revert(snapshot) return computation else: contract_code = computation.output if contract_code: contract_code_gas_cost = len(contract_code) * constants.GAS_CODEDEPOSIT try: computation.gas_meter.consume_gas( contract_code_gas_cost, reason="Write contract code for CREATE", ) except OutOfGas as err: # Different from Frontier: reverts state on gas failure while # writing contract code. computation._error = err self.vm_state.revert(snapshot) else: if self.logger: self.logger.debug( "SETTING CODE: %s -> length: %s | hash: %s", encode_hex(self.msg.storage_address), len(contract_code), encode_hex(keccak(contract_code)) ) with self.vm_state.state_db() as state_db: state_db.set_code(self.msg.storage_address, contract_code) self.vm_state.commit(snapshot) else: self.vm_state.commit(snapshot) return computation
def execute_transaction(self, transaction): computation = _execute_frontier_transaction(self, transaction) # # EIP161 state clearing # touched_accounts = collect_touched_accounts(computation) with self.state_db() as state_db: for account in touched_accounts: if state_db.account_exists(account) and state_db.account_is_empty(account): self.logger.debug( "CLEARING EMPTY ACCOUNT: %s", encode_hex(account), ) state_db.delete_account(account) return computation
def sstore(computation): slot, value = computation.stack.pop(num_items=2, type_hint=constants.UINT256) with computation.vm_state.state_db(read_only=True) as state_db: current_value = state_db.get_storage( address=computation.msg.storage_address, slot=slot, ) is_currently_empty = not bool(current_value) is_going_to_be_empty = not bool(value) if is_currently_empty: gas_refund = 0 elif is_going_to_be_empty: gas_refund = constants.REFUND_SCLEAR else: gas_refund = 0 if is_currently_empty and is_going_to_be_empty: gas_cost = constants.GAS_SRESET elif is_currently_empty: gas_cost = constants.GAS_SSET elif is_going_to_be_empty: gas_cost = constants.GAS_SRESET else: gas_cost = constants.GAS_SRESET computation.gas_meter.consume_gas(gas_cost, reason="SSTORE: {0}[{1}] -> {2} ({3})".format( encode_hex(computation.msg.storage_address), slot, value, current_value, )) if gas_refund: computation.gas_meter.refund_gas(gas_refund) with computation.vm_state.state_db() as state_db: state_db.set_storage( address=computation.msg.storage_address, slot=slot, value=value, )
def _execute_frontier_transaction(vm_state, transaction): # Reusable for other forks # # 1) Pre Computation # # Validate the transaction transaction.validate() vm_state.validate_transaction(transaction) gas_fee = transaction.gas * transaction.gas_price with vm_state.state_db() as state_db: # Buy Gas state_db.delta_balance(transaction.sender, -1 * gas_fee) # Increment Nonce state_db.increment_nonce(transaction.sender) # Setup VM Message message_gas = transaction.gas - transaction.intrinsic_gas if transaction.to == constants.CREATE_CONTRACT_ADDRESS: contract_address = generate_contract_address( transaction.sender, state_db.get_nonce(transaction.sender) - 1, ) data = b'' code = transaction.data else: contract_address = None data = transaction.data code = state_db.get_code(transaction.to) vm_state.logger.info( ( "TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | " "gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s" ), encode_hex(transaction.sender), encode_hex(transaction.to), transaction.value, transaction.gas, transaction.gas_price, transaction.s, transaction.r, transaction.v, encode_hex(keccak(transaction.data)), ) message = Message( gas=message_gas, to=transaction.to, sender=transaction.sender, value=transaction.value, data=data, code=code, create_address=contract_address, ) transaction_context = vm_state.get_transaction_context_class()( gas_price=transaction.gas_price, origin=transaction.sender, ) # # 2) Apply the message to the VM. # if message.is_create: with vm_state.state_db(read_only=True) as state_db: is_collision = state_db.account_has_code_or_nonce(contract_address) if is_collision: # The address of the newly created contract has *somehow* collided # with an existing contract address. computation = vm_state.get_computation(message, transaction_context) computation._error = ContractCreationCollision( "Address collision while creating contract: {0}".format( encode_hex(contract_address), ) ) vm_state.logger.debug( "Address collision while creating contract: %s", encode_hex(contract_address), ) else: computation = vm_state.get_computation( message, transaction_context, ).apply_create_message() else: computation = vm_state.get_computation(message, transaction_context).apply_message() # # 2) Post Computation # # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.gas_meter.refund_gas(REFUND_SELFDESTRUCT * num_deletions) # Gas Refunds gas_remaining = computation.get_gas_remaining() gas_refunded = computation.get_gas_refund() gas_used = transaction.gas - gas_remaining gas_refund = min(gas_refunded, gas_used // 2) gas_refund_amount = (gas_refund + gas_remaining) * transaction.gas_price if gas_refund_amount: vm_state.logger.debug( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(message.sender), ) with vm_state.state_db() as state_db: state_db.delta_balance(message.sender, gas_refund_amount) # Miner Fees transaction_fee = (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price vm_state.logger.debug( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(vm_state.coinbase), ) with vm_state.state_db() as state_db: state_db.delta_balance(vm_state.coinbase, transaction_fee) # Process Self Destructs with vm_state.state_db() as state_db: for account, beneficiary in computation.get_accounts_for_deletion(): # TODO: need to figure out how we prevent multiple selfdestructs from # the same account and if this is the right place to put this. vm_state.logger.debug('DELETING ACCOUNT: %s', encode_hex(account)) # TODO: this balance setting is likely superflous and can be # removed since `delete_account` does this. state_db.set_balance(account, 0) state_db.delete_account(account) return computation
def __call__(self, computation): computation.gas_meter.consume_gas(self.gas_cost, reason=self.mnemonic) value, start_position, size = computation.stack.pop( num_items=3, type_hint=constants.UINT256, ) computation.extend_memory(start_position, size) with computation.vm_state.state_db(read_only=True) as state_db: insufficient_funds = state_db.get_balance( computation.msg.storage_address) < value stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT if insufficient_funds or stack_too_deep: computation.stack.push(0) return call_data = computation.memory.read(start_position, size) create_msg_gas = self.max_child_gas_modifier( computation.gas_meter.gas_remaining ) computation.gas_meter.consume_gas(create_msg_gas, reason="CREATE") with computation.vm_state.state_db() as state_db: creation_nonce = state_db.get_nonce(computation.msg.storage_address) state_db.increment_nonce(computation.msg.storage_address) contract_address = generate_contract_address( computation.msg.storage_address, creation_nonce, ) is_collision = state_db.account_has_code_or_nonce(contract_address) if is_collision: computation.vm_state.logger.debug( "Address collision while creating contract: %s", encode_hex(contract_address), ) computation.stack.push(0) return child_msg = computation.prepare_child_message( gas=create_msg_gas, to=constants.CREATE_CONTRACT_ADDRESS, value=value, data=b'', code=call_data, create_address=contract_address, ) child_computation = computation.apply_child_computation(child_msg) if child_computation.is_error: computation.stack.push(0) else: computation.stack.push(contract_address) computation.gas_meter.return_gas(child_computation.gas_meter.gas_remaining)
def hex_hash(self): return encode_hex(self.hash)
def test_round_trip_with_hex_string_start(value): intermediate_value = decode_hex(value) round_trip_value = encode_hex(intermediate_value) assert round_trip_value == value
def test_basic_hexadecimal_encoding(value, expected): actual = encode_hex(value) assert actual == expected
def __repr__(self): return '<BlockHeader #{0} {1}>'.format( self.block_number, encode_hex(self.hash)[2:10], )