diff --git a/GeneralStateTests.md b/GeneralStateTests.md index 1505c6f63..f206be473 100644 --- a/GeneralStateTests.md +++ b/GeneralStateTests.md @@ -370,8 +370,8 @@ OK: 3/3 Fail: 0/3 Skip: 0/3 CREATE_ContractRETURNBigOffset.json Skip + CREATE_ContractSSTOREDuringInit.json OK + CREATE_ContractSuicideDuringInit.json OK - CREATE_ContractSuicideDuringInit_ThenStoreThenReturn.json Skip - CREATE_ContractSuicideDuringInit_WithValue.json Skip ++ CREATE_ContractSuicideDuringInit_ThenStoreThenReturn.json OK ++ CREATE_ContractSuicideDuringInit_WithValue.json OK + CREATE_ContractSuicideDuringInit_WithValueToItself.json OK CREATE_EContractCreateEContractInInit_Tr.json Skip + CREATE_EContractCreateNEContractInInitOOG_Tr.json OK @@ -397,7 +397,7 @@ OK: 3/3 Fail: 0/3 Skip: 0/3 TransactionCollisionToEmptyButCode.json Skip TransactionCollisionToEmptyButNonce.json Skip ``` -OK: 12/30 Fail: 0/30 Skip: 18/30 +OK: 14/30 Fail: 0/30 Skip: 16/30 ## stDelegatecallTestHomestead ```diff Call1024BalanceTooLow.json Skip @@ -508,9 +508,9 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 + contractCreationOOGdontLeaveEmptyContractViaTransaction.json OK + createContractViaContract.json OK createContractViaContractOOGInitCode.json Skip - createContractViaTransactionCost53000.json Skip ++ createContractViaTransactionCost53000.json OK ``` -OK: 3/5 Fail: 0/5 Skip: 2/5 +OK: 4/5 Fail: 0/5 Skip: 1/5 ## stInitCodeTest ```diff + CallContractToCreateContractAndCallItOOG.json OK @@ -527,12 +527,12 @@ OK: 3/5 Fail: 0/5 Skip: 2/5 + ReturnTest.json OK + ReturnTest2.json OK + StackUnderFlowContractCreation.json OK - TransactionCreateAutoSuicideContract.json Skip ++ TransactionCreateAutoSuicideContract.json OK + TransactionCreateRandomInitCode.json OK + TransactionCreateStopInInitcode.json OK + TransactionCreateSuicideInInitcode.json OK ``` -OK: 10/18 Fail: 0/18 Skip: 8/18 +OK: 11/18 Fail: 0/18 Skip: 7/18 ## stLogTests ```diff + log0_emptyMem.json OK @@ -1510,12 +1510,12 @@ OK: 315/327 Fail: 0/327 Skip: 12/327 + randomStatetest641.json OK + randomStatetest642.json OK randomStatetest643.json Skip - randomStatetest644.json Skip - randomStatetest645.json Skip ++ randomStatetest644.json OK ++ randomStatetest645.json OK randomStatetest646.json Skip randomStatetest647.json Skip ``` -OK: 219/227 Fail: 0/227 Skip: 8/227 +OK: 221/227 Fail: 0/227 Skip: 6/227 ## stRecursiveCreate ```diff recursiveCreate.json Skip @@ -2093,8 +2093,8 @@ OK: 51/67 Fail: 0/67 Skip: 16/67 + CreateTransactionReverted.json OK + CreateTransactionSuccess.json OK + EmptyTransaction.json OK - EmptyTransaction2.json Skip - EmptyTransaction3.json Skip ++ EmptyTransaction2.json OK ++ EmptyTransaction3.json OK + HighGasLimit.json OK + InternalCallHittingGasLimit.json OK + InternalCallHittingGasLimit2.json OK @@ -2122,7 +2122,7 @@ OK: 51/67 Fail: 0/67 Skip: 16/67 + TransactionFromCoinbaseNotEnoughFounds.json OK + TransactionNonceCheck.json OK + TransactionNonceCheck2.json OK - TransactionSendingToEmpty.json Skip ++ TransactionSendingToEmpty.json OK + TransactionSendingToZero.json OK + TransactionToAddressh160minusOne.json OK + TransactionToItself.json OK @@ -2131,7 +2131,7 @@ OK: 51/67 Fail: 0/67 Skip: 16/67 + UserTransactionZeroCost.json OK + UserTransactionZeroCostWithData.json OK ``` -OK: 38/44 Fail: 0/44 Skip: 6/44 +OK: 41/44 Fail: 0/44 Skip: 3/44 ## stTransitionTest ```diff + createNameRegistratorPerTxsAfter.json OK @@ -2520,4 +2520,4 @@ OK: 0/133 Fail: 0/133 Skip: 133/133 OK: 0/130 Fail: 0/130 Skip: 130/130 ---TOTAL--- -OK: 1170/2334 Fail: 0/2334 Skip: 1164/2334 +OK: 1179/2334 Fail: 0/2334 Skip: 1155/2334 diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index e2097c04a..bba3a3c6f 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -7,18 +7,20 @@ import tables, sequtils, algorithm, - ranges, state_db, nimcrypto, eth/trie/[hexary, db], eth/[common, rlp], byteutils, chronicles, - ../errors, ../block_types, ../utils/header, ../constants, ./storage_types + ranges, state_db, eth/trie/[hexary, db], + eth/[common, rlp], byteutils, chronicles, + ../errors, ../constants, ./storage_types, + ../utils type BaseChainDB* = ref object db* : TrieDatabaseRef pruneTrie*: bool - KeyType = enum - blockNumberToHash - blockHashToScore - + #KeyType = enum + # blockNumberToHash + # blockHashToScore + # TransactionKey = tuple blockNumber: BlockNumber index: int @@ -105,7 +107,7 @@ proc persistTransactions*(self: BaseChainDB, blockNumber: BlockNumber, transacti for idx, tx in transactions: let encodedTx = rlp.encode(tx).toRange - txHash = keccak256.digest(encodedTx.toOpenArray) + txHash = keccak(encodedTx.toOpenArray) txKey: TransactionKey = (blockNumber, idx) trie.put(rlp.encode(idx).toRange, encodedTx) self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey)) @@ -125,7 +127,7 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): ## Returns an iterable of the transaction hashes from th block specified ## by the given block header. for encodedTx in self.getBlockTransactionData(blockHeader.txRoot): - yield keccak256.digest(encodedTx.toOpenArray) + yield keccak(encodedTx.toOpenArray) proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool = var header: BlockHeader @@ -245,24 +247,24 @@ proc persistUncles*(self: BaseChainDB, uncles: openarray[BlockHeader]): Hash256 ## Persists the list of uncles to the database. ## Returns the uncles hash. let enc = rlp.encode(uncles) - result = keccak256.digest(enc) + result = keccak(enc) self.db.put(genericHashKey(result).toOpenArray, enc) -proc persistBlockToDb*(self: BaseChainDB; blk: Block): ValidationResult = - ## Persist the given block's header and uncles. - ## Assumes all block transactions have been persisted already. - let newCanonicalHeaders = self.persistHeaderToDb(blk.header) - for header in newCanonicalHeaders: - var index = 0 - for txHash in self.getBlockTransactionHashes(header): - self.addTransactionToCanonicalChain(txHash, header, index) - inc index - - if blk.uncles.len != 0: - let ommersHash = self.persistUncles(blk.uncles) - if ommersHash != blk.header.ommersHash: - debug "ommersHash mismatch" - return ValidationResult.Error +#proc persistBlockToDb*(self: BaseChainDB; blk: Block): ValidationResult = +# ## Persist the given block's header and uncles. +# ## Assumes all block transactions have been persisted already. +# let newCanonicalHeaders = self.persistHeaderToDb(blk.header) +# for header in newCanonicalHeaders: +# var index = 0 +# for txHash in self.getBlockTransactionHashes(header): +# self.addTransactionToCanonicalChain(txHash, header, index) +# inc index +# +# if blk.uncles.len != 0: +# let ommersHash = self.persistUncles(blk.uncles) +# if ommersHash != blk.header.ommersHash: +# debug "ommersHash mismatch" +# return ValidationResult.Error # Deprecated: proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader {.deprecated.} = @@ -273,4 +275,3 @@ proc lookupBlockHash*(self: BaseChainDB; n: BlockNumber): Hash256 {.deprecated.} proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockHeader {.deprecated.} = self.getBlockHeader(n) - diff --git a/nimbus/db/state_db.nim b/nimbus/db/state_db.nim index d93433c59..3bf8fb110 100644 --- a/nimbus/db/state_db.nim +++ b/nimbus/db/state_db.nim @@ -7,8 +7,8 @@ import sequtils, strformat, tables, - chronicles, eth/[common, rlp], nimcrypto, eth/trie/[hexary, db], - ../constants, ../errors, ../validation, + chronicles, eth/[common, rlp], eth/trie/[hexary, db], + ../constants, ../errors, ../validation, ../utils, storage_types logScope: @@ -115,7 +115,7 @@ proc setStorage*(db: var AccountStateDB, var triedb = HexaryTrie(db.trie).db # slotHash can be obtained from accountTrie.put? - slotHash = keccak256.digest(slot.toByteArrayBE) + slotHash = keccak(slot.toByteArrayBE) triedb.put(slotHashToSlotKey(slotHash.data).toOpenArray, rlp.encode(slot)) account.storageRoot = accountTrie.rootHash @@ -156,13 +156,16 @@ proc getNonce*(db: AccountStateDB, address: EthAddress): AccountNonce = let account = db.getAccount(address) account.nonce +proc incNonce*(db: AccountStateDB, address: EthAddress) {.inline.} = + db.setNonce(address, db.getNonce(address) + 1) + proc setCode*(db: AccountStateDB, address: EthAddress, code: ByteRange) = var account = db.getAccount(address) # TODO: implement JournalDB to store code and storage # also use JournalDB to revert state trie let - newCodeHash = keccak256.digest code.toOpenArray + newCodeHash = keccak code.toOpenArray triedb = HexaryTrie(db.trie).db if code.len != 0: diff --git a/nimbus/p2p/executor.nim b/nimbus/p2p/executor.nim index bdba9e29c..066f67302 100644 --- a/nimbus/p2p/executor.nim +++ b/nimbus/p2p/executor.nim @@ -6,115 +6,57 @@ import options, ../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. +proc contractCall*(tx: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): GasInt = 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 - + var computation = setupComputation(vmState, tx, sender, forkOverride) if execComputation(computation): let - gasRemaining = computation.gasMeter.gasRemaining.u256 - gasRefunded = computation.getGasRefund().u256 - gasUsed = t.gasLimit.u256 - gasRemaining + gasRemaining = computation.gasMeter.gasRemaining + gasRefunded = computation.getGasRefund() + gasUsed = tx.gasLimit - gasRemaining gasRefund = min(gasRefunded, gasUsed div 2) - gasRefundAmount = (gasRefund + gasRemaining) * t.gasPrice.u256 + gasRefundAmount = (gasRefund + gasRemaining).u256 * tx.gasPrice.u256 db.addBalance(sender, gasRefundAmount) - - return (t.gasLimit.u256 - gasRemaining - gasRefund) * t.gasPrice.u256 + return (tx.gasLimit - gasRemaining - gasRefund) 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 + return tx.gasLimit -proc processTransaction*(t: Transaction, sender: EthAddress, vmState: BaseVMState): UInt256 = +proc processTransaction*(tx: Transaction, sender: EthAddress, vmState: BaseVMState): GasInt = ## 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 + trace "txHash", rlpHash = tx.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 + let upfrontGasCost = tx.gasLimit.u256 * tx.gasPrice.u256 + let upfrontCost = upfrontGasCost + tx.value var balance = db.getBalance(sender) if balance < upfrontCost: if balance <= upfrontGasCost: - result = balance + result = (balance div tx.gasPrice.u256).truncate(GasInt) balance = 0.u256 else: - result = upfrontGasCost + result = tx.gasLimit balance -= upfrontGasCost transactionFailed = true else: - balance -= upfrontCost + balance -= upfrontGasCost + db.incNonce(sender) db.setBalance(sender, balance) - if transactionFailed: - return - - var gasUsed = t.payload.intrinsicGas.GasInt # += 32000 appears in Homestead when contract create + if transactionFailed: return - if gasUsed > t.gasLimit: - debug "Transaction failed. Out of gas." - transactionFailed = true + # TODO: Run the vm with proper fork + if tx.isContractCreation: + result = applyCreateTransaction(tx, vmState, sender) 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 + result = contractCall(tx, vmState, sender) type # TODO: these types need to be removed @@ -167,15 +109,11 @@ proc processBlock*(chainDB: BaseChainDB, head, header: BlockHeader, body: BlockB 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) + let gasUsed = processTransaction(tx, sender, vmState) cumulativeGasUsed += gasUsed # miner fee + let txFee = gasUsed.u256 * tx.gasPrice.u256 stateDb.addBalance(header.coinbase, txFee) else: debug "Could not get sender", txIndex, tx diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 67db5acfe..1795fc8a6 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -9,10 +9,10 @@ import strutils, times, options, - nimcrypto, json_rpc/rpcserver, hexstrings, stint, byteutils, ranges/typedranges, - eth/[common, keys, rlp, p2p], eth/trie/db, - ../utils/header, ../transaction, ../config, ../vm_state, ../constants, ../vm_types, - ../vm_state_transactions, ../utils/addresses, + json_rpc/rpcserver, hexstrings, stint, byteutils, ranges/typedranges, + eth/[common, keys, rlp, p2p], eth/trie/db, nimcrypto, + ../transaction, ../config, ../vm_state, ../constants, ../vm_types, + ../vm_state_transactions, ../utils, ../db/[db_chain, state_db, storage_types], rpc_types, rpc_utils, ../vm/[message, computation, interpreter_dispatch] @@ -376,7 +376,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = for i in 0 ..< blockBody.uncles.len: rawData[startIdx .. startIdx + 32] = blockBody.uncles[i].hash.data startIdx += 32 - result.sha3Uncles = keccak256.digest(rawData) + result.sha3Uncles = keccak(rawData) result.logsBloom = some(header.bloom) result.transactionsRoot = header.txRoot @@ -414,7 +414,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns BlockObject or nil when no block was found. let header = chain.headerFromTag(quantityTag) - + result = some(populateBlockObject(header, getBlockBody(header.hash))) proc populateTransactionObject(transaction: Transaction, txIndex: int64, blockHeader: BlockHeader, blockHash: Hash256): TransactionObject = diff --git a/nimbus/tracer.nim b/nimbus/tracer.nim index abf201fc2..380abcf31 100644 --- a/nimbus/tracer.nim +++ b/nimbus/tracer.nim @@ -1,7 +1,7 @@ import db/[db_chain, state_db, capturedb], eth/common, utils, json, constants, vm_state, vm_types, transaction, p2p/executor, - eth/trie/db, nimcrypto, strutils, ranges, ./utils/addresses, + eth/trie/db, nimcrypto, strutils, ranges, chronicles, rpc/hexstrings, launcher proc getParentHeader(self: BaseChainDB, header: BlockHeader): BlockHeader = @@ -32,17 +32,6 @@ proc toJson*(receipts: seq[Receipt]): JsonNode = for receipt in receipts: result.add receipt.toJson -proc getSender*(tx: Transaction): EthAddress = - if not tx.getSender(result): - raise newException(ValueError, "Could not get sender") - -proc getRecipient*(tx: Transaction): EthAddress = - if tx.isContractCreation: - let sender = tx.getSender() - result = generateAddress(sender, tx.accountNonce) - else: - result = tx.to - proc captureAccount(n: JsonNode, db: AccountStateDB, address: EthAddress, name: string) = var jaccount = newJObject() jaccount["name"] = %name @@ -113,11 +102,11 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader, stateDiff["beforeRoot"] = %($stateDb.rootHash) beforeRoot = stateDb.rootHash - let txFee = processTransaction(tx, sender, vmState) + gasUsed = processTransaction(tx, sender, vmState) + let txFee = gasUsed.u256 * tx.gasPrice.u256 stateDb.addBalance(header.coinbase, txFee) if idx == txIndex: - gasUsed = (txFee div tx.gasPrice.u256).truncate(GasInt) after.captureAccount(stateDb, sender, senderName) after.captureAccount(stateDb, recipient, recipientName) after.captureAccount(stateDb, header.coinbase, minerName) @@ -215,10 +204,8 @@ proc traceBlock*(db: BaseChainDB, header: BlockHeader, body: BlockBody, tracerFl var gasUsed = GasInt(0) for tx in body.transactions: - let - sender = tx.getSender - txFee = processTransaction(tx, sender, vmState) - gasUsed = gasUsed + (txFee div tx.gasPrice.u256).truncate(GasInt) + let sender = tx.getSender + gasUsed = gasUsed + processTransaction(tx, sender, vmState) result = vmState.getTracingResult() result["gas"] = %gasUsed diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index bc349ea30..9fc16c6d3 100644 --- a/nimbus/transaction.nim +++ b/nimbus/transaction.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - constants, errors, eth/[common, rlp, keys], nimcrypto + constants, errors, eth/[common, rlp, keys], nimcrypto, utils proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAddress, value: UInt256, payload: Blob, V: byte, R, S: UInt256, isContractCreation = false): Transaction = @@ -15,6 +15,7 @@ proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAd result.gasLimit = gasLimit result.to = to result.value = value + result.payload = payload result.V = V result.R = R result.S = S @@ -115,3 +116,9 @@ proc getSender*(transaction: Transaction): EthAddress = if not transaction.getSender(result): raise newException(ValidationError, "Could not derive sender address from transaction") +proc getRecipient*(tx: Transaction): EthAddress = + if tx.isContractCreation: + let sender = tx.getSender() + result = generateAddress(sender, tx.accountNonce) + else: + result = tx.to diff --git a/nimbus/utils.nim b/nimbus/utils.nim index 8421f3335..6ee50d693 100644 --- a/nimbus/utils.nim +++ b/nimbus/utils.nim @@ -1,4 +1,6 @@ -import eth/trie/db, eth/[trie, rlp, common] +import eth/trie/db, eth/[trie, rlp, common], nimcrypto + +export nimcrypto.`$` proc calcRootHash[T](items: openArray[T]): Hash256 = var tr = initHexaryTrie(newMemoryDB()) @@ -11,3 +13,12 @@ template calcTxRoot*(transactions: openArray[Transaction]): Hash256 = template calcReceiptRoot*(receipts: openArray[Receipt]): Hash256 = calcRootHash(receipts) + +func keccak*(value: openarray[byte]): Hash256 {.inline.} = + keccak256.digest value + +func generateAddress*(address: EthAddress, nonce: AccountNonce): EthAddress = + result[0..19] = keccak(rlp.encodeList(address, nonce)).data.toOpenArray(12, 31) + +func hash*(b: BlockHeader): Hash256 {.inline.} = + rlpHash(b) diff --git a/nimbus/utils/addresses.nim b/nimbus/utils/addresses.nim deleted file mode 100644 index 6bbb72433..000000000 --- a/nimbus/utils/addresses.nim +++ /dev/null @@ -1,4 +0,0 @@ -import nimcrypto, eth/[common, rlp] - -func generateAddress*(address: EthAddress, nonce: AccountNonce): EthAddress = - result[0..19] = keccak256.digest(rlp.encodeList(address, nonce)).data.toOpenArray(12, 31) diff --git a/nimbus/utils/header.nim b/nimbus/utils/header.nim index ce4f5315f..735e7d2e2 100644 --- a/nimbus/utils/header.nim +++ b/nimbus/utils/header.nim @@ -91,9 +91,3 @@ proc generateHeaderFromParentHeader*( coinbase: coinbase, # TODO: data: extraData, ) - -import nimcrypto -# TODO: required otherwise -# eth/common/rlp_serialization.nim(18, 12) template/generic instantiation from here -# nimcrypto/hash.nim(46, 6) Error: attempting to call undeclared routine: 'init' -proc hash*(b: BlockHeader): Hash256 {.inline.} = rlpHash(b) diff --git a/nimbus/utils/keccak.nim b/nimbus/utils/keccak.nim deleted file mode 100644 index ae96c0658..000000000 --- a/nimbus/utils/keccak.nim +++ /dev/null @@ -1,19 +0,0 @@ -# Nimbus -# Copyright (c) 2018 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -# at your option. This file may not be copied, modified, or distributed except according to those terms. - -import - nimcrypto, strutils, eth/common - -proc keccak*(value: openarray[byte]): Hash256 {.inline.} = - keccak256.digest value - -proc keccak*(value: string): Hash256 {.inline.} = - keccak256.digest value - -proc keccak*(value: cstring): Hash256 {.inline.} = - # TODO: this is inefficient it allocates for the cstring -> string and then for string -> result - keccak $value diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim index fdacc406c..b3dd5ed70 100644 --- a/nimbus/vm/interpreter/opcodes_impl.nim +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -12,7 +12,7 @@ import ./gas_meter, ./gas_costs, ./opcode_values, ./vm_forks, ../memory, ../message, ../stack, ../code_stream, ../computation, ../../vm_state, ../../errors, ../../constants, ../../vm_types, - ../../db/[db_chain, state_db], ../../utils/addresses + ../../db/[db_chain, state_db], ../../utils logScope: topics = "opcode impl" diff --git a/nimbus/vm/transaction_tracer.nim b/nimbus/vm/transaction_tracer.nim index f863f9921..c25e470e8 100644 --- a/nimbus/vm/transaction_tracer.nim +++ b/nimbus/vm/transaction_tracer.nim @@ -25,6 +25,9 @@ proc initTracer*(tracer: var TransactionTracer, flags: set[TracerFlags] = {}) = tracer.storageKeys = @[] proc prepare*(tracer: var TransactionTracer, compDepth: int) = + # this uncommon arragement is intentional + # compDepth will be varying up and down: 1,2,3,4,3,3,2,2,1 + # see issue #245 and PR #247 discussion if compDepth >= tracer.storageKeys.len: let prevLen = tracer.storageKeys.len tracer.storageKeys.setLen(compDepth + 1) diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index 5e1b457d1..d90d416d5 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -9,7 +9,7 @@ import macros, strformat, tables, sets, eth/common, eth/trie/db, ./constants, ./errors, ./transaction, ./db/[db_chain, state_db], - ./utils/header, json, vm_types, vm/transaction_tracer + ./utils, json, vm_types, vm/transaction_tracer proc newAccessLogs*: AccessLogs = AccessLogs(reads: initTable[string, string](), writes: initTable[string, string]()) diff --git a/nimbus/vm_state_transactions.nim b/nimbus/vm_state_transactions.nim index e41168fea..2f4c6ebce 100644 --- a/nimbus/vm_state_transactions.nim +++ b/nimbus/vm_state_transactions.nim @@ -7,43 +7,69 @@ import ranges/typedranges, sequtils, strformat, tables, options, - eth/common, chronicles, - ./constants, ./errors, ./vm/computation, - ./transaction, ./vm_types, ./vm_state, ./block_types, ./db/[db_chain, state_db], ./utils/header, - ./vm/interpreter, ./vm/interpreter/gas_costs, ./utils/addresses + eth/common, chronicles, ./db/[db_chain, state_db], + constants, errors, transaction, vm_types, vm_state, utils, + ./vm/[computation, interpreter], ./vm/interpreter/gas_costs proc validateTransaction*(vmState: BaseVMState, transaction: Transaction, sender: EthAddress): bool = # XXX: https://github.com/status-im/nimbus/issues/35#issuecomment-391726518 # XXX: lots of avoidable u256 construction var readOnlyDB = vmState.readOnlyStateDB let limitAndValue = transaction.gasLimit.u256 + transaction.value - let gas_cost = transaction.gasLimit.u256 * transaction.gasPrice.u256 + let gasCost = transaction.gasLimit.u256 * transaction.gasPrice.u256 - transaction.gasLimit >= transaction.payload.intrinsicGas and + transaction.gasLimit >= transaction.intrinsicGas and transaction.gasPrice <= (1 shl 34) and limitAndValue <= readOnlyDB.getBalance(sender) and transaction.accountNonce == readOnlyDB.getNonce(sender) and - readOnlyDB.getBalance(sender) >= gas_cost - -proc setupComputation*(vmState: BaseVMState, transaction: Transaction, sender: EthAddress, forkOverride=none(Fork)) : BaseComputation = - let message = newMessage( - gas = transaction.gasLimit - transaction.payload.intrinsicGas, - gasPrice = transaction.gasPrice, - to = transaction.to, - sender = sender, - value = transaction.value, - data = transaction.payload, - code = vmState.readOnlyStateDB.getCode(transaction.to).toSeq, - options = newMessageOptions(origin = sender, - createAddress = transaction.to)) - - result = newBaseComputation(vmState, vmState.blockNumber, message, forkOverride) + readOnlyDB.getBalance(sender) >= gasCost + +proc setupComputation*(vmState: BaseVMState, tx: Transaction, sender: EthAddress, forkOverride=none(Fork)) : BaseComputation = + let fork = + if forkOverride.isSome: + forkOverride.get + else: + vmState.blockNumber.toFork + + var recipient: EthAddress + var gas = tx.gasLimit - tx.intrinsicGas + + # TODO: refactor message to use byterange + # instead of seq[byte] + var data, code: seq[byte] + + if tx.isContractCreation: + recipient = generateAddress(sender, tx.accountNonce) + gas = gas - gasFees[fork][GasTXCreate] + data = @[] + code = tx.payload + else: + recipient = tx.to + data = tx.payload + code = vmState.readOnlyStateDB.getCode(tx.to).toSeq + + let msg = newMessage( + gas = gas, + gasPrice = tx.gasPrice, + to = tx.to, + sender = sender, + value = tx.value, + data = data, + code = code, + options = newMessageOptions(origin = sender, + createAddress = recipient)) + + result = newBaseComputation(vmState, vmState.blockNumber, msg, forkOverride) doAssert result.isOriginComputation proc execComputation*(computation: var BaseComputation): bool = var snapshot = computation.snapshot() defer: snapshot.dispose() + computation.vmState.mutateStateDB: + db.subBalance(computation.msg.origin, computation.msg.value) + db.addBalance(computation.msg.storageAddress, computation.msg.value) + try: computation.executeOpcodes() computation.vmState.mutateStateDB: @@ -52,31 +78,19 @@ proc execComputation*(computation: var BaseComputation): bool = result = not computation.isError except ValueError: result = false + debug "execComputation() error", msg = getCurrentExceptionMsg() if result: snapshot.commit() else: snapshot.revert() -proc applyCreateTransaction*(t: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): UInt256 = - doAssert t.isContractCreation +proc applyCreateTransaction*(tx: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): GasInt = + doAssert tx.isContractCreation # TODO: clean up params trace "Contract creation" - let fork = - if forkOverride.isSome: - forkOverride.get - else: - vmState.blockNumber.toFork - let gasUsed = t.payload.intrinsicGas.GasInt + gasFees[fork][GasTXCreate] - - # 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, vmState.blockNumber, msg, forkOverride) + var c = setupComputation(vmState, tx, sender, forkOverride) if execComputation(c): var db = vmState.accountDb @@ -86,12 +100,10 @@ proc applyCreateTransaction*(t: Transaction, vmState: BaseVMState, sender: EthAd # also a couple lines can collapse because variable used once # once verified in GST fixture let - gasRemaining = c.gasMeter.gasRemaining.u256 - gasRefunded = c.getGasRefund().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 + gasRemaining = c.gasMeter.gasRemaining + gasRefunded = c.getGasRefund() + gasUsed = tx.gasLimit - gasRemaining + gasRefund = min(gasRefunded, gasUsed div 2) var codeCost = 200 * c.output.len @@ -99,10 +111,10 @@ proc applyCreateTransaction*(t: Transaction, vmState: BaseVMState, sender: EthAd # for purposes of accounting. Py-EVM apparently does consume the gas, but it is # not matching observed blockchain balances if consumeGas is called. + let contractAddress = c.msg.storageAddress if not c.isSuicided(contractAddress): # make changes only if it not selfdestructed - db.addBalance(contractAddress, t.value) - if gasRemaining >= codeCost.u256: + if gasRemaining >= codeCost: db.setCode(contractAddress, c.output.toRange) else: # XXX: Homestead behaves differently; reverts state on gas failure @@ -110,20 +122,14 @@ proc applyCreateTransaction*(t: Transaction, vmState: BaseVMState, sender: EthAd codeCost = 0 db.setCode(contractAddress, ByteRange()) - db.addBalance(sender, (t.gasLimit.u256 - gasUsed2 - codeCost.u256 + gasRefund) * t.gasPrice.u256) - return (gasUsed2 + codeCost.u256 - gasRefund) * t.gasPrice.u256 + db.addBalance(sender, (tx.gasLimit - gasUsed - codeCost + gasRefund).u256 * tx.gasPrice.u256) + return (gasUsed + codeCost - gasRefund) 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 - # especially when it's cross-function, it's ugly/fragile - var db = vmState.accountDb - db.addBalance(sender, t.value) - debug "execComputation() error", isError = c.isError - if c.tracingEnabled: - c.traceError() + if c.tracingEnabled: c.traceError() vmState.clearLogs() - return t.gasLimit.u256 * t.gasPrice.u256 + return tx.gasLimit +#[ method executeTransaction(vmState: BaseVMState, transaction: Transaction): (BaseComputation, BlockHeader) {.base.}= # Execute the transaction in the vm # TODO: introduced here: https://github.com/ethereum/py-evm/commit/21c57f2d56ab91bb62723c3f9ebe291d0b132dde @@ -184,3 +190,4 @@ method applyTransaction*( else: var (computation, blockHeader) = vmState.executeTransaction(transaction) return (computation, nil, initTable[string, string]()) +]# \ No newline at end of file diff --git a/premix/.gitignore b/premix/.gitignore index d291b3446..67e723053 100644 --- a/premix/.gitignore +++ b/premix/.gitignore @@ -2,5 +2,7 @@ *.db-lock /output /temp +/data +/nimcache *.json premixData.js diff --git a/premix/hunter.nim b/premix/hunter.nim index 3b099a5d4..f7b31ffca 100644 --- a/premix/hunter.nim +++ b/premix/hunter.nim @@ -1,10 +1,9 @@ import - json, downloader, stint, strutils, byteutils, parser, nimcrypto, - chronicles, ../nimbus/tracer, eth/trie/[trie_defs, db], ../nimbus/vm_state, + json, downloader, stint, strutils, byteutils, parser, + chronicles, ../nimbus/[tracer, vm_state, utils], eth/trie/[trie_defs, db], ../nimbus/db/[db_chain, state_db], ../nimbus/p2p/executor, premixcore, - eth/common, configuration, tables, ../nimbus/vm_types, hashes, - ../nimbus/utils/header - + eth/common, configuration, tables, ../nimbus/vm_types, hashes + const emptyCodeHash = blankStringHash emptyStorageHash = emptyRlpHash @@ -12,7 +11,7 @@ const proc store(memoryDB: TrieDatabaseRef, branch: JsonNode) = for p in branch: let rlp = hexToSeqByte(p.getStr) - let hash = keccak256.digest(rlp) + let hash = keccak(rlp) memoryDB.put(hash.data, rlp) proc parseAddress(address: string): EthAddress = diff --git a/premix/premixcore.nim b/premix/premixcore.nim index 80870d8e6..7b3dcaca3 100644 --- a/premix/premixcore.nim +++ b/premix/premixcore.nim @@ -1,7 +1,7 @@ import json, strutils, os, stint, chronicles, eth/common, - ../nimbus/tracer, ../nimbus/launcher, + ../nimbus/transaction, ../nimbus/launcher, ./js_tracer, ./parser, ./downloader proc fakeAlloc(n: JsonNode) = diff --git a/tests/macro_assembler.nim b/tests/macro_assembler.nim index 81c948119..9073bee81 100644 --- a/tests/macro_assembler.nim +++ b/tests/macro_assembler.nim @@ -6,11 +6,10 @@ import import options, json, os, eth/trie/[db, hexary], - ../nimbus/[vm_state, tracer, vm_types, transaction], + ../nimbus/[vm_state, tracer, vm_types, transaction, utils], ../nimbus/db/[db_chain, state_db], ../nimbus/vm_state_transactions, ../nimbus/vm/interpreter/[vm_forks, gas_costs], - ../nimbus/utils/addresses, ../nimbus/vm/[message, computation, memory] export opcode_values, byteutils @@ -223,7 +222,7 @@ proc initComputation(blockNumber: Uint256, chainDB: BaseChainDB, payload, data: var tx = body.transactions[0] - sender = tracer.getSender(tx) + sender = transaction.getSender(tx) tx.payload = payload tx.gasLimit = 500000000 diff --git a/tests/test_generalstate_failing.nim b/tests/test_generalstate_failing.nim index 9988afb03..5d2f42002 100644 --- a/tests/test_generalstate_failing.nim +++ b/tests/test_generalstate_failing.nim @@ -24,8 +24,6 @@ func allowedFailingGeneralStateTest*(folder, name: string): bool = "callcodecallcode_11.json", "callcodecallcodecall_110.json", "callcodecallcodecallcode_111.json", - "CREATE_ContractSuicideDuringInit_ThenStoreThenReturn.json", - "CREATE_ContractSuicideDuringInit_WithValue.json", "CREATE_EContractCreateEContractInInit_Tr.json", "CREATE_EContract_ThenCALLToNonExistentAcc.json", "CREATE_EmptyContract.json", @@ -59,7 +57,6 @@ func allowedFailingGeneralStateTest*(folder, name: string): bool = "RawCreateGasMemory.json", "RawCreateGasValueTransferMemory.json", "createContractViaContractOOGInitCode.json", - "createContractViaTransactionCost53000.json", "CallContractToCreateContractNoCash.json", "CallContractToCreateContractOOG.json", "CallContractToCreateContractWhichWouldCreateContractIfCalled.json", @@ -67,7 +64,6 @@ func allowedFailingGeneralStateTest*(folder, name: string): bool = "CallTheContractToCreateEmptyContract.json", "OutOfGasContractCreation.json", "OutOfGasPrefundedContractCreation.json", - "TransactionCreateAutoSuicideContract.json", "callDataCopyOffset.json", "codeCopyOffset.json", "CALLCODEEcrecover0_0input.json", @@ -103,8 +99,6 @@ func allowedFailingGeneralStateTest*(folder, name: string): bool = "randomStatetest85.json", #"randomStatetest579.json", "randomStatetest643.json", - "randomStatetest644.json", - "randomStatetest645.json", "randomStatetest646.json", "randomStatetest248.json", "RevertOpcodeCalls.json", @@ -121,12 +115,9 @@ func allowedFailingGeneralStateTest*(folder, name: string): bool = "CreateHashCollision.json", "suicideCoinbase.json", "testRandomTest.json", - "EmptyTransaction2.json", - "EmptyTransaction3.json", "Opcodes_TransactionInit.json", "SuicidesMixingCoinbase.json", "TransactionFromCoinbaseHittingBlockGasLimit1.json", - "TransactionSendingToEmpty.json", "createNameRegistratorPerTxsNotEnoughGasAfter.json", "createNameRegistratorPerTxsNotEnoughGasAt.json", "createNameRegistratorPerTxsNotEnoughGasBefore.json", diff --git a/tests/test_generalstate_json.nim b/tests/test_generalstate_json.nim index 17d3f443f..bffc606cf 100644 --- a/tests/test_generalstate_json.nim +++ b/tests/test_generalstate_json.nim @@ -9,10 +9,9 @@ import unittest, strformat, strutils, tables, json, ospaths, times, byteutils, ranges/typedranges, nimcrypto/[keccak, hash], options, eth/[rlp, common, keys], eth/trie/db, chronicles, - ./test_helpers, + ./test_helpers, ../nimbus/p2p/executor, ../nimbus/[constants, errors], - ../nimbus/[vm_state, vm_types, vm_state_transactions], - ../nimbus/utils/[header, addresses], + ../nimbus/[vm_state, vm_types, vm_state_transactions, utils], ../nimbus/vm/interpreter, ../nimbus/db/[db_chain, state_db] @@ -21,8 +20,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) suite "generalstate json tests": jsonTest("GeneralStateTests", testFixture) - -proc testFixtureIndexes(prevStateRoot: Hash256, header: BlockHeader, pre: JsonNode, transaction: Transaction, sender: EthAddress, expectedHash: string, testStatusIMPL: var TestStatus, fork: Fork) = +proc testFixtureIndexes(prevStateRoot: Hash256, header: BlockHeader, pre: JsonNode, tx: Transaction, sender: EthAddress, expectedHash: string, testStatusIMPL: var TestStatus, fork: Fork) = when enabledLogLevel <= TRACE: let tracerFlags = {TracerFlags.EnableTracing} else: @@ -36,63 +34,27 @@ proc testFixtureIndexes(prevStateRoot: Hash256, header: BlockHeader, pre: JsonNo let obtainedHash = "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii check obtainedHash == expectedHash - if not validateTransaction(vmState, transaction, sender): + if not validateTransaction(vmState, tx, sender): vmState.mutateStateDB: # pre-EIP158 (e.g., Byzantium) should ensure currentCoinbase exists # in later forks, don't create at all db.addBalance(header.coinbase, 0.u256) return - # TODO: replace with cachingDb or similar approach; necessary - # when calls/subcalls/etc come in, too. - var readOnly = vmState.readOnlyStateDB - let storageRoot = readOnly.getStorageRoot(transaction.to) - - let gas_cost = transaction.gasLimit.u256 * transaction.gasPrice.u256 + let gasCost = tx.gasLimit.u256 * tx.gasPrice.u256 vmState.mutateStateDB: - db.setNonce(sender, db.getNonce(sender) + 1) - db.subBalance(sender, transaction.value + gas_cost) + db.incNonce(sender) + db.subBalance(sender, gasCost) - if transaction.isContractCreation and transaction.payload.len > 0: + if tx.isContractCreation and tx.payload.len > 0: vmState.mutateStateDB: - # TODO: move into applyCreateTransaction - # fixtures/GeneralStateTests/stTransactionTest/TransactionSendingToZero.json - # fixtures/GeneralStateTests/stTransactionTest/TransactionSendingToEmpty.json - #db.addBalance(generateAddress(sender, transaction.accountNonce), transaction.value) - - let createGasUsed = applyCreateTransaction(transaction, vmState, sender, some(fork)) - db.addBalance(header.coinbase, createGasUsed) + let createGasUsed = applyCreateTransaction(tx, vmState, sender, some(fork)) + db.addBalance(header.coinbase, createGasUsed.u256 * tx.gasPrice.u256) return - var computation = setupComputation(vmState, transaction, sender, some(fork)) vmState.mutateStateDB: - # contract creation transaction.to == 0, so ensure happens after - db.addBalance(transaction.to, transaction.value) - - # What remains is call and/or value transfer - if execComputation(computation): - let - gasRemaining = computation.gasMeter.gasRemaining.u256 - gasRefunded = computation.getGasRefund().u256 - gasUsed = transaction.gasLimit.u256 - gasRemaining - gasRefund = min(gasRefunded, gasUsed div 2) - gasRefundAmount = (gasRefund + gasRemaining) * transaction.gasPrice.u256 - - vmState.mutateStateDB: - # TODO if the balance/etc calls were gated on gAFD or similar, - # that would simplify/combine codepaths - if header.coinbase notin computation.getAccountsForDeletion: - db.subBalance(header.coinbase, gasRefundAmount) - db.addBalance(header.coinbase, gas_cost) - db.addBalance(sender, gasRefundAmount) - # TODO: only here does one commit, with some nuance/caveat - else: - vmState.mutateStateDB: - # XXX: the coinbase has to be committed; the rest are basically reverts - db.subBalance(transaction.to, transaction.value) - db.addBalance(sender, transaction.value) - db.setStorageRoot(transaction.to, storageRoot) - db.addBalance(header.coinbase, gas_cost) + let gasUsed = contractCall(tx, vmState, sender, some(fork)) + db.addBalance(header.coinbase, gasUsed.u256 * tx.gasPrice.u256) proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = var fixture: JsonNode diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index b854e8f35..562b2c189 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -7,17 +7,17 @@ import unittest, strformat, strutils, sequtils, tables, json, ospaths, times, - byteutils, ranges/typedranges, nimcrypto/[keccak, hash], + byteutils, ranges/typedranges, eth/[rlp, common], eth/trie/db, ./test_helpers, ../nimbus/[constants, errors], ../nimbus/[vm_state, vm_types], - ../nimbus/utils/header, + ../nimbus/utils, ../nimbus/vm/interpreter, ../nimbus/db/[db_chain, state_db] proc hashLogEntries(logs: seq[Log]): string = - toLowerAscii("0x" & $keccak256.digest(rlp.encode(logs))) + toLowerAscii("0x" & $keccak(rlp.encode(logs))) proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) @@ -32,7 +32,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = break let fenv = fixture["env"] - var emptyRlpHash = keccak256.digest(rlp.encode("")) + var emptyRlpHash = keccak(rlp.encode("")) let header = BlockHeader( coinbase: fenv{"currentCoinbase"}.getStr.parseAddress, difficulty: fromHex(UInt256, fenv{"currentDifficulty"}.getStr),