import options, eth_common, ranges, chronicles, eth_bloom, nimcrypto, ../db/[db_chain, state_db], ../utils, ../constants, ../transaction, ../vm_state, ../vm_types, ../vm_state_transactions, ../vm/[computation, interpreter_dispatch, message], ../vm/interpreter/vm_forks proc contractCall(t: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): UInt256 = # TODO: this function body was copied from GST with it's comments and TODOs. # Right now it's main purpose is to produce VM tracing when syncing block with # contract call. At later stage, this proc together with applyCreateTransaction # and processTransaction need to be restructured. # TODO: replace with cachingDb or similar approach; necessary # when calls/subcalls/etc come in, too. var db = vmState.accountDb let storageRoot = db.getStorageRoot(t.to) var computation = setupComputation(vmState, t, sender, forkOverride) # contract creation transaction.to == 0, so ensure happens after db.addBalance(t.to, t.value) let header = vmState.blockHeader let gasCost = t.gasLimit.u256 * t.gasPrice.u256 if execComputation(computation): let gasRemaining = computation.gasMeter.gasRemaining.u256 gasRefunded = computation.gasMeter.gasRefunded.u256 gasUsed = t.gasLimit.u256 - gasRemaining gasRefund = min(gasRefunded, gasUsed div 2) gasRefundAmount = (gasRefund + gasRemaining) * t.gasPrice.u256 db.addBalance(sender, gasRefundAmount) return (t.gasLimit.u256 - gasRemaining - gasRefund) * t.gasPrice.u256 else: db.subBalance(t.to, t.value) db.addBalance(sender, t.value) db.setStorageRoot(t.to, storageRoot) if computation.tracingEnabled: computation.traceError() vmState.clearLogs() return t.gasLimit.u256 * t.gasPrice.u256 proc processTransaction*(t: Transaction, sender: EthAddress, vmState: BaseVMState): UInt256 = ## Process the transaction, write the results to db. ## Returns amount of ETH to be rewarded to miner trace "Sender", sender trace "txHash", rlpHash = t.rlpHash var db = vmState.accountDb # Inct nonce: db.setNonce(sender, db.getNonce(sender) + 1) var transactionFailed = false #t.dump # TODO: combine/refactor re validate let upfrontGasCost = t.gasLimit.u256 * t.gasPrice.u256 let upfrontCost = upfrontGasCost + t.value var balance = db.getBalance(sender) if balance < upfrontCost: if balance <= upfrontGasCost: result = balance balance = 0.u256 else: result = upfrontGasCost balance -= upfrontGasCost transactionFailed = true else: balance -= upfrontCost db.setBalance(sender, balance) if transactionFailed: return var gasUsed = t.payload.intrinsicGas.GasInt # += 32000 appears in Homestead when contract create if gasUsed > t.gasLimit: debug "Transaction failed. Out of gas." transactionFailed = true else: if t.isContractCreation: # TODO: re-derive sender in callee for cleaner interface, perhaps return applyCreateTransaction(t, vmState, sender) else: let code = db.getCode(t.to) if code.len == 0: # Value transfer trace "Transfer", value = t.value, sender, to = t.to db.addBalance(t.to, t.value) else: # Contract call trace "Contract call" trace "Transaction", sender, to = t.to, value = t.value, hasCode = code.len != 0 #let msg = newMessage(t.gasLimit, t.gasPrice, t.to, sender, t.value, t.payload, code.toSeq) # TODO: Run the vm with proper fork return contractCall(t, vmState, sender) if gasUsed > t.gasLimit: gasUsed = t.gasLimit var refund = (t.gasLimit - gasUsed).u256 * t.gasPrice.u256 if transactionFailed: refund += t.value db.addBalance(sender, refund) return gasUsed.u256 * t.gasPrice.u256 type # TODO: these types need to be removed # once eth_bloom and eth_common sync'ed Bloom = eth_common.BloomFilter LogsBloom = eth_bloom.BloomFilter # TODO: move these three receipt procs below somewhere else more appropriate func logsBloom(logs: openArray[Log]): LogsBloom = for log in logs: result.incl log.address for topic in log.topics: result.incl topic func createBloom*(receipts: openArray[Receipt]): Bloom = var bloom: LogsBloom for receipt in receipts: bloom.value = bloom.value or logsBloom(receipt.logs).value result = bloom.value.toByteArrayBE proc makeReceipt(vmState: BaseVMState, cumulativeGasUsed: GasInt, fork = FkFrontier): Receipt = if fork < FkByzantium: result.stateRootOrStatus = hashOrStatus(vmState.accountDb.rootHash) else: # TODO: post byzantium fork use status instead of rootHash let vmStatus = true # success or failure result.stateRootOrStatus = hashOrStatus(vmStatus) result.cumulativeGasUsed = cumulativeGasUsed result.logs = vmState.getAndClearLogEntries() result.bloom = logsBloom(result.logs).value.toByteArrayBE proc processBlock*(chainDB: BaseChainDB, head, header: BlockHeader, body: BlockBody, vmState: BaseVMState): ValidationResult = let blockReward = 5.u256 * pow(10.u256, 18) # 5 ETH if body.transactions.calcTxRoot != header.txRoot: debug "Mismatched txRoot", blockNumber=header.blockNumber return ValidationResult.Error var stateDb = vmState.accountDb if header.txRoot != BLANK_ROOT_HASH: if body.transactions.len == 0: debug "No transactions in body", blockNumber=header.blockNumber return ValidationResult.Error else: trace "Has transactions", blockNumber = header.blockNumber, blockHash = header.blockHash vmState.receipts = newSeq[Receipt](body.transactions.len) var cumulativeGasUsed = GasInt(0) for txIndex, tx in body.transactions: var sender: EthAddress if tx.getSender(sender): let txFee = processTransaction(tx, sender, vmState) # perhaps this can be altered somehow # or processTransaction return only gasUsed # a `div` here is ugly and possibly div by zero let gasUsed = (txFee div tx.gasPrice.u256).truncate(GasInt) cumulativeGasUsed += gasUsed # miner fee stateDb.addBalance(header.coinbase, txFee) else: debug "Could not get sender", txIndex, tx return ValidationResult.Error vmState.receipts[txIndex] = makeReceipt(vmState, cumulativeGasUsed) var mainReward = blockReward if header.ommersHash != EMPTY_UNCLE_HASH: let h = chainDB.persistUncles(body.uncles) if h != header.ommersHash: debug "Uncle hash mismatch" return ValidationResult.Error for uncle in body.uncles: var uncleReward = uncle.blockNumber + 8.u256 uncleReward -= header.blockNumber uncleReward = uncleReward * blockReward uncleReward = uncleReward div 8.u256 stateDb.addBalance(uncle.coinbase, uncleReward) mainReward += blockReward div 32.u256 # Reward beneficiary stateDb.addBalance(header.coinbase, mainReward) if header.stateRoot != stateDb.rootHash: error "Wrong state root in block", blockNumber=header.blockNumber, expected=header.stateRoot, actual=stateDb.rootHash, arrivedFrom=chainDB.getCanonicalHead().stateRoot # this one is a show stopper until we are confident in our VM's # compatibility with the main chain return ValidationResult.Error let bloom = createBloom(vmState.receipts) if header.bloom != bloom: debug "wrong bloom in block", blockNumber=header.blockNumber return ValidationResult.Error let receiptRoot = calcReceiptRoot(vmState.receipts) if header.receiptRoot != receiptRoot: debug "wrong receiptRoot in block", blockNumber=header.blockNumber, actual=receiptRoot, expected=header.receiptRoot return ValidationResult.Error