import ../db/[db_chain, state_db], eth_common, chronicles, ../vm_state, ../vm_types, ../transaction, ranges, ../vm/[computation, interpreter_dispatch, message], ../constants, stint, nimcrypto, ../utils/addresses, ../vm_state_transactions, eth_trie/memdb, eth_trie, rlp, sugar type Chain* = ref object of AbstractChainDB db: BaseChainDB proc newChain*(db: BaseChainDB): Chain = result.new result.db = db method genesisHash*(c: Chain): KeccakHash = c.db.getBlockHash(0.toBlockNumber) method getBlockHeader*(c: Chain, b: HashOrNum, output: var BlockHeader): bool = case b.isHash of true: c.db.getBlockHeader(b.hash, output) else: c.db.getBlockHeader(b.number, output) method getBestBlockHeader*(c: Chain): BlockHeader = c.db.getCanonicalHead() method getSuccessorHeader*(c: Chain, h: BlockHeader, output: var BlockHeader): bool = let n = h.blockNumber + 1 c.db.getBlockHeader(n, output) method getBlockBody*(c: Chain, blockHash: KeccakHash): BlockBodyRef = result = nil proc processTransaction(db: var AccountStateDB, t: Transaction, sender: EthAddress, head: BlockHeader, chainDB: BaseChainDB): UInt256 = ## Process the transaction, write the results to db. ## Returns amount of ETH to be rewarded to miner echo "Sender: ", sender echo "txHash: ", t.rlpHash # 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 if t.isContractCreation: # gasUsed += 32000 # This appears in Homestead. echo "Contract creation" # TODO: check the 200x constant up front if gasUsed > t.gasLimit: echo "Transaction failed. Out of gas." transactionFailed = true else: if t.isContractCreation: # TODO: split out into separate function?; interleaving the cases obfuscates let vmState = newBaseVMState(head, chainDB) # TODO: setupComputation refactoring let contractAddress = generateAddress(sender, t.accountNonce) let msg = newMessage(t.gasLimit - gasUsed, t.gasPrice, t.to, sender, t.value, @[], t.payload, options = newMessageOptions(origin = sender, createAddress = contractAddress)) var c = newBaseComputation(vmState, head.blockNumber, msg) if execComputation(c, vmState): db.addBalance(contractAddress, t.value) # XXX: copy/pasted from GST fixture # TODO: more merging/refactoring/etc # also a couple lines can collapse because variable used once # once verified in GST fixture let gasRemaining = c.gasMeter.gasRemaining.u256 gasRefunded = c.gasMeter.gasRefunded.u256 gasUsed2 = t.gasLimit.u256 - gasRemaining gasRefund = min(gasRefunded, gasUsed2 div 2) gasRefundAmount = (gasRefund + gasRemaining) * t.gasPrice.u256 #echo "gasRemaining is ", gasRemaining, " and gasRefunded = ", gasRefunded, " and gasUsed2 = ", gasUsed2, " and gasRefund = ", gasRefund, " and gasRefundAmount = ", gasRefundAmount var codeCost = 200 * c.output.len # This apparently is not supposed to actually consume the gas, just be able to, # for purposes of accounting. Py-EVM apparently does consume the gas, but it is # not matching observed blockchain balances if consumeGas is called. if gasRemaining >= codeCost.u256: db.setCode(contractAddress, c.output.toRange) else: # XXX: Homestead behaves differently; reverts state on gas failure # https://github.com/ethereum/py-evm/blob/master/eth/vm/forks/homestead/computation.py codeCost = 0 db.setCode(contractAddress, ByteRange()) db.addBalance(sender, (t.gasLimit.u256 - gasUsed2 - codeCost.u256)*t.gasPrice.u256) return (gasUsed2 + codeCost.u256) * t.gasPrice.u256 else: # FIXME: don't do this revert, but rather only subBalance correctly # the if transactionfailed at end is what is supposed to pick it up db.addBalance(sender, t.value) echo "isError: ", c.isError return upfrontGasCost else: let code = db.getCode(t.to) if code.len == 0: # Value transfer echo "Transfer ", t.value, " from ", sender, " to ", t.to db.addBalance(t.to, t.value) else: # Contract call echo "Contract call" debug "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 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 proc calcTxRoot(transactions: openarray[Transaction]): Hash256 = var tr = initHexaryTrie(trieDB(newMemDB())) for i, t in transactions: tr.put(rlp.encode(i).toRange, rlp.encode(t).toRange) return tr.rootHash method persistBlocks*(c: Chain, headers: openarray[BlockHeader], bodies: openarray[BlockBody]) = # Run the VM here assert(headers.len == bodies.len) let blockReward = 5.u256 * pow(10.u256, 18) # 5 ETH echo "Persisting blocks: ", headers[0].blockNumber, " - ", headers[^1].blockNumber for i in 0 ..< headers.len: let head = c.db.getCanonicalHead() assert(head.blockNumber == headers[i].blockNumber - 1) var stateDb = newAccountStateDB(c.db.db, head.stateRoot) var gasReward = 0.u256 assert(bodies[i].transactions.calcTxRoot == headers[i].txRoot) if headers[i].txRoot != BLANK_ROOT_HASH: # assert(head.blockNumber == headers[i].blockNumber - 1) let vmState = newBaseVMState(head, c.db) assert(bodies[i].transactions.len != 0) if bodies[i].transactions.len != 0: echo "block: ", headers[i].blockNumber echo "h: ", headers[i].blockHash for t in bodies[i].transactions: var sender: EthAddress if t.getSender(sender): gasReward += processTransaction(stateDb, t, sender, head, c.db) else: assert(false, "Could not get sender") var mainReward = blockReward + gasReward #echo "mainReward = ", mainReward , " with blockReward = ", blockReward, " and gasReward = ", gasReward if headers[i].ommersHash != EMPTY_UNCLE_HASH: let h = c.db.persistUncles(bodies[i].uncles) assert(h == headers[i].ommersHash) for u in 0 ..< bodies[i].uncles.len: var uncleReward = bodies[i].uncles[u].blockNumber + 8.u256 uncleReward -= headers[i].blockNumber uncleReward = uncleReward * blockReward uncleReward = uncleReward div 8.u256 stateDb.addBalance(bodies[i].uncles[u].coinbase, uncleReward) mainReward += blockReward div 32.u256 # Reward beneficiary stateDb.addBalance(headers[i].coinbase, mainReward) if headers[i].stateRoot != stateDb.rootHash: echo "Wrong state root in block ", headers[i].blockNumber, ". Expected: ", headers[i].stateRoot, ", Actual: ", stateDb.rootHash, " arrived from ", c.db.getCanonicalHead().stateRoot assert(headers[i].stateRoot == stateDb.rootHash) discard c.db.persistHeaderToDb(headers[i]) assert(c.db.getCanonicalHead().blockHash == headers[i].blockHash)