def test_vm_fixtures(fixture, vm_class, computation_getter): chaindb = ChainDB(get_db_backend()) header = BlockHeader( coinbase=fixture['env']['currentCoinbase'], difficulty=fixture['env']['currentDifficulty'], block_number=fixture['env']['currentNumber'], gas_limit=fixture['env']['currentGasLimit'], timestamp=fixture['env']['currentTimestamp'], ) vm = vm_class(header=header, chaindb=chaindb) state = vm.state with state.mutable_state_db() as state_db: setup_state_db(fixture['pre'], state_db) code = state_db.get_code(fixture['exec']['address']) # Update state_root manually vm.block.header.state_root = state.state_root message = Message( to=fixture['exec']['address'], sender=fixture['exec']['caller'], value=fixture['exec']['value'], data=fixture['exec']['data'], code=code, gas=fixture['exec']['gas'], ) transaction_context = BaseTransactionContext( origin=fixture['exec']['origin'], gas_price=fixture['exec']['gasPrice'], ) computation = vm.state.get_computation( message, transaction_context).apply_computation( vm.state, message, transaction_context, ) # Update state_root manually vm.block.header.state_root = computation.state.state_root if 'post' in fixture: # # Success checks # assert not computation.is_error log_entries = computation.get_log_entries() if 'logs' in fixture: actual_logs_hash = hash_log_entries(log_entries) expected_logs_hash = fixture['logs'] assert expected_logs_hash == actual_logs_hash elif log_entries: raise AssertionError("Got log entries: {0}".format(log_entries)) expected_output = fixture['out'] assert computation.output == expected_output gas_meter = computation._gas_meter expected_gas_remaining = fixture['gas'] actual_gas_remaining = gas_meter.gas_remaining gas_delta = actual_gas_remaining - expected_gas_remaining assert gas_delta == 0, "Gas difference: {0}".format(gas_delta) call_creates = fixture.get('callcreates', []) assert len(computation.children) == len(call_creates) call_creates = fixture.get('callcreates', []) for child_computation, created_call in zip(computation.children, call_creates): to_address = created_call['destination'] data = created_call['data'] gas_limit = created_call['gasLimit'] value = created_call['value'] assert child_computation.msg.to == to_address assert data == child_computation.msg.data or child_computation.msg.code assert gas_limit == child_computation.msg.gas assert value == child_computation.msg.value post_state = fixture['post'] else: # # Error checks # assert computation.is_error assert isinstance(computation._error, VMError) post_state = fixture['pre'] verify_state_db(post_state, vm.state.read_only_state_db)
def _execute_frontier_transaction(vm, transaction): # # 1) Pre Computation # # Validate the transaction transaction.validate() vm.validate_transaction(transaction) gas_fee = transaction.gas * transaction.gas_price with vm.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.intrensic_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.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, gas_price=transaction.gas_price, to=transaction.to, sender=transaction.sender, value=transaction.value, data=data, code=code, create_address=contract_address, ) # # 2) Apply the message to the VM. # if message.is_create: with vm.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 = Computation(vm, message) computation._error = ContractCreationCollision( "Address collision while creating contract: {0}".format( encode_hex(contract_address), ) ) vm.logger.debug( "Address collision while creating contract: %s", encode_hex(contract_address), ) else: computation = vm.apply_create_message(message) else: computation = vm.apply_message(message) # # 2) Post Computation # # Self Destruct Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.gas_meter.refund_gas(constants.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.logger.debug( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(message.sender), ) with vm.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.logger.debug( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(vm.block.header.coinbase), ) with vm.state_db() as state_db: state_db.delta_balance(vm.block.header.coinbase, transaction_fee) # Process Self Destructs with vm.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.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 _execute_frontier_transaction(vm, transaction): # # 1) Pre Computation # # Validate the transaction transaction.validate() vm.validate_transaction(transaction) gas_cost = transaction.gas * transaction.gas_price with vm.state_db() as state_db: sender_balance = state_db.get_balance(transaction.sender) # Buy Gas state_db.set_balance(transaction.sender, sender_balance - gas_cost) # Increment Nonce state_db.increment_nonce(transaction.sender) # Setup VM Message message_gas = transaction.gas - transaction.intrensic_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) if vm.logger: vm.logger.info( ("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | " "gas-price: %s | s: %s | r: %s | v: %s | data: %s"), encode_hex(transaction.sender), encode_hex(transaction.to), transaction.value, transaction.gas, transaction.gas_price, transaction.s, transaction.r, transaction.v, encode_hex(transaction.data), ) message = Message( gas=message_gas, gas_price=transaction.gas_price, to=transaction.to, sender=transaction.sender, value=transaction.value, data=data, code=code, create_address=contract_address, ) # # 2) Apply the message to the VM. # if message.is_create: computation = vm.apply_create_message(message) else: computation = vm.apply_message(message) # # 2) Post Computation # if computation.error: # Miner Fees transaction_fee = transaction.gas * transaction.gas_price if vm.logger: vm.logger.debug('TRANSACTION FEE: %s', transaction_fee) with vm.state_db() as state_db: coinbase_balance = state_db.get_balance(vm.block.header.coinbase) state_db.set_balance( vm.block.header.coinbase, coinbase_balance + transaction_fee, ) else: # Suicide Refunds num_deletions = len(computation.get_accounts_for_deletion()) if num_deletions: computation.gas_meter.refund_gas(constants.REFUND_SUICIDE * 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: if vm.logger: vm.logger.debug( 'TRANSACTION REFUND: %s -> %s', gas_refund_amount, encode_hex(message.sender), ) with vm.state_db() as state_db: sender_balance = state_db.get_balance(message.sender) state_db.set_balance(message.sender, sender_balance + gas_refund_amount) # Miner Fees transaction_fee = (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price if vm.logger: vm.logger.debug( 'TRANSACTION FEE: %s -> %s', transaction_fee, encode_hex(vm.block.header.coinbase), ) with vm.state_db() as state_db: coinbase_balance = state_db.get_balance(vm.block.header.coinbase) state_db.set_balance( vm.block.header.coinbase, coinbase_balance + transaction_fee, ) # Suicides for account, beneficiary in computation.get_accounts_for_deletion(): # TODO: need to figure out how we prevent multiple suicides from # the same account and if this is the right place to put this. if vm.logger is not None: vm.logger.debug('DELETING ACCOUNT: %s', encode_hex(account)) with vm.state_db() as state_db: state_db.set_balance(account, 0) state_db.delete_account(account) return computation