From b4fd38e0621972276c91f34f006e962d21274a1e Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 5 Sep 2018 16:31:22 +0100 Subject: [PATCH 01/21] Call now gets and checks balance against funds, also now fetches code --- nimbus/vm/interpreter/opcodes_impl.nim | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index 2fb641f61..bf774c1cf 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -695,20 +695,20 @@ template genCall(callName: untyped): untyped = computation.memory.extend(memInPos, memInLen) computation.memory.extend(memOutPos, memOutLen) - let callData = computation.memory.read(memInPos, memInLen) - - ##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} - # computation.vmState.db(readOnly = true): - # let senderBalance = db.getBalance(computation.msg.storageAddress) # TODO check gas balance rollover - - let insufficientFunds = false # shouldTransferValue and senderBalance < value - let stackTooDeep = computation.msg.depth >= MaxCallDepth + let + callData = computation.memory.read(memInPos, memInLen) + senderBalance = computation.vmState.readOnlyStateDb.getBalance(computation.msg.storageAddress) + # TODO check gas balance rollover + # TODO: shouldTransferValue is not set up, should be: + # call, callCode: True + # callDelegate, callStatic: False + insufficientFunds = senderBalance < value # TODO: and shouldTransferValue + stackTooDeep = computation.msg.depth >= MaxCallDepth if insufficientFunds or stackTooDeep: computation.returnData = @[] var errMessage: string if insufficientFunds: - let senderBalance = -1 # TODO workaround # Note: for some reason we can't use strformat here, we get undeclared identifiers errMessage = &"Insufficient Funds: have: " & $senderBalance & "need: " & $value elif stackTooDeep: @@ -721,11 +721,11 @@ template genCall(callName: untyped): untyped = push: 0 return - ##### getCode type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} - # computation.vmState.db(readOnly = true): - # let code = if codeAddress != ZERO_ADDRESS: db.getCode(codeAddress) - # else: db.getCode(to) - let code: seq[byte] = @[] + let code = + if codeAddress != ZERO_ADDRESS: + computation.vmState.readOnlyStateDb.getCode(codeAddress) + else: + computation.vmState.readOnlyStateDb.getCode(to) var childMsg = prepareChildMessage( computation, From d24108a79b799b19267823795b10c2ae64304bd3 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Thu, 6 Sep 2018 16:40:55 +0100 Subject: [PATCH 02/21] Convert code from byte range to seq --- nimbus/vm/interpreter/opcodes_impl.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index bf774c1cf..f602ab306 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -733,7 +733,7 @@ template genCall(callName: untyped): untyped = to, value, callData, - code, + code.toSeq, MessageOptions(flags: flags) ) From 69f07c587abbc82f6b4aa84c265ad3e45e33c9d0 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 7 Sep 2018 17:18:46 +0100 Subject: [PATCH 03/21] Add generateChildComputation, addChildComputation and applyChildComputation --- nimbus/vm/computation.nim | 65 +++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index 08f042673..59178732e 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -39,16 +39,30 @@ template isSuccess*(c: BaseComputation): bool = template isError*(c: BaseComputation): bool = not c.isSuccess -proc shouldBurnGas*(c: BaseComputation): bool = +func shouldBurnGas*(c: BaseComputation): bool = c.isError and c.error.burnsGas -proc shouldEraseReturnData*(c: BaseComputation): bool = +func shouldEraseReturnData*(c: BaseComputation): bool = c.isError and c.error.erasesReturnData func bytesToHex(x: openarray[byte]): string {.inline.} = ## TODO: use seq[byte] for raw data and delete this proc foldl(x, a & b.int.toHex(2).toLowerAscii, "0x") +func output*(c: BaseComputation): seq[byte] = + if c.shouldEraseReturnData: + @[] + else: + c.rawOutput + +func `output=`*(c: var BaseComputation, value: openarray[byte]) = + c.rawOutput = @value + +proc outputHex*(c: BaseComputation): string = + if c.shouldEraseReturnData: + return "0x" + c.rawOutput.bytesToHex + proc prepareChildMessage*( c: var BaseComputation, gas: GasInt, @@ -70,19 +84,46 @@ proc prepareChildMessage*( code, childOptions) -func output*(c: BaseComputation): seq[byte] = - if c.shouldEraseReturnData: - @[] +proc applyCreateMessage(computation: BaseComputation): BaseComputation = + # TODO: This needs to be different depending on fork. + raise newException(NotImplementedError, "Apply create message not implemented") + +proc applyMessage(computation: BaseComputation): BaseComputation = + # TODO: This needs to be different depending on fork. + raise newException(NotImplementedError, "Apply message not implemented") + +proc generateChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation = + new result + if childMsg.isCreate: + result = newBaseComputation( + computation.vmState, + computation.vmState.blockHeader.blockNumber, + childMsg).applyCreateMessage() else: - c.rawOutput + result = newBaseComputation( + computation.vmState, + computation.vmState.blockHeader.blockNumber, + childMsg).applyMessage() -func `output=`*(c: var BaseComputation, value: openarray[byte]) = - c.rawOutput = @value +proc addChildComputation(computation: BaseComputation, child: BaseComputation) = + if child.isError: + if child.msg.isCreate: + computation.returnData = child.output + elif child.shouldBurnGas: + computation.returnData = @[] + else: + computation.returnData = child.output + else: + if child.msg.isCreate: + computation.returnData = @[] + else: + computation.returnData = child.output + computation.children.add(child) -proc outputHex*(c: BaseComputation): string = - if c.shouldEraseReturnData: - return "0x" - c.rawOutput.bytesToHex +proc applyChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation = + ## Apply the vm message childMsg as a child computation. + result = computation.generateChildComputation(childMsg) + computation.addChildComputation(result) proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) = if c.msg.storageAddress in c.accountsToDelete: From 2a38c0194d4f29706f0b51ef5360d2c604a21b30 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 7 Sep 2018 17:19:03 +0100 Subject: [PATCH 04/21] Expose isCreate for export --- nimbus/vm/message.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimbus/vm/message.nim b/nimbus/vm/message.nim index 869749a56..5ef0be4aa 100644 --- a/nimbus/vm/message.nim +++ b/nimbus/vm/message.nim @@ -83,5 +83,5 @@ proc `storageAddress`*(message: Message): EthAddress = else: message.destination -proc isCreate(message: Message): bool = +proc isCreate*(message: Message): bool = message.destination == CREATE_CONTRACT_ADDRESS From d465fcd038f7d34a4e73fab436de2148792ad247 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 7 Sep 2018 17:19:32 +0100 Subject: [PATCH 05/21] Appy child computation in call op code --- nimbus/vm/interpreter/opcodes_impl.nim | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index f602ab306..fa06768ab 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -699,9 +699,9 @@ template genCall(callName: untyped): untyped = callData = computation.memory.read(memInPos, memInLen) senderBalance = computation.vmState.readOnlyStateDb.getBalance(computation.msg.storageAddress) # TODO check gas balance rollover - # TODO: shouldTransferValue is not set up, should be: - # call, callCode: True - # callDelegate, callStatic: False + # TODO: shouldTransferValue in py-evm is: + # True for call and callCode + # False for callDelegate and callStatic insufficientFunds = senderBalance < value # TODO: and shouldTransferValue stackTooDeep = computation.msg.depth >= MaxCallDepth @@ -740,9 +740,7 @@ template genCall(callName: untyped): untyped = if sender != ZERO_ADDRESS: childMsg.sender = sender - # let childComputation = applyChildBaseComputation(computation, childMsg) - var childComputation: BaseComputation # TODO - stub - new childComputation + var childComputation = applyChildComputation(computation, childMsg) childComputation.gasMeter.init(0) if childComputation.isError: From 2b6342764cdfcd836919a6fe9c7b0e7b91f50b46 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 11 Sep 2018 14:53:15 +0100 Subject: [PATCH 06/21] Add fork to string proc --- nimbus/vm/interpreter/vm_forks.nim | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nimbus/vm/interpreter/vm_forks.nim b/nimbus/vm/interpreter/vm_forks.nim index 72e9cc7d4..e8a6e5add 100644 --- a/nimbus/vm/interpreter/vm_forks.nim +++ b/nimbus/vm/interpreter/vm_forks.nim @@ -48,3 +48,15 @@ proc toFork*(blockNumber: UInt256): Fork = elif blockNumber < forkBlocks[FkByzantium]: FkSpurious else: FkByzantium # Update for constantinople when announced + +proc `$`*(fork: Fork): string = + case fork + of FkFrontier: result = "Frontier" + of FkThawing: result = "Thawing" + of FkHomestead: result = "Homestead" + of FkDao: result = "Dao" + of FkTangerine: result = "Tangerine" + of FkSpurious: result = "Spurious" + of FkByzantium: result = "Byzantium" + else: result = "UNKNOWN FORK" + From 6183761251fd562898105f1907c7a0ae481fdb1c Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 11 Sep 2018 18:48:38 +0100 Subject: [PATCH 07/21] Refactor for generateChildComputation --- nimbus/vm/computation.nim | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index 59178732e..851c0c9d4 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -93,17 +93,14 @@ proc applyMessage(computation: BaseComputation): BaseComputation = raise newException(NotImplementedError, "Apply message not implemented") proc generateChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation = - new result + let childComp = newBaseComputation( + computation.vmState, + computation.vmState.blockHeader.blockNumber, + childMsg) if childMsg.isCreate: - result = newBaseComputation( - computation.vmState, - computation.vmState.blockHeader.blockNumber, - childMsg).applyCreateMessage() + result = applyCreateMessage(childComp) else: - result = newBaseComputation( - computation.vmState, - computation.vmState.blockHeader.blockNumber, - childMsg).applyMessage() + result = applyMessage(childComp) proc addChildComputation(computation: BaseComputation, child: BaseComputation) = if child.isError: From 213aebde7d71fbb6def5b05f4b26927d0849f987 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Wed, 12 Sep 2018 14:06:26 +0100 Subject: [PATCH 08/21] Update some comments, adding TODO item for getStateDb --- nimbus/db/db_chain.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index 904e8697a..405e5c6f8 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -238,8 +238,10 @@ proc persistBlockToDb*(self: BaseChainDB; blk: Block) = # receiptDb[indexKey] = rlp.encode(receipt) # return receiptDb.rootHash -# proc snapshot*(self: BaseChainDB): UUID = -# return self.db.snapshot() +#proc snapshot*(self: BaseChainDB): UUID = + # Snapshots are a combination of the state_root at the time of the + # snapshot and the id of the changeset from the journaled DB. + #return self.db.snapshot() # proc commit*(self: BaseChainDB; checkpoint: UUID): void = # self.db.commit(checkpoint) @@ -248,6 +250,7 @@ proc persistBlockToDb*(self: BaseChainDB; blk: Block) = # self.db.clear() proc getStateDb*(self: BaseChainDB; stateRoot: Hash256; readOnly: bool = false): AccountStateDB = + # TODO: readOnly is not used. result = newAccountStateDB(self.db, stateRoot) From 7cd7a73a1f93e931ae90e5aec6a17a1372ef43f6 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 14 Sep 2018 16:42:15 +0100 Subject: [PATCH 09/21] Add opCodeExec to allow computation to execute code --- nimbus/vm_types.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nimbus/vm_types.nim b/nimbus/vm_types.nim index a0bdf2a8c..1cdafb0b4 100644 --- a/nimbus/vm_types.nim +++ b/nimbus/vm_types.nim @@ -14,6 +14,8 @@ import type + OpcodeExecutor* = proc(computation: var BaseComputation) + BaseComputation* = ref object of RootObj # The execution computation vmState*: BaseVMState @@ -32,6 +34,7 @@ type opcodes*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] precompiles*: Table[string, Opcode] gasCosts*: GasCosts # TODO - will be hidden at a lower layer + opCodeExec*: OpcodeExecutor Error* = ref object info*: string From af76d209a6a40d60bbda17f241f1f65215a69ef0 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 14 Sep 2018 16:47:40 +0100 Subject: [PATCH 10/21] Add opcode executor proc to computation --- nimbus/vm/interpreter_dispatch.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index 46d529024..f2fcd7801 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -226,7 +226,9 @@ proc executeOpcodes*(computation: var BaseComputation) = let fork = computation.vmState.blockHeader.blockNumber.toFork try: case fork - of FkFrontier: computation.frontierVM() + of FkFrontier: + computation.opCodeExec = frontierVM + computation.frontierVM() else: raise newException(ValueError, "not implemented fork: " & $fork) except VMError: From c7a014bb102d78657c09a72909d4ecb713a5890d Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 14 Sep 2018 17:59:21 +0100 Subject: [PATCH 11/21] Add gas cost for create --- nimbus/vm/interpreter/gas_costs.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nimbus/vm/interpreter/gas_costs.nim b/nimbus/vm/interpreter/gas_costs.nim index 65e50580c..36164beda 100644 --- a/nimbus/vm/interpreter/gas_costs.nim +++ b/nimbus/vm/interpreter/gas_costs.nim @@ -168,6 +168,9 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp if not value.isZero: result += static(FeeSchedule[GasExpByte]) * (1 + log256(value)) + func `prefix gasCreate`(currentMemSize, memOffset, memLength: Natural): GasInt {.nimcall.} = + result = static(FeeSchedule[GasCodeDeposit]) * memLength + func `prefix gasSha3`(currentMemSize, memOffset, memLength: Natural): GasInt {.nimcall.} = result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength) @@ -494,7 +497,7 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp Log4: memExpansion `prefix gasLog4`, # f0s: System operations - Create: fixed GasCreate, # TODO, dynamic cost + Create: memExpansion `prefix gasCreate`, # TODO: Change to dynamic? Call: complex `prefix gasCall`, CallCode: complex `prefix gasCall`, Return: memExpansion `prefix gasHalt`, From 63f9acd8ce71465e64393cddd49ff235ab88c094 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 14 Sep 2018 18:03:26 +0100 Subject: [PATCH 12/21] Add create and apply message procs --- nimbus/vm/computation.nim | 92 +++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index 851c0c9d4..c212b5fca 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -10,7 +10,8 @@ import eth_common, ../constants, ../errors, ../validation, ../vm_state, ../vm_types, ./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks], - ./code_stream, ./memory, ./message, ./stack + ./code_stream, ./memory, ./message, ./stack, ../db/[state_db, db_chain], + ../utils/header, byteutils, ranges logScope: topics = "vm computation" @@ -84,25 +85,87 @@ proc prepareChildMessage*( code, childOptions) -proc applyCreateMessage(computation: BaseComputation): BaseComputation = - # TODO: This needs to be different depending on fork. - raise newException(NotImplementedError, "Apply create message not implemented") +proc applyFrontierMessage(computation: var BaseComputation) = + # TODO: Snapshots/Journaling, see: https://github.com/status-im/nimbus/issues/142 + #let snapshot = computation.vmState.snapshot() -proc applyMessage(computation: BaseComputation): BaseComputation = - # TODO: This needs to be different depending on fork. - raise newException(NotImplementedError, "Apply message not implemented") + if computation.msg.depth > STACK_DEPTH_LIMIT: + raise newException(StackDepthError, "Stack depth limit reached") -proc generateChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation = - let childComp = newBaseComputation( + if computation.msg.value != 0: + let senderBalance = + computation.vmState.chainDb.getStateDb( + computation.vmState.blockHeader.hash, false). + getBalance(computation.msg.sender) + + if sender_balance < computation.msg.value: + raise newException(InsufficientFunds, + &"Insufficient funds: {senderBalance} < {computation.msg.value}" + ) + + computation.vmState.mutateStateDb: + db.deltaBalance(computation.msg.sender, -1 * computation.msg.value) + db.deltaBalance(computation.msg.storage_address, computation.msg.value) + + debug "Apply message", + value = computation.msg.value, + sender = computation.msg.sender.toHex, + address = computation.msg.storage_address.toHex + + computation.opcodeExec(computation) + + # TODO: Snapshots/Journaling + #[ + if result.isError: + computation.vmState.revert(snapshot) + else: + computation.vmState.commit(snapshot) + ]# + +proc createFrontierMessage(computation: var BaseComputation) = + computation.applyFrontierMessage() + + if computation.isError: + return + else: + let contractCode = computation.output + + if contractCode.len > 0: + try: + computation.gasMeter.consumeGas( + computation.gasCosts[Create].m_handler(0, 0, contractCode.len), + reason="Write contract code for CREATE") + except OutOfGas: + computation.output = @[] + else: + let storageAddr = computation.msg.storage_address + debug "SETTING CODE", + address = storageAddr.toHex, + length = len(contract_code), + hash = contractCode.rlpHash + computation.vmState.mutateStateDB: + db.setCode(storageAddr, contractCode.toRange) + +proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMsg: Message): BaseComputation = + var childComp = newBaseComputation( computation.vmState, computation.vmState.blockHeader.blockNumber, childMsg) if childMsg.isCreate: - result = applyCreateMessage(childComp) + case fork + of FkFrontier: + createFrontierMessage(childComp) + else: + raise newException(NotImplementedError, "Create message not yet implemented for fork " & $fork) else: - result = applyMessage(childComp) + case fork + of FkFrontier: + applyFrontierMessage(childComp) + else: + raise newException(NotImplementedError, "Apply message not yet implemented for fork " & $fork) + return childComp -proc addChildComputation(computation: BaseComputation, child: BaseComputation) = +proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseComputation) = if child.isError: if child.msg.isCreate: computation.returnData = child.output @@ -119,8 +182,9 @@ proc addChildComputation(computation: BaseComputation, child: BaseComputation) = proc applyChildComputation*(computation: BaseComputation, childMsg: Message): BaseComputation = ## Apply the vm message childMsg as a child computation. - result = computation.generateChildComputation(childMsg) - computation.addChildComputation(result) + let fork = computation.vmState.blockHeader.blockNumber.toFork + result = fork.generateChildComputation(computation, childMsg) + fork.addChildComputation(computation, result) proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) = if c.msg.storageAddress in c.accountsToDelete: From cdfcda41b465ba6c7edf7fbef0aa6dd3ad9c8441 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Fri, 14 Sep 2018 18:04:09 +0100 Subject: [PATCH 13/21] Remove init 0 after child computation --- nimbus/vm/interpreter/opcodes_impl.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index fa06768ab..9cf08b0ed 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -741,7 +741,6 @@ template genCall(callName: untyped): untyped = childMsg.sender = sender var childComputation = applyChildComputation(computation, childMsg) - childComputation.gasMeter.init(0) if childComputation.isError: push: 0 From 949d4c11f8742bc848adf3e4ebbe93003d2ab6b5 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 17 Sep 2018 18:38:10 +0100 Subject: [PATCH 14/21] Full names for `$`(fork) --- nimbus/vm/interpreter/vm_forks.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nimbus/vm/interpreter/vm_forks.nim b/nimbus/vm/interpreter/vm_forks.nim index e8a6e5add..442585c93 100644 --- a/nimbus/vm/interpreter/vm_forks.nim +++ b/nimbus/vm/interpreter/vm_forks.nim @@ -55,8 +55,8 @@ proc `$`*(fork: Fork): string = of FkThawing: result = "Thawing" of FkHomestead: result = "Homestead" of FkDao: result = "Dao" - of FkTangerine: result = "Tangerine" - of FkSpurious: result = "Spurious" + of FkTangerine: result = "Tangerine Whistle" + of FkSpurious: result = "Spurious Dragon" of FkByzantium: result = "Byzantium" else: result = "UNKNOWN FORK" From c300044474ee52bae9a1342542e57cdf76e53cd7 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 17 Sep 2018 19:34:29 +0100 Subject: [PATCH 15/21] Add EIP170_CODE_SIZE_LIMIT --- nimbus/constants.nim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nimbus/constants.nim b/nimbus/constants.nim index 57aff9c43..23ec08ffc 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -52,3 +52,11 @@ const MAX_PREV_HEADER_DEPTH* = 256.toBlockNumber MaxCallDepth* = 1024 + + ## Fork specific constants + + # See: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md + # and: https://github.com/ethereum/EIPs/issues/170 + EIP170_CODE_SIZE_LIMIT* = 24577 + + From d484420f5b953ab347b8d4e51d4e79cf47d840c1 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Mon, 17 Sep 2018 19:35:07 +0100 Subject: [PATCH 16/21] Remove fork distinguished procs, handle in applyCreateMessage --- nimbus/vm/computation.nim | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index c212b5fca..eebe8f20c 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -85,7 +85,7 @@ proc prepareChildMessage*( code, childOptions) -proc applyFrontierMessage(computation: var BaseComputation) = +proc applyMessage(computation: var BaseComputation) = # TODO: Snapshots/Journaling, see: https://github.com/status-im/nimbus/issues/142 #let snapshot = computation.vmState.snapshot() @@ -122,21 +122,36 @@ proc applyFrontierMessage(computation: var BaseComputation) = computation.vmState.commit(snapshot) ]# -proc createFrontierMessage(computation: var BaseComputation) = - computation.applyFrontierMessage() +proc applyCreateMessage(fork: Fork, computation: var BaseComputation) = + computation.applyMessage() + if fork >= FkFrontier: + # TODO: Take snapshot + discard if computation.isError: + if fork == FkSpurious: + # TODO: Revert snapshot + discard return else: let contractCode = computation.output - if contractCode.len > 0: + if fork >= FkSpurious and contractCode.len >= EIP170_CODE_SIZE_LIMIT: + # TODO: Revert snapshot + raise newException(OutOfGas, &"Contract code size exceeds EIP170 limit of {EIP170_CODE_SIZE_LIMIT}. Got code of size: {contractCode.len}") + try: computation.gasMeter.consumeGas( computation.gasCosts[Create].m_handler(0, 0, contractCode.len), - reason="Write contract code for CREATE") + reason = "Write contract code for CREATE") except OutOfGas: - computation.output = @[] + if fork == FkFrontier: + computation.output = @[] + else: + # Different from Frontier: + # Reverts state on gas failure while writing contract code. + # TODO: Revert snapshot + discard else: let storageAddr = computation.msg.storage_address debug "SETTING CODE", @@ -152,17 +167,9 @@ proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMs computation.vmState.blockHeader.blockNumber, childMsg) if childMsg.isCreate: - case fork - of FkFrontier: - createFrontierMessage(childComp) - else: - raise newException(NotImplementedError, "Create message not yet implemented for fork " & $fork) + fork.applyCreateMessage(childComp) else: - case fork - of FkFrontier: - applyFrontierMessage(childComp) - else: - raise newException(NotImplementedError, "Apply message not yet implemented for fork " & $fork) + applyMessage(childComp) return childComp proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseComputation) = From d71ce6fb2402683044328d5270f39588703fdf1d Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 18 Sep 2018 02:56:10 +0300 Subject: [PATCH 17/21] Make use of the new transactional API offered by the Trie DB --- nimbus/vm/computation.nim | 48 ++++++++++++++++++++------------------- nimbus/vm_state.nim | 8 ++++++- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index eebe8f20c..c7863a883 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -86,8 +86,8 @@ proc prepareChildMessage*( childOptions) proc applyMessage(computation: var BaseComputation) = - # TODO: Snapshots/Journaling, see: https://github.com/status-im/nimbus/issues/142 - #let snapshot = computation.vmState.snapshot() + var transaction = computation.vmState.beginTransaction() + defer: transaction.dispose() if computation.msg.depth > STACK_DEPTH_LIMIT: raise newException(StackDepthError, "Stack depth limit reached") @@ -99,7 +99,7 @@ proc applyMessage(computation: var BaseComputation) = getBalance(computation.msg.sender) if sender_balance < computation.msg.value: - raise newException(InsufficientFunds, + raise newException(InsufficientFunds, &"Insufficient funds: {senderBalance} < {computation.msg.value}" ) @@ -114,36 +114,43 @@ proc applyMessage(computation: var BaseComputation) = computation.opcodeExec(computation) - # TODO: Snapshots/Journaling - #[ - if result.isError: - computation.vmState.revert(snapshot) - else: - computation.vmState.commit(snapshot) - ]# + if not computation.isError: + transaction.commit() proc applyCreateMessage(fork: Fork, computation: var BaseComputation) = computation.applyMessage() + + var transaction: DbTransaction + defer: transaction.safeDispose() + if fork >= FkFrontier: - # TODO: Take snapshot - discard + transaction = computation.vmState.beginTransaction() if computation.isError: - if fork == FkSpurious: - # TODO: Revert snapshot - discard return else: let contractCode = computation.output if contractCode.len > 0: if fork >= FkSpurious and contractCode.len >= EIP170_CODE_SIZE_LIMIT: - # TODO: Revert snapshot raise newException(OutOfGas, &"Contract code size exceeds EIP170 limit of {EIP170_CODE_SIZE_LIMIT}. Got code of size: {contractCode.len}") try: computation.gasMeter.consumeGas( computation.gasCosts[Create].m_handler(0, 0, contractCode.len), reason = "Write contract code for CREATE") + + let storageAddr = computation.msg.storage_address + debug "SETTING CODE", + address = storageAddr.toHex, + length = len(contract_code), + hash = contractCode.rlpHash + + computation.vmState.mutateStateDb: + db.setCode(storageAddr, contractCode.toRange) + + if transaction != nil: + transaction.commit() + except OutOfGas: if fork == FkFrontier: computation.output = @[] @@ -153,13 +160,8 @@ proc applyCreateMessage(fork: Fork, computation: var BaseComputation) = # TODO: Revert snapshot discard else: - let storageAddr = computation.msg.storage_address - debug "SETTING CODE", - address = storageAddr.toHex, - length = len(contract_code), - hash = contractCode.rlpHash - computation.vmState.mutateStateDB: - db.setCode(storageAddr, contractCode.toRange) + if transaction != nil: + transaction.commit() proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMsg: Message): BaseComputation = var childComp = newBaseComputation( diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index e2b1999c6..5ebf332a9 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -7,7 +7,7 @@ import macros, strformat, tables, - eth_common, + eth_common, eth_trie/db, ./constants, ./errors, ./transaction, ./db/[db_chain, state_db], ./utils/header @@ -118,3 +118,9 @@ template mutateStateDB*(vmState: BaseVMState, body: untyped) = proc readOnlyStateDB*(vmState: BaseVMState): AccountStateDB {.inline.}= vmState.chaindb.getStateDb(vmState.blockHeader.stateRoot, readOnly = true) + +export DbTransaction, commit, rollback, dispose, safeDispose + +proc beginTransaction*(vmState: BaseVMState): DbTransaction = + vmState.chaindb.db.beginTransaction() + From 760e4f96854b19cd7ef0d585f37a17f75662e43a Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 18 Sep 2018 12:02:34 +0100 Subject: [PATCH 18/21] Adjust trie creation to properly init transactional tries --- tests/test_vm_json.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index bbd079981..aec200b4d 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -40,7 +40,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = ) var memDb = newMemDB() - var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb)) + var vmState = newBaseVMState(header, newBaseChainDB(newMemoryDB())) let fexec = fixture["exec"] var code: seq[byte] vmState.mutateStateDB: From 4a944398c9892be646a17e8f967326d8c510435c Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 18 Sep 2018 12:18:59 +0100 Subject: [PATCH 19/21] More newMemoryDb updates --- tests/test_generalstate_json.nim | 2 +- tests/test_opcode.nim | 2 +- tests/test_rpc.nim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_generalstate_json.nim b/tests/test_generalstate_json.nim index dd1d67e2f..05895751e 100644 --- a/tests/test_generalstate_json.nim +++ b/tests/test_generalstate_json.nim @@ -52,7 +52,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = let gas_cost = transaction.gasLimit.u256 * transaction.gasPrice.u256 var memDb = newMemDB() - var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb)) + var vmState = newBaseVMState(header, newBaseChainDB(newMemoryDb())) vmState.mutateStateDB: setupStateDB(fixture{"pre"}, db) diff --git a/tests/test_opcode.nim b/tests/test_opcode.nim index d279a8c3d..b6ef21776 100644 --- a/tests/test_opcode.nim +++ b/tests/test_opcode.nim @@ -19,7 +19,7 @@ from eth_common import GasInt proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputation = let header = BlockHeader(blockNumber: blockNum) var memDb = newMemDB() - var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb)) + var vmState = newBaseVMState(header, newBaseChainDB(newMemoryDb())) # coinbase: "", # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 373ec4a4f..89ec268b4 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -45,7 +45,7 @@ proc doTests = emptyRlpHash = keccak256.digest(rlp.encode("").toOpenArray) header = BlockHeader(stateRoot: emptyRlpHash) var - chain = newBaseChainDB(trieDB newMemDB()) + chain = newBaseChainDB(newMemoryDb()) state = newBaseVMState(header, chain) ethNode.chain = chain From d435e434ec69ebff1cf3032dab1d85be2a16e805 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 18 Sep 2018 13:03:22 +0100 Subject: [PATCH 20/21] Copy opCodeExec to child computation --- nimbus/vm/computation.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index c7863a883..9478de4e7 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -168,6 +168,10 @@ proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMs computation.vmState, computation.vmState.blockHeader.blockNumber, childMsg) + + # Copy the fork op code executor proc (assumes child computation is in the same fork) + childComp.opCodeExec = computation.opCodeExec + if childMsg.isCreate: fork.applyCreateMessage(childComp) else: From b49637cdbf4551078d89b1065a9225711f9dea76 Mon Sep 17 00:00:00 2001 From: coffeepots Date: Tue, 18 Sep 2018 13:10:30 +0100 Subject: [PATCH 21/21] Helper procs for updating opcode executor based on fork --- nimbus/vm/interpreter_dispatch.nim | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index f2fcd7801..a64b9bd62 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -222,14 +222,22 @@ macro genFrontierDispatch(computation: BaseComputation): untyped = proc frontierVM(computation: var BaseComputation) = genFrontierDispatch(computation) +proc updateOpcodeExec*(computation: var BaseComputation, fork: Fork) = + case fork + of FkFrontier: + computation.opCodeExec = frontierVM + computation.frontierVM() + else: + raise newException(VMError, "Unknown or not implemented fork: " & $fork) + +proc updateOpcodeExec*(computation: var BaseComputation) = + let fork = computation.vmState.blockHeader.blockNumber.toFork + computation.updateOpcodeExec(fork) + proc executeOpcodes*(computation: var BaseComputation) = + # TODO: Optimise getting fork and updating opCodeExec only when necessary let fork = computation.vmState.blockHeader.blockNumber.toFork try: - case fork - of FkFrontier: - computation.opCodeExec = frontierVM - computation.frontierVM() - else: - raise newException(ValueError, "not implemented fork: " & $fork) + computation.updateOpcodeExec(fork) except VMError: computation.error = Error(info: getCurrentExceptionMsg())