Merge branch 'cleanups' of https://github.com/jangko/nimbus into jangko-cleanups

This commit is contained in:
Ștefan Talpalaru 2019-03-21 15:25:40 +01:00
commit 3c6ea4ac9c
No known key found for this signature in database
GPG Key ID: CBF7934204F1B6F9
10 changed files with 108 additions and 183 deletions

View File

@ -1,5 +1,5 @@
import ../db/[db_chain, state_db], eth/common, chronicles, ../vm_state, ../vm_types, ../transaction, ranges, import ../db/[db_chain, state_db], eth/common, chronicles, ../vm_state, ../vm_types, ../transaction, ranges,
../vm/[computation, interpreter_dispatch, message], ../constants, stint, nimcrypto, ../vm/[computation, message], ../constants, stint, nimcrypto,
../vm_state_transactions, sugar, ../utils, eth/trie/db, ../tracer, ./executor ../vm_state_transactions, sugar, ../utils, eth/trie/db, ../tracer, ./executor
type type

View File

@ -3,7 +3,7 @@ import options,
../db/[db_chain, state_db], ../db/[db_chain, state_db],
../utils, ../constants, ../transaction, ../utils, ../constants, ../transaction,
../vm_state, ../vm_types, ../vm_state_transactions, ../vm_state, ../vm_types, ../vm_state_transactions,
../vm/[computation, interpreter_dispatch, message], ../vm/[computation, message],
../vm/interpreter/vm_forks ../vm/interpreter/vm_forks
proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, forkOverride=none(Fork)): GasInt = proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, forkOverride=none(Fork)): GasInt =
@ -12,18 +12,6 @@ proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMSta
trace "Sender", sender trace "Sender", sender
trace "txHash", rlpHash = tx.rlpHash trace "txHash", rlpHash = tx.rlpHash
# TODO: we have identical `fork` code in setupComputation.
# at later stage, we need to get rid of it
# and apply changes in eth_*, debug_* RPC,
# macro assembler and premix tool set.
# at every place where setupComputation and
# processTransaction are used.
let fork =
if forkOverride.isSome:
forkOverride.get
else:
vmState.blockNumber.toFork
let upfrontGasCost = tx.gasLimit.u256 * tx.gasPrice.u256 let upfrontGasCost = tx.gasLimit.u256 * tx.gasPrice.u256
var balance = vmState.readOnlyStateDb().getBalance(sender) var balance = vmState.readOnlyStateDb().getBalance(sender)
if balance < upfrontGasCost: if balance < upfrontGasCost:
@ -43,24 +31,10 @@ proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMSta
if tx.isContractCreation and isCollision: if tx.isContractCreation and isCollision:
return tx.gasLimit return tx.gasLimit
var snapshot = vmState.snapshot()
defer: snapshot.dispose()
var contractOK = true
result = tx.gasLimit result = tx.gasLimit
if execComputation(computation): if execComputation(computation):
if tx.isContractCreation:
contractOK = computation.writeContract(fork)
result = computation.refundGas(tx, sender) result = computation.refundGas(tx, sender)
if not contractOK and fork == FkHomestead:
snapshot.revert()
# consume all gas
result = tx.gasLimit
else:
snapshot.commit()
if computation.isSuicided(vmState.blockHeader.coinbase): if computation.isSuicided(vmState.blockHeader.coinbase):
return 0 return 0

View File

@ -14,7 +14,7 @@ import
../transaction, ../config, ../vm_state, ../constants, ../vm_types, ../transaction, ../config, ../vm_state, ../constants, ../vm_types,
../vm_state_transactions, ../utils, ../vm_state_transactions, ../utils,
../db/[db_chain, state_db, storage_types], ../db/[db_chain, state_db, storage_types],
rpc_types, rpc_utils, ../vm/[message, computation, interpreter_dispatch] rpc_types, rpc_utils, ../vm/[message, computation]
#[ #[
Note: Note:

View File

@ -112,61 +112,12 @@ proc commit*(snapshot: var ComputationSnapshot) {.inline.} =
proc dispose*(snapshot: var ComputationSnapshot) {.inline.} = proc dispose*(snapshot: var ComputationSnapshot) {.inline.} =
snapshot.snapshot.dispose() snapshot.snapshot.dispose()
proc transferBalance(computation: var BaseComputation, opCode: static[Op]) = proc getFork*(computation: BaseComputation): Fork =
if computation.msg.depth >= MaxCallDepth: result =
raise newException(StackDepthError, "Stack depth limit reached") if computation.forkOverride.isSome:
computation.forkOverride.get
let senderBalance = computation.vmState.readOnlyStateDb(). else:
getBalance(computation.msg.sender) computation.vmState.blockNumber.toFork
if senderBalance < computation.msg.value:
raise newException(InsufficientFunds,
&"Insufficient funds: {senderBalance} < {computation.msg.value}")
when opCode in {Call, Create}:
computation.vmState.mutateStateDb:
db.subBalance(computation.msg.sender, computation.msg.value)
db.addBalance(computation.msg.storageAddress, computation.msg.value)
proc applyMessage(computation: var BaseComputation, opCode: static[Op]): bool =
var snapshot = computation.snapshot()
defer: snapshot.dispose()
when opCode in {CallCode, Call, Create}:
try:
computation.transferBalance(opCode)
except VMError:
snapshot.revert()
debug "transferBalance failed", msg = computation.error.info
return
if computation.gasMeter.gasRemaining < 0:
snapshot.commit()
return
try:
# Run code
# We cannot use the normal dispatching function `executeOpcodes`
# within `interpreter_dispatch.nim` due to a cyclic dependency.
computation.opcodeExec(computation)
snapshot.commit()
except VMError:
snapshot.revert(true)
debug "VMError applyMessage failed",
msg = computation.error.info,
depth = computation.msg.depth
except EVMError:
snapshot.revert() # TODO: true or false?
debug "EVMError applyMessage failed",
msg = computation.error.info,
depth = computation.msg.depth
except ValueError:
snapshot.revert(true)
debug "ValueError applyMessage failed",
msg = computation.error.info,
depth = computation.msg.depth
result = not computation.isError
proc writeContract*(computation: var BaseComputation, fork: Fork): bool = proc writeContract*(computation: var BaseComputation, fork: Fork): bool =
result = true result = true
@ -192,19 +143,65 @@ proc writeContract*(computation: var BaseComputation, fork: Fork): bool =
if fork < FkHomestead: computation.output = @[] if fork < FkHomestead: computation.output = @[]
result = false result = false
proc generateChildComputation*(fork: Fork, computation: var BaseComputation, childMsg: Message): BaseComputation = proc transferBalance(computation: var BaseComputation, opCode: static[Op]): bool =
var childComp = newBaseComputation( if computation.msg.depth >= MaxCallDepth:
computation.vmState, debug "Stack depth limit reached", depth=computation.msg.depth
computation.vmState.blockNumber, return false
childMsg,
some(fork))
# Copy the fork op code executor proc (assumes child computation is in the same fork) let senderBalance = computation.vmState.readOnlyStateDb().
childComp.opCodeExec = computation.opCodeExec getBalance(computation.msg.sender)
return childComp if senderBalance < computation.msg.value:
debug "insufficient funds", available=senderBalance, needed=computation.msg.value
return false
proc addChildComputation(fork: Fork, computation: var BaseComputation, child: BaseComputation) = when opCode in {Call, Create}:
computation.vmState.mutateStateDb:
db.subBalance(computation.msg.sender, computation.msg.value)
db.addBalance(computation.msg.storageAddress, computation.msg.value)
result = true
proc executeOpcodes*(computation: var BaseComputation) {.gcsafe.}
proc applyMessage*(computation: var BaseComputation, opCode: static[Op]): bool =
var snapshot = computation.snapshot()
defer: snapshot.dispose()
when opCode in {CallCode, Call, Create}:
if not computation.transferBalance(opCode):
snapshot.revert()
return
if computation.gasMeter.gasRemaining < 0:
snapshot.commit()
return
try:
executeOpcodes(computation)
result = not computation.isError
except VMError:
result = false
debug "applyMessage VM Error",
msg = getCurrentExceptionMsg(),
depth = computation.msg.depth
except ValueError:
result = false
debug "applyMessage Value Error",
msg = getCurrentExceptionMsg(),
depth = computation.msg.depth
if result and computation.msg.isCreate:
var fork = computation.getFork
let contractFailed = not computation.writeContract(fork)
result = not(contractFailed and fork == FkHomestead)
if result:
snapshot.commit()
else:
snapshot.revert(true)
proc addChildComputation(computation: var BaseComputation, child: BaseComputation) =
if child.isError: if child.isError:
if child.msg.isCreate: if child.msg.isCreate:
computation.returnData = child.output computation.returnData = child.output
@ -222,32 +219,10 @@ proc addChildComputation(fork: Fork, computation: var BaseComputation, child: Ba
computation.logEntries.add child.logEntries computation.logEntries.add child.logEntries
computation.children.add(child) computation.children.add(child)
proc getFork*(computation: BaseComputation): Fork =
result =
if computation.forkOverride.isSome:
computation.forkOverride.get
else:
computation.vmState.blockNumber.toFork
proc applyChildComputation*(parentComp, childComp: var BaseComputation, opCode: static[Op]) = proc applyChildComputation*(parentComp, childComp: var BaseComputation, opCode: static[Op]) =
## Apply the vm message childMsg as a child computation. ## Apply the vm message childMsg as a child computation.
let fork = parentComp.getFork discard childComp.applyMessage(opCode)
parentComp.addChildComputation(childComp)
var snapshot = parentComp.snapshot()
defer: snapshot.dispose()
var contractOK = true
if applyMessage(childComp, opCode):
if childComp.msg.isCreate:
contractOK = childComp.writeContract(fork)
if not contractOK and fork == FkHomestead:
# consume all gas
snapshot.revert(true)
else:
snapshot.commit()
fork.addChildComputation(parentComp, childComp)
proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) = proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =
if c.msg.storageAddress in c.accountsToDelete: if c.msg.storageAddress in c.accountsToDelete:
@ -261,14 +236,10 @@ proc addLogEntry*(c: var BaseComputation, log: Log) {.inline.} =
# many methods are basically TODO, but they still return valid values # many methods are basically TODO, but they still return valid values
# in order to test some existing code # in order to test some existing code
func getAccountsForDeletion*(c: BaseComputation): seq[EthAddress] = iterator accountsForDeletion*(c: BaseComputation): EthAddress =
# TODO if not c.isError:
if c.isError:
result = @[]
else:
result = @[]
for account in c.accountsToDelete.keys: for account in c.accountsToDelete.keys:
result.add(account) yield account
proc getGasRefund*(c: BaseComputation): GasInt = proc getGasRefund*(c: BaseComputation): GasInt =
if c.isError: if c.isError:
@ -302,3 +273,5 @@ proc traceError*(c: BaseComputation) =
proc prepareTracer*(c: BaseComputation) = proc prepareTracer*(c: BaseComputation) =
c.vmState.tracer.prepare(c.msg.depth) c.vmState.tracer.prepare(c.msg.depth)
include interpreter_dispatch

View File

@ -10,11 +10,11 @@ import
./interpreter/vm_forks ./interpreter/vm_forks
import # Used in vm_types. Beware of recursive dependencies import # Used in vm_types. Beware of recursive dependencies
./code_stream, ./computation, ./stack, ./message, interpreter_dispatch ./code_stream, ./computation, ./stack, ./message
export export
opcode_values, gas_meter, opcode_values, gas_meter,
vm_forks vm_forks
export export
code_stream, computation, stack, message, interpreter_dispatch code_stream, computation, stack, message

View File

@ -572,7 +572,11 @@ proc setupCreate(computation: var BaseComputation, memPos, len: int, value: Uint
) )
childMsg.sender = computation.msg.storageAddress childMsg.sender = computation.msg.storageAddress
result = generateChildComputation(computation.getFork, computation, childMsg) result = newBaseComputation(
computation.vmState,
computation.vmState.blockNumber,
childMsg,
some(computation.getFork))
op create, inline = false, value, startPosition, size: op create, inline = false, value, startPosition, size:
## 0xf0, Create a new account with associated code. ## 0xf0, Create a new account with associated code.
@ -739,7 +743,12 @@ template genCall(callName: untyped, opCode: Op): untyped =
when opCode == CallCode: when opCode == CallCode:
childMsg.storageAddress = computation.msg.storageAddress childMsg.storageAddress = computation.msg.storageAddress
var childComp = generateChildComputation(computation.getFork, computation, childMsg) var childComp = newBaseComputation(
computation.vmState,
computation.vmState.blockNumber,
childMsg,
some(computation.getFork))
result = (childComp, memOutPos, memOutLen) result = (childComp, memOutPos, memOutLen)
op callName, inline = false: op callName, inline = false:

View File

@ -11,13 +11,13 @@ import
./interpreter/[opcode_values, opcodes_impl, vm_forks, gas_costs, gas_meter, utils/macros_gen_opcodes], ./interpreter/[opcode_values, opcodes_impl, vm_forks, gas_costs, gas_meter, utils/macros_gen_opcodes],
./code_stream, ./code_stream,
../vm_types, ../errors, precompiles, ../vm_types, ../errors, precompiles,
./stack, ./computation, terminal # Those are only needed for logging ./stack, terminal # Those are only needed for logging
logScope: logScope:
topics = "vm opcode" topics = "vm opcode"
func invalidInstruction*(computation: var BaseComputation) {.inline.} = func invalidInstruction*(computation: var BaseComputation) {.inline.} =
raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.") raise newException(InvalidInstruction, "Invalid instruction, received an opcode not implemented in the current fork.")
let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block: let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block:
fill_enum_table_holes(Op, newIdentNode("invalidInstruction")): fill_enum_table_holes(Op, newIdentNode("invalidInstruction")):
@ -256,24 +256,14 @@ proc homesteadVM(computation: var BaseComputation) =
if not computation.execPrecompiles: if not computation.execPrecompiles:
genHomesteadDispatch(computation) genHomesteadDispatch(computation)
proc updateOpcodeExec*(computation: var BaseComputation, fork: Fork) = proc executeOpcodes(computation: var BaseComputation) =
# TODO: Optimise getting fork and updating opCodeExec only when necessary
let fork = computation.getFork
case fork case fork
of FkFrontier..FkThawing: of FkFrontier..FkThawing:
computation.opCodeExec = frontierVM
computation.frontierVM() computation.frontierVM()
of FkHomestead..FkSpurious: of FkHomestead..FkSpurious:
computation.opCodeExec = homesteadVM
computation.homesteadVM() computation.homesteadVM()
else: else:
raise newException(VMError, "Unknown or not implemented fork: " & $fork) raise newException(VMError, "Unknown or not implemented fork: " & $fork)
proc executeOpcodes*(computation: var BaseComputation) =
# TODO: Optimise getting fork and updating opCodeExec only when necessary
let fork = computation.getFork
try:
computation.updateOpcodeExec(fork)
except VMError:
computation.error = Error(info: getCurrentExceptionMsg())
debug "VM Error executeOpcodes() failed", error = getCurrentExceptionMsg()
except EVMError:
debug "EVM Error executeOpcodes() failed", error = getCurrentExceptionMsg()

View File

@ -64,28 +64,18 @@ proc setupComputation*(vmState: BaseVMState, tx: Transaction, sender, recipient:
doAssert result.isOriginComputation doAssert result.isOriginComputation
proc execComputation*(computation: var BaseComputation): bool = proc execComputation*(computation: var BaseComputation): bool =
var snapshot = computation.snapshot() if computation.msg.isCreate:
defer: snapshot.dispose() result = computation.applyMessage(Create)
else:
result = computation.applyMessage(Call)
computation.vmState.mutateStateDB: computation.vmState.mutateStateDB:
db.subBalance(computation.msg.origin, computation.msg.value) for deletedAccount in computation.accountsForDeletion:
db.addBalance(computation.msg.storageAddress, computation.msg.value) db.deleteAccount deletedAccount
try:
computation.executeOpcodes()
computation.vmState.mutateStateDB:
for deletedAccount in computation.getAccountsForDeletion:
db.deleteAccount deletedAccount
result = not computation.isError
except ValueError:
result = false
debug "execComputation() error", msg = getCurrentExceptionMsg()
if result: if result:
snapshot.commit()
computation.vmState.addLogs(computation.logEntries) computation.vmState.addLogs(computation.logEntries)
else: else:
snapshot.revert()
if computation.tracingEnabled: computation.traceError() if computation.tracingEnabled: computation.traceError()
proc refundGas*(computation: BaseComputation, tx: Transaction, sender: EthAddress): GasInt = proc refundGas*(computation: BaseComputation, tx: Transaction, sender: EthAddress): GasInt =

View File

@ -44,8 +44,6 @@ type
accounts*: HashSet[EthAddress] accounts*: HashSet[EthAddress]
storageKeys*: seq[HashSet[Uint256]] storageKeys*: seq[HashSet[Uint256]]
OpcodeExecutor* = proc(computation: var BaseComputation)
BaseComputation* = ref object of RootObj BaseComputation* = ref object of RootObj
# The execution computation # The execution computation
vmState*: BaseVMState vmState*: BaseVMState
@ -62,7 +60,6 @@ type
accountsToDelete*: Table[EthAddress, EthAddress] accountsToDelete*: Table[EthAddress, EthAddress]
opcodes*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] opcodes*: Table[Op, proc(computation: var BaseComputation){.nimcall.}]
gasCosts*: GasCosts # TODO - will be hidden at a lower layer gasCosts*: GasCosts # TODO - will be hidden at a lower layer
opCodeExec*: OpcodeExecutor
forkOverride*: Option[Fork] forkOverride*: Option[Fork]
logEntries*: seq[Log] logEntries*: seq[Log]
@ -71,13 +68,6 @@ type
burnsGas*: bool burnsGas*: bool
erasesReturnData*: bool erasesReturnData*: bool
Opcode* = ref object of RootObj
# TODO can't use a stack-allocated object because
# "BaseComputation is not a concrete type"
# TODO: We can probably remove this.
kind*: Op
runLogic*: proc(computation: var BaseComputation)
GasMeter* = object GasMeter* = object
gasRefunded*: GasInt gasRefunded*: GasInt
startGas*: GasInt startGas*: GasInt

View File

@ -7,13 +7,9 @@
import import
unittest, strformat, strutils, sequtils, tables, json, ospaths, times, unittest, strformat, strutils, sequtils, tables, json, ospaths, times,
byteutils, ranges/typedranges, byteutils, ranges/typedranges, eth/[rlp, common], eth/trie/db,
eth/[rlp, common], eth/trie/db, ./test_helpers, ../nimbus/vm/interpreter,
./test_helpers, ../nimbus/[constants, errors, vm_state, vm_types, utils],
../nimbus/[constants, errors],
../nimbus/[vm_state, vm_types],
../nimbus/utils,
../nimbus/vm/interpreter,
../nimbus/db/[db_chain, state_db] ../nimbus/db/[db_chain, state_db]
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus)
@ -60,7 +56,10 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
createAddress = toAddress)) createAddress = toAddress))
var computation = newBaseComputation(vmState, header.blockNumber, message) var computation = newBaseComputation(vmState, header.blockNumber, message)
computation.executeOpcodes() try:
computation.executeOpcodes()
except VMError:
computation.error = Error(info: getCurrentExceptionMsg())
if not fixture{"post"}.isNil: if not fixture{"post"}.isNil:
# Success checks # Success checks
@ -92,7 +91,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
if not fixture{"post"}.isNil: if not fixture{"post"}.isNil:
verifyStateDb(fixture{"post"}, computation.vmState.readOnlyStateDB) verifyStateDb(fixture{"post"}, computation.vmState.readOnlyStateDB)
else: else:
# Error checks # Error checks
check(computation.isError) check(computation.isError)
# TODO postState = fixture{"pre"} if not fixture{"pre"}.isNil:
verifyStateDb(fixture{"pre"}, computation.vmState.readOnlyStateDB)