nimbus-eth1/nimbus/p2p/executor.nim

217 lines
7.9 KiB
Nim

import options,
eth/[common, bloom], ranges, chronicles, 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.getGasRefund().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
# this proc should not be here, we need to refactor
# processTransaction
proc isPrecompiles*(address: EthAddress): bool {.inline.} =
for i in 0..18:
if address[i] != 0: return
result = address[19] >= 1.byte and address[19] <= 8.byte
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 and not isPrecompiles(t.to):
# 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 = 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, 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