import options, sets, eth/[common, bloom], stew/ranges, chronicles, nimcrypto, ../db/[db_chain, state_db], ../utils, ../constants, ../transaction, ../vm_state, ../vm_types, ../vm_state_transactions, ../vm/[computation, message], ../vm/interpreter/vm_forks, ./dao proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt = ## Process the transaction, write the results to db. ## Returns amount of ETH to be rewarded to miner trace "Sender", sender trace "txHash", rlpHash = tx.rlpHash if fork >= FkSpurious: vmState.touchedAccounts.incl(vmState.blockHeader.coinbase) var gasUsed = tx.gasLimit var coinBaseSuicide = false block: if vmState.cumulativeGasUsed + gasUsed > vmState.blockHeader.gasLimit: debug "invalid tx: block header gasLimit reached", blockGasLimit=vmState.blockHeader.gasLimit, gasUsed=gasUsed, txGasLimit=tx.gasLimit gasUsed = 0 break let upfrontGasCost = tx.gasLimit.u256 * tx.gasPrice.u256 var balance = vmState.readOnlyStateDb().getBalance(sender) if balance < upfrontGasCost: break let recipient = tx.getRecipient() let isCollision = vmState.readOnlyStateDb().hasCodeOrNonce(recipient) var computation = setupComputation(vmState, tx, sender, recipient, fork) if computation.isNil: # OOG in setupComputation gasUsed = 0 break vmState.mutateStateDB: db.incNonce(sender) db.subBalance(sender, upfrontGasCost) if tx.isContractCreation and isCollision: break execComputation(computation) if not computation.shouldBurnGas: gasUsed = computation.refundGas(tx, sender) coinBaseSuicide = computation.isSuicided(vmState.blockHeader.coinbase) vmState.cumulativeGasUsed += gasUsed # miner fee vmState.mutateStateDB: if not coinBaseSuicide: let txFee = gasUsed.u256 * tx.gasPrice.u256 db.addBalance(vmState.blockHeader.coinbase, txFee) else: db.addBalance(vmState.blockHeader.coinbase, 0.u256) # EIP158 state clearing for account in vmState.touchedAccounts: if db.accountExists(account) and db.isEmptyAccount(account): debug "state clearing", account db.deleteAccount(account) vmState.accountDb.updateOriginalRoot() result = gasUsed type # TODO: these types need to be removed # once eth/bloom and eth/common sync'ed Bloom = common.BloomFilter LogsBloom = 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, fork = FkFrontier): Receipt = if fork < FkByzantium: result.stateRootOrStatus = hashOrStatus(vmState.accountDb.rootHash) else: result.stateRootOrStatus = hashOrStatus(vmState.status) result.cumulativeGasUsed = vmState.cumulativeGasUsed result.logs = vmState.getAndClearLogEntries() result.bloom = logsBloom(result.logs).value.toByteArrayBE func eth(n: int): Uint256 {.compileTime.} = n.u256 * pow(10.u256, 18) const eth5 = 5.eth eth3 = 3.eth eth2 = 2.eth blockRewards*: array[Fork, Uint256] = [ eth5, # FkFrontier eth5, # FkThawing eth5, # FkHomestead eth5, # FkDao eth5, # FkTangerine eth5, # FkSpurious eth3, # FkByzantium eth2, # FkConstantinople eth2 # FkIstanbul ] proc processBlock*(chainDB: BaseChainDB, header: BlockHeader, body: BlockBody, vmState: BaseVMState): ValidationResult = if chainDB.config.daoForkSupport and header.blockNumber == chainDB.config.daoForkBlock: vmState.mutateStateDB: db.applyDAOHardFork() if body.transactions.calcTxRoot != header.txRoot: debug "Mismatched txRoot", blockNumber=header.blockNumber return ValidationResult.Error let fork = vmState.blockNumber.toFork 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) vmState.cumulativeGasUsed = 0 for txIndex, tx in body.transactions: var sender: EthAddress if tx.getSender(sender): let gasUsed = processTransaction(tx, sender, vmState, fork) else: debug "Could not get sender", txIndex, tx return ValidationResult.Error vmState.receipts[txIndex] = makeReceipt(vmState, fork) let blockReward = blockRewards[fork] 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.u256 + 8.u256 uncleReward -= header.blockNumber.u256 uncleReward = uncleReward * blockReward uncleReward = uncleReward div 8.u256 vmState.mutateStateDB: db.addBalance(uncle.coinbase, uncleReward) mainReward += blockReward div 32.u256 # Reward beneficiary vmState.mutateStateDB: db.addBalance(header.coinbase, mainReward) let stateDb = vmState.accountDb 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