From 18b7bbb3b0b80a01be10f1ce85e9ae89c93425a7 Mon Sep 17 00:00:00 2001 From: zah Date: Thu, 5 Jul 2018 15:41:01 +0300 Subject: [PATCH] Implemented most of the stubbed-out state handling instructions (#59) Merge note: currently cannot compile due to `quasiBoolean` (#63). This will be solved by https://github.com/status-im/nimbus/pull/65 ---- * Implemented most of the stubbed out state handling instructions The code compiles, but still fails at the moment due to incorrect initialization of the VM. Don't merge yet. More commits will be pushed in the coming days. * Fixed crash * trie put and del are void now * getBlockTransactionData and getReceipts * Working code for extcodesize0.json * fix origin.json * fix calldatasize1 * fix calldataloadSizeTooHighPartial * fix calldataloadSizeTooHigh * more efficient PushX implementation * fix and, or, xor --- VMTests.md | 184 +++++++++--------- nimbus/account.nim | 12 +- nimbus/block_types.nim | 2 +- nimbus/constants.nim | 6 +- nimbus/db/db_chain.nim | 89 +++++---- nimbus/db/state_db.nim | 120 +++++++----- nimbus/db/storage_types.nim | 4 +- nimbus/transaction.nim | 2 +- nimbus/utils/header.nim | 2 +- nimbus/validation.nim | 2 +- nimbus/vm/code_stream.nim | 27 ++- nimbus/vm/computation.nim | 3 +- .../frontier_validation.nim | 3 +- .../forks/f20150730_frontier/frontier_vm.nim | 5 +- .../tangerine_validation.nim | 3 +- .../tangerine_vm.nim | 4 +- nimbus/vm/interpreter/gas_costs.nim | 2 +- nimbus/vm/interpreter/opcode_table.nim | 6 +- .../interpreter/opcodes_impl/arithmetic.nim | 4 +- nimbus/vm/interpreter/opcodes_impl/block.nim | 3 +- nimbus/vm/interpreter/opcodes_impl/call.nim | 2 +- .../interpreter/opcodes_impl/comparison.nim | 12 +- .../vm/interpreter/opcodes_impl/context.nim | 97 +++++---- .../vm/interpreter/opcodes_impl/helpers.nim | 4 +- .../opcodes_impl/impl_std_import.nim | 4 +- .../vm/interpreter/opcodes_impl/stack_ops.nim | 9 +- .../vm/interpreter/opcodes_impl/storage.nim | 25 +-- .../interpreter/opcodes_impl/system_ops.nim | 10 +- nimbus/vm/interpreter/vm_forks.nim | 4 +- nimbus/vm/memory.nim | 37 ++-- nimbus/vm/message.nim | 9 +- nimbus/vm/stack.nim | 10 +- nimbus/vm/utils/utils_numeric.nim | 3 +- nimbus/vm_state.nim | 65 ++++--- nimbus/vm_types.nim | 3 +- tests/all_tests.nim | 17 +- tests/test_gas_meter.nim | 7 +- tests/test_helpers.nim | 47 ++++- tests/test_memory.nim | 7 +- tests/test_opcode.nim | 6 +- tests/test_stack.nim | 6 +- tests/test_storage_backends.nim | 2 +- tests/test_vm_json.nim | 42 ++-- 43 files changed, 518 insertions(+), 393 deletions(-) diff --git a/VMTests.md b/VMTests.md index c1e92d7f5..a111e729b 100644 --- a/VMTests.md +++ b/VMTests.md @@ -115,7 +115,7 @@ VMTests + expPowerOf2_4.json OK + expPowerOf2_64.json OK + expPowerOf2_8.json OK -- expXY.json Fail ++ expXY.json OK + expXY_success.json OK + fibbonacci_unrolled.json OK + mod0.json OK @@ -150,12 +150,12 @@ VMTests + mulmoddivByZero2.json OK + mulmoddivByZero3.json OK + not1.json OK -+ sdiv0.json OK +- sdiv0.json Fail + sdiv1.json OK + sdiv2.json OK -+ sdiv3.json OK -+ sdiv4.json OK -+ sdiv5.json OK +- sdiv3.json Fail +- sdiv4.json Fail +- sdiv5.json Fail + sdiv6.json OK + sdiv7.json OK + sdiv8.json OK @@ -164,8 +164,8 @@ VMTests + sdivByZero1.json OK + sdivByZero2.json OK + sdiv_dejavu.json OK -+ sdiv_i256min.json OK -+ sdiv_i256min2.json OK +- sdiv_i256min.json Fail +- sdiv_i256min2.json Fail + sdiv_i256min3.json OK + signextendInvalidByteNumber.json OK + signextend_00.json OK @@ -180,9 +180,9 @@ VMTests + signextend_Overflow_dj42.json OK + signextend_bigBytePlus1.json OK + signextend_bitIsSet.json OK -+ smod0.json OK -+ smod1.json OK -+ smod2.json OK +- smod0.json Fail +- smod1.json Fail +- smod2.json Fail + smod3.json OK + smod4.json OK + smod5.json OK @@ -190,7 +190,7 @@ VMTests + smod7.json OK + smod8_byZero.json OK + smod_i256min1.json OK -+ smod_i256min2.json OK +- smod_i256min2.json Fail + stop.json OK + sub0.json OK + sub1.json OK @@ -198,7 +198,7 @@ VMTests + sub3.json OK + sub4.json OK ``` -OK: 194/195 Fail: 1/195 Skip: 0/195 +OK: 185/195 Fail: 10/195 Skip: 0/195 ## vmBitwiseLogicOperation ```diff + and0.json OK @@ -267,7 +267,7 @@ OK: 60/60 Fail: 0/60 Skip: 0/60 ```diff + blockhash257Block.json OK + blockhash258Block.json OK -+ blockhashInRange.json OK +- blockhashInRange.json Fail + blockhashMyBlock.json OK + blockhashNotExistingBlock.json OK + blockhashOutOfRange.json OK @@ -278,21 +278,21 @@ OK: 60/60 Fail: 0/60 Skip: 0/60 + number.json OK + timestamp.json OK ``` -OK: 12/12 Fail: 0/12 Skip: 0/12 +OK: 11/12 Fail: 1/12 Skip: 0/12 ## vmEnvironmentalInfo ```diff - ExtCodeSizeAddressInputTooBigLeftMyAddress.json Skip - ExtCodeSizeAddressInputTooBigRightMyAddress.json Skip - address0.json Skip - address1.json Skip - balance0.json Skip - balance01.json Skip - balance1.json Skip - balanceAddress2.json Skip - balanceAddressInputTooBig.json Skip - balanceAddressInputTooBigLeftMyAddress.json Skip ++ ExtCodeSizeAddressInputTooBigLeftMyAddress.json OK ++ ExtCodeSizeAddressInputTooBigRightMyAddress.json OK ++ address0.json OK ++ address1.json OK ++ balance0.json OK +- balance01.json Fail +- balance1.json Fail ++ balanceAddress2.json OK ++ balanceAddressInputTooBig.json OK +- balanceAddressInputTooBigLeftMyAddress.json Fail balanceAddressInputTooBigRightMyAddress.json Skip - balanceCaller3.json Skip ++ balanceCaller3.json OK calldatacopy0.json Skip calldatacopy0_return.json Skip calldatacopy1.json Skip @@ -307,41 +307,41 @@ OK: 12/12 Fail: 0/12 Skip: 0/12 calldatacopy_DataIndexTooHigh2_return.json Skip calldatacopy_DataIndexTooHigh_return.json Skip calldatacopy_sec.json Skip - calldataload0.json Skip - calldataload1.json Skip - calldataload2.json Skip - calldataloadSizeTooHigh.json Skip - calldataloadSizeTooHighPartial.json Skip - calldataload_BigOffset.json Skip - calldatasize0.json Skip - calldatasize1.json Skip - calldatasize2.json Skip - caller.json Skip - callvalue.json Skip - codecopy0.json Skip - codecopyZeroMemExpansion.json Skip - codecopy_DataIndexTooHigh.json Skip - codesize.json Skip - env1.json Skip - extcodecopy0.json Skip - extcodecopy0AddressTooBigLeft.json Skip - extcodecopy0AddressTooBigRight.json Skip - extcodecopyZeroMemExpansion.json Skip - extcodecopy_DataIndexTooHigh.json Skip - extcodesize0.json Skip - extcodesize1.json Skip - extcodesizeUnderFlow.json Skip - gasprice.json Skip - origin.json Skip ++ calldataload0.json OK ++ calldataload1.json OK ++ calldataload2.json OK ++ calldataloadSizeTooHigh.json OK ++ calldataloadSizeTooHighPartial.json OK ++ calldataload_BigOffset.json OK ++ calldatasize0.json OK ++ calldatasize1.json OK ++ calldatasize2.json OK ++ caller.json OK ++ callvalue.json OK +- codecopy0.json Fail ++ codecopyZeroMemExpansion.json OK +- codecopy_DataIndexTooHigh.json Fail ++ codesize.json OK +- env1.json Fail +- extcodecopy0.json Fail +- extcodecopy0AddressTooBigLeft.json Fail +- extcodecopy0AddressTooBigRight.json Fail +- extcodecopyZeroMemExpansion.json Fail +- extcodecopy_DataIndexTooHigh.json Fail ++ extcodesize0.json OK ++ extcodesize1.json OK ++ extcodesizeUnderFlow.json OK ++ gasprice.json OK ++ origin.json OK ``` -OK: 0/52 Fail: 0/52 Skip: 52/52 +OK: 26/52 Fail: 11/52 Skip: 15/52 ## vmIOandFlowOperations ```diff + BlockNumberDynamicJump0_AfterJumpdest.json OK + BlockNumberDynamicJump0_AfterJumpdest3.json OK + BlockNumberDynamicJump0_foreverOutOfGas.json OK -- BlockNumberDynamicJump0_jumpdest0.json Fail -- BlockNumberDynamicJump0_jumpdest2.json Fail ++ BlockNumberDynamicJump0_jumpdest0.json OK ++ BlockNumberDynamicJump0_jumpdest2.json OK + BlockNumberDynamicJump0_withoutJumpdest.json OK + BlockNumberDynamicJump1.json OK + BlockNumberDynamicJumpInsidePushWithJumpDest.json OK @@ -353,7 +353,7 @@ OK: 0/52 Fail: 0/52 Skip: 52/52 - BlockNumberDynamicJumpiOutsideBoundary.json Fail + BlockNumberDynamicJumpifInsidePushWithJumpDest.json OK + BlockNumberDynamicJumpifInsidePushWithoutJumpDest.json OK -- DyanmicJump0_outOfBoundary.json Fail ++ DyanmicJump0_outOfBoundary.json OK + DynamicJump0_AfterJumpdest.json OK + DynamicJump0_AfterJumpdest3.json OK + DynamicJump0_foreverOutOfGas.json OK @@ -366,15 +366,15 @@ OK: 0/52 Fail: 0/52 Skip: 52/52 + DynamicJumpInsidePushWithoutJumpDest.json OK + DynamicJumpJD_DependsOnJumps0.json OK + DynamicJumpJD_DependsOnJumps1.json OK -- DynamicJumpPathologicalTest0.json Fail ++ DynamicJumpPathologicalTest0.json OK + DynamicJumpPathologicalTest1.json OK + DynamicJumpPathologicalTest2.json OK + DynamicJumpPathologicalTest3.json OK + DynamicJumpStartWithJumpDest.json OK -+ DynamicJump_value1.json OK -+ DynamicJump_value2.json OK -+ DynamicJump_value3.json OK -+ DynamicJump_valueUnderflow.json OK +- DynamicJump_value1.json Fail +- DynamicJump_value2.json Fail +- DynamicJump_value3.json Fail +- DynamicJump_valueUnderflow.json Fail + DynamicJumpi0.json OK + DynamicJumpi1.json OK + DynamicJumpi1_jumpdest.json OK @@ -382,22 +382,22 @@ OK: 0/52 Fail: 0/52 Skip: 52/52 - DynamicJumpiOutsideBoundary.json Fail + DynamicJumpifInsidePushWithJumpDest.json OK + DynamicJumpifInsidePushWithoutJumpDest.json OK -- JDfromStorageDynamicJump0_AfterJumpdest.json Fail -- JDfromStorageDynamicJump0_AfterJumpdest3.json Fail -- JDfromStorageDynamicJump0_foreverOutOfGas.json Fail ++ JDfromStorageDynamicJump0_AfterJumpdest.json OK ++ JDfromStorageDynamicJump0_AfterJumpdest3.json OK ++ JDfromStorageDynamicJump0_foreverOutOfGas.json OK - JDfromStorageDynamicJump0_jumpdest0.json Fail - JDfromStorageDynamicJump0_jumpdest2.json Fail -- JDfromStorageDynamicJump0_withoutJumpdest.json Fail -- JDfromStorageDynamicJump1.json Fail -- JDfromStorageDynamicJumpInsidePushWithJumpDest.json Fail -- JDfromStorageDynamicJumpInsidePushWithoutJumpDest.json Fail -- JDfromStorageDynamicJumpi0.json Fail ++ JDfromStorageDynamicJump0_withoutJumpdest.json OK ++ JDfromStorageDynamicJump1.json OK ++ JDfromStorageDynamicJumpInsidePushWithJumpDest.json OK ++ JDfromStorageDynamicJumpInsidePushWithoutJumpDest.json OK ++ JDfromStorageDynamicJumpi0.json OK - JDfromStorageDynamicJumpi1.json Fail -- JDfromStorageDynamicJumpi1_jumpdest.json Fail ++ JDfromStorageDynamicJumpi1_jumpdest.json OK - JDfromStorageDynamicJumpiAfterStop.json Fail -- JDfromStorageDynamicJumpiOutsideBoundary.json Fail -- JDfromStorageDynamicJumpifInsidePushWithJumpDest.json Fail -- JDfromStorageDynamicJumpifInsidePushWithoutJumpDest.json Fail ++ JDfromStorageDynamicJumpiOutsideBoundary.json OK ++ JDfromStorageDynamicJumpifInsidePushWithJumpDest.json OK ++ JDfromStorageDynamicJumpifInsidePushWithoutJumpDest.json OK + bad_indirect_jump1.json OK + bad_indirect_jump2.json OK + byte1.json OK @@ -461,9 +461,9 @@ OK: 0/52 Fail: 0/52 Skip: 52/52 + mstore0.json OK + mstore1.json OK + mstore8MemExp.json OK -+ mstore8WordToBigError.json OK -+ mstore8_0.json OK -+ mstore8_1.json OK +- mstore8WordToBigError.json Fail +- mstore8_0.json Fail +- mstore8_1.json Fail + mstoreMemExp.json OK + mstoreWordToBigError.json OK + mstore_mload0.json OK @@ -475,15 +475,15 @@ OK: 0/52 Fail: 0/52 Skip: 52/52 - return2.json Fail + sha3MemExp.json OK + sstore_load_0.json OK -+ sstore_load_1.json OK +- sstore_load_1.json Fail - sstore_load_2.json Fail + sstore_underflow.json OK -+ stack_loop.json OK +- stack_loop.json Fail + stackjump1.json OK + swapAt52becameMstore.json OK + when.json OK ``` -OK: 103/145 Fail: 41/145 Skip: 1/145 +OK: 110/145 Fail: 34/145 Skip: 1/145 ## vmLogTest ```diff + log0_emptyMem.json OK @@ -614,7 +614,7 @@ OK: 0/18 Fail: 0/18 Skip: 18/18 + push7.json OK + push8.json OK + push9.json OK -+ swap1.json OK +- swap1.json Fail + swap10.json OK + swap11.json OK + swap12.json OK @@ -631,9 +631,9 @@ OK: 0/18 Fail: 0/18 Skip: 18/18 + swap7.json OK + swap8.json OK + swap9.json OK -+ swapjump1.json OK +- swapjump1.json Fail ``` -OK: 74/74 Fail: 0/74 Skip: 0/74 +OK: 72/74 Fail: 2/74 Skip: 0/74 ## vmRandomTest ```diff 201503102037PYTHON.json Skip @@ -658,25 +658,25 @@ OK: 0/17 Fail: 0/17 Skip: 17/17 ## vmSha3Test ```diff + sha3_0.json OK -+ sha3_1.json OK -+ sha3_2.json OK +- sha3_1.json Fail +- sha3_2.json Fail + sha3_3.json OK + sha3_4.json OK + sha3_5.json OK - sha3_6.json Fail - sha3_bigOffset.json Fail -+ sha3_bigOffset2.json OK +- sha3_bigOffset2.json Fail - sha3_bigSize.json Fail -+ sha3_memSizeNoQuadraticCost31.json OK -+ sha3_memSizeQuadraticCost32.json OK +- sha3_memSizeNoQuadraticCost31.json Fail +- sha3_memSizeQuadraticCost32.json Fail - sha3_memSizeQuadraticCost32_zeroSize.json Fail -+ sha3_memSizeQuadraticCost33.json OK -+ sha3_memSizeQuadraticCost63.json OK -+ sha3_memSizeQuadraticCost64.json OK -+ sha3_memSizeQuadraticCost64_2.json OK -+ sha3_memSizeQuadraticCost65.json OK +- sha3_memSizeQuadraticCost33.json Fail +- sha3_memSizeQuadraticCost63.json Fail +- sha3_memSizeQuadraticCost64.json Fail +- sha3_memSizeQuadraticCost64_2.json Fail +- sha3_memSizeQuadraticCost65.json Fail ``` -OK: 14/18 Fail: 4/18 Skip: 0/18 +OK: 4/18 Fail: 14/18 Skip: 0/18 ## vmSystemOperations ```diff ABAcalls0.json Skip diff --git a/nimbus/account.nim b/nimbus/account.nim index 58e235690..19f76cdd0 100644 --- a/nimbus/account.nim +++ b/nimbus/account.nim @@ -6,16 +6,18 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - constants, errors, stint, rlp, eth_common + constants, errors, eth_common, rlp, eth_common/eth_types type - Account* = ref object + Account* = object nonce*: UInt256 balance*: UInt256 storageRoot*: Hash256 codeHash*: Hash256 -rlpFields Account, nonce, balance - proc newAccount*(nonce: UInt256 = 0.u256, balance: UInt256 = 0.u256): Account = - Account(nonce: nonce, balance: balance) + result.nonce = nonce + result.balance = balance + result.storageRoot = BLANK_ROOT_HASH + result.codeHash = EMPTY_SHA3 + diff --git a/nimbus/block_types.nim b/nimbus/block_types.nim index 8471a8294..4bc101ad9 100644 --- a/nimbus/block_types.nim +++ b/nimbus/block_types.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, eth_common, + eth_common, ./logging, ./constants type diff --git a/nimbus/constants.nim b/nimbus/constants.nim index 3d95c63c2..3ad8d7a85 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -1,6 +1,6 @@ import - stint, math, strutils, utils/padding, eth_common + math, strutils, utils/padding, eth_common proc default(t: typedesc): t = discard @@ -48,8 +48,8 @@ let GENESIS_EXTRA_DATA* = "" GAS_LIMIT_MINIMUM* = 5000 - EMPTYSHA3 = "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p" - BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest() + BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest + EMPTY_SHA3* = "883f7328a6c30727a655daff17eba3a86049871bc7839a5b71e2bc26a99c4d4c".toDigest GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.u256 diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index f314afbd9..0ec580bdd 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -5,13 +5,14 @@ # * 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 stint, tables, sequtils, algorithm, rlp, ranges, state_db, nimcrypto, - ../errors, ../block_types, ../utils/header, ../constants, eth_common, byteutils, - ./storage_types.nim, backends/memory_backend +import + tables, sequtils, algorithm, + rlp, ranges, state_db, nimcrypto, eth_trie/[types, hexary], eth_common, byteutils, + ../errors, ../block_types, ../utils/header, ../constants, ./storage_types.nim type BaseChainDB* = ref object - db*: MemoryDB + db*: TrieDatabaseRef # TODO db*: JournalDB KeyType = enum @@ -22,13 +23,10 @@ type blockNumber: BlockNumber index: int -proc newBaseChainDB*(db: MemoryDB): BaseChainDB = +proc newBaseChainDB*(db: TrieDatabaseRef): BaseChainDB = new(result) result.db = db -proc contains*(self: BaseChainDB; key: Hash256): bool = - return self.db.contains(genericHashKey(key)) - proc `$`*(db: BaseChainDB): string = result = "BaseChainDB" @@ -37,17 +35,17 @@ proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader = ## ## Raises BlockNotFound if it is not present in the db. try: - let blk = self.db.get(genericHashKey(blockHash)) + let blk = self.db.get(genericHashKey(blockHash).toOpenArray).toRange return decode(blk, BlockHeader) except KeyError: raise newException(BlockNotFound, "No block with hash " & blockHash.data.toHex) proc getHash(self: BaseChainDB, key: DbKey): Hash256 {.inline.} = - rlp.decode(self.db.get(key).toRange, Hash256) + rlp.decode(self.db.get(key.toOpenArray).toRange, Hash256) proc getCanonicalHead*(self: BaseChainDB): BlockHeader = let k = canonicalHeadHashKey() - if k notin self.db: + if k.toOpenArray notin self.db: raise newException(CanonicalHeadNotFound, "No canonical head set for this chain") return self.getBlockHeaderByHash(self.getHash(k)) @@ -64,7 +62,7 @@ proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockH self.getBlockHeaderByHash(self.lookupBlockHash(n)) proc getScore*(self: BaseChainDB; blockHash: Hash256): int = - rlp.decode(self.db.get(blockHashToScoreKey(blockHash)).toRange, int) + rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray).toRange, int) iterator findNewAncestors(self: BaseChainDB; header: BlockHeader): BlockHeader = ## Returns the chain leading up from the given header until the first ancestor it has in @@ -86,7 +84,8 @@ iterator findNewAncestors(self: BaseChainDB; header: BlockHeader): BlockHeader = h = self.getBlockHeaderByHash(h.parentHash) proc addBlockNumberToHashLookup(self: BaseChainDB; header: BlockHeader) = - self.db.set(blockNumberToHashKey(header.blockNumber), rlp.encode(header.hash)) + self.db.put(blockNumberToHashKey(header.blockNumber).toOpenArray, + rlp.encode(header.hash).toOpenArray) iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): Hash256 = ## Returns an iterable of the transaction hashes from th block specified @@ -101,7 +100,7 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): proc removeTransactionFromCanonicalChain(self: BaseChainDB, transactionHash: Hash256) {.inline.} = ## Removes the transaction specified by the given hash from the canonical chain. - self.db.delete(transactionHashToBlockKey(transactionHash)) + self.db.del(transactionHashToBlockKey(transactionHash).toOpenArray) proc setAsCanonicalChainHead(self: BaseChainDB; headerHash: Hash256): seq[BlockHeader] = ## Sets the header as the canonical chain HEAD. @@ -125,36 +124,36 @@ proc setAsCanonicalChainHead(self: BaseChainDB; headerHash: Hash256): seq[BlockH for h in newCanonicalHeaders: self.addBlockNumberToHashLookup(h) - self.db.set(canonicalHeadHashKey(), rlp.encode(header.hash)) + self.db.put(canonicalHeadHashKey().toOpenArray, rlp.encode(header.hash).toOpenArray) + return newCanonicalHeaders proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool = ## Returns True if the header with the given block hash is in our DB. - self.contains(blockHash) + self.db.contains(blockHash.data) iterator getBlockTransactionData(self: BaseChainDB, transactionRoot: Hash256): BytesRange = - doAssert(false, "TODO: Implement me") - # var transactionDb = HexaryTrie(self.db, transactionRoot) - # var transactionIdx = 0 - # while true: - # var transactionKey = rlp.encode(transactionIdx) - # if transactionKey in transactionDb: - # var transactionData = transactionDb[transactionKey] - # yield transactionDb[transactionKey] - # else: - # break - # inc transactionIdx + var transactionDb = initHexaryTrie(self.db, transactionRoot) + var transactionIdx = 0 + while true: + let transactionKey = rlp.encode(transactionIdx).toRange + if transactionKey in transactionDb: + yield transactionDb.get(transactionKey) + else: + break + inc transactionIdx - -# iterator getReceipts*(self: BaseChainDB; header: BlockHeader; receiptClass: typedesc): Receipt = -# var receiptDb = HexaryTrie() -# for receiptIdx in itertools.count(): -# var receiptKey = rlp.encode(receiptIdx) -# if receiptKey in receiptDb: -# var receiptData = receiptDb[receiptKey] -# yield rlp.decode(receiptData) -# else: -# break +iterator getReceipts*(self: BaseChainDB; header: BlockHeader; receiptClass: typedesc): Receipt = + var receiptDb = initHexaryTrie(self.db, header.receiptRoot) + var receiptIdx = 0 + while true: + let receiptKey = rlp.encode(receiptIdx).toRange + if receiptKey in receiptDb: + let receiptData = receiptDb.get(receiptKey) + yield rlp.decode(receiptData, receiptClass) + else: + break + inc receiptIdx iterator getBlockTransactions(self: BaseChainDB; transactionRoot: Hash256; transactionClass: typedesc): transactionClass = @@ -166,10 +165,12 @@ proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader if not isGenesis and not self.headerExists(header.parentHash): raise newException(ParentNotFound, "Cannot persist block header " & $header.hash & " with unknown parent " & $header.parentHash) - self.db.set(genericHashKey(header.hash), rlp.encode(header)) + self.db.put(genericHashKey(header.hash).toOpenArray, rlp.encode(header).toOpenArray) + let score = if isGenesis: header.difficulty else: self.getScore(header.parentHash).u256 + header.difficulty - self.db.set(blockHashToScoreKey(header.hash), rlp.encode(score)) + self.db.put(blockHashToScoreKey(header.hash).toOpenArray, rlp.encode(score).toOpenArray) + var headScore: int try: headScore = self.getScore(self.getCanonicalHead().hash) @@ -179,18 +180,17 @@ proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader if score > headScore.u256: result = self.setAsCanonicalChainHead(header.hash) - proc addTransactionToCanonicalChain(self: BaseChainDB, txHash: Hash256, blockHeader: BlockHeader, index: int) = let k: TransactionKey = (blockHeader.blockNumber, index) - self.db.set(transactionHashToBlockKey(txHash), rlp.encode(k)) + self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(k).toOpenArray) 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.toOpenArray()) - self.db.set(genericHashKey(result), enc) + self.db.put(genericHashKey(result).toOpenArray, enc.toOpenArray) proc persistBlockToDb*(self: BaseChainDB; blk: Block) = ## Persist the given block's header and uncles. @@ -227,6 +227,5 @@ proc persistBlockToDb*(self: BaseChainDB; blk: Block) = # proc clear*(self: BaseChainDB): void = # self.db.clear() -method getStateDb*(self: BaseChainDB; stateRoot: Hash256; readOnly: bool = false): AccountStateDB = - # TODO - result = newAccountStateDB(initTable[string, string]()) +proc getStateDb*(self: BaseChainDB; stateRoot: Hash256; readOnly: bool = false): AccountStateDB = + result = newAccountStateDB(self.db, stateRoot) diff --git a/nimbus/db/state_db.nim b/nimbus/db/state_db.nim index dfa673d79..2db99ea87 100644 --- a/nimbus/db/state_db.nim +++ b/nimbus/db/state_db.nim @@ -6,33 +6,44 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - eth_common, tables, nimcrypto, stint, rlp, + sequtils, tables, + eth_common, nimcrypto, rlp, eth_trie/[hexary, memdb], ../constants, ../errors, ../validation, ../account, ../logging type AccountStateDB* = ref object - db*: Table[string, BytesRange] - rootHash*: Hash256 # TODO trie + trie: HexaryTrie -proc newAccountStateDB*(db: Table[string, string], readOnly: bool = false): AccountStateDB = - result = AccountStateDB(db: initTable[string, BytesRange]()) +proc rootHash*(accountDb: AccountStateDB): KeccakHash = + accountDb.trie.rootHash + +# proc `rootHash=`*(db: var AccountStateDB, value: string) = +# TODO: self.Trie.rootHash = value + +proc newAccountStateDB*(backingStore: TrieDatabaseRef, + root: KeccakHash, readOnly: bool = false): AccountStateDB = + result.new() + result.trie = initHexaryTrie(backingStore, root) proc logger*(db: AccountStateDB): Logger = logging.getLogger("db.State") +template createRangeFromAddress(address: EthAddress): ByteRange = + ## XXX: The name of this proc is intentionally long, because it + ## performs a memory allocation and data copying that may be eliminated + ## in the future. Avoid renaming it to something similar as `toRange`, so + ## it can remain searchable in the code. + toRange(@address) + proc getAccount(db: AccountStateDB, address: EthAddress): Account = - # let rlpAccount = db.trie[address] - # if not rlpAccount.isNil: - # account = rlp.decode[Account](rlpAccount) - # account.mutable = true - # else: - # account = newAccount() - result = newAccount() # TODO + let recordFound = db.trie.get(createRangeFromAddress address) + if recordFound.len > 0: + result = rlp.decode(recordFound, Account) + else: + result = newAccount() proc setAccount(db: AccountStateDB, address: EthAddress, account: Account) = - # db.trie[address] = rlp.encode[Account](account) - discard # TODO - + db.trie.put createRangeFromAddress(address), rlp.encode(account) proc getCodeHash*(db: AccountStateDB, address: EthAddress): Hash256 = validateCanonicalAddress(address, title="Storage Address") @@ -46,51 +57,55 @@ proc getBalance*(db: AccountStateDB, address: EthAddress): UInt256 = proc setBalance*(db: var AccountStateDB, address: EthAddress, balance: UInt256) = validateCanonicalAddress(address, title="Storage Address") - let account = db.getAccount(address) + var account = db.getAccount(address) account.balance = balance db.setAccount(address, account) proc deltaBalance*(db: var AccountStateDB, address: EthAddress, delta: UInt256) = db.setBalance(address, db.getBalance(address) + delta) +template createTrieKeyFromSlot(slot: UInt256): ByteRange = + # XXX: This is too expensive. Similar to `createRangeFromAddress` + # Converts a number to hex big-endian representation including + # prefix and leading zeros: + @(keccak256.digest(slot.toByteArrayBE).data).toRange + # Original py-evm code: + # pad32(int_to_big_endian(slot)) -proc setStorage*(db: var AccountStateDB, address: EthAddress, slot: UInt256, value: UInt256) = +proc setStorage*(db: var AccountStateDB, + address: EthAddress, + slot: UInt256, value: UInt256) = #validateGte(value, 0, title="Storage Value") #validateGte(slot, 0, title="Storage Slot") validateCanonicalAddress(address, title="Storage Address") - # TODO - # let account = db.getAccount(address) - # var storage = HashTrie(HexaryTrie(self.db, account.storageRoot)) + var account = db.getAccount(address) + var accountTrie = initHexaryTrie(db.trie.db, account.storageRoot) + let slotAsKey = createTrieKeyFromSlot slot - let slotAsKey = "0x" & slot.dumpHex # Convert to a number to hex big-endian representation including prefix and leading zeros - var storage = db.db - # TODO fix if value > 0: - let encodedValue = rlp.encode value.toByteArrayBE - storage[slotAsKey] = encodedValue + let encodedValue = rlp.encode value + accountTrie.put(slotAsKey, encodedValue) else: - storage.del(slotAsKey) - #storage[slotAsKey] = value - # account.storageRoot = storage.rootHash - # db.setAccount(address, account) + accountTrie.del(slotAsKey) -proc getStorage*(db: var AccountStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) = + account.storageRoot = accountTrie.rootHash + db.setAccount(address, account) + +proc getStorage*(db: AccountStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) = validateCanonicalAddress(address, title="Storage Address") #validateGte(slot, 0, title="Storage Slot") - # TODO - # make it more correct - # for now, we just use a table + let + account = db.getAccount(address) + slotAsKey = createTrieKeyFromSlot slot + accountTrie = initHexaryTrie(db.trie.db, account.storageRoot) - # let account = db.GetAccount(address) - # var storage = HashTrie(HexaryTrie(self.db, account.storageRoot)) + let + foundRecord = accountTrie.get(slotAsKey) - let slotAsKey = "0x" & slot.dumpHex # Convert to a number to hex big-endian representation including prefix and leading zeros - var storage = db.db - if storage.hasKey(slotAsKey): - let byteRange = storage[slotAsKey] - result = (readUintBE[256](byteRange.toOpenArray), true) + if foundRecord.len > 0: + result = (rlp.decode(foundRecord, UInt256), true) else: result = (0.u256, false) @@ -109,24 +124,21 @@ proc getNonce*(db: AccountStateDB, address: EthAddress): UInt256 = let account = db.getAccount(address) return account.nonce -proc setCode*(db: var AccountStateDB, address: EthAddress, code: string) = +proc toByteRange_Unnecessary*(h: KeccakHash): ByteRange = + ## XXX: Another proc used to mark unnecessary conversions it the code + var s = @(h.data) + return s.toRange + +proc setCode*(db: var AccountStateDB, address: EthAddress, code: ByteRange) = validateCanonicalAddress(address, title="Storage Address") var account = db.getAccount(address) - - account.codeHash = keccak256.digest code - #db.db[account.codeHash] = code + account.codeHash = keccak256.digest code.toOpenArray + # XXX: this uses the journaldb in py-evm + db.trie.put(account.codeHash.toByteRange_Unnecessary, code) db.setAccount(address, account) -proc getCode*(db: var AccountStateDB, address: EthAddress): string = +proc getCode*(db: AccountStateDB, address: EthAddress): ByteRange = let codeHash = db.getCodeHash(address) - #if db.db.hasKey(codeHash): - # result = db.db[codeHash] - #else: - result = "" + result = db.trie.get(codeHash.toByteRange_Unnecessary) -# proc rootHash*(db: AccountStateDB): string = - # TODO return self.Trie.rootHash - -# proc `rootHash=`*(db: var AccountStateDB, value: string) = - # TODO: self.Trie.rootHash = value diff --git a/nimbus/db/storage_types.nim b/nimbus/db/storage_types.nim index 978efbc67..b04fae691 100644 --- a/nimbus/db/storage_types.nim +++ b/nimbus/db/storage_types.nim @@ -11,8 +11,8 @@ type DbKey* = object # The first byte stores the key type. The rest are key-specific values - data: array[33, byte] - dataEndPos: uint8 # the last populated position in the data + data*: array[33, byte] + dataEndPos*: uint8 # the last populated position in the data StorageError* = object of Exception diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index 5d14b3c06..24e6c6a09 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, stint, errors, eth_common + constants, errors, eth_common type BaseTransaction* = ref object diff --git a/nimbus/utils/header.nim b/nimbus/utils/header.nim index 2a8593240..e1265c86a 100644 --- a/nimbus/utils/header.nim +++ b/nimbus/utils/header.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. -import eth_common, ../constants, stint, strformat, times, ../validation, rlp +import eth_common, ../constants, strformat, times, ../validation, rlp export BlockHeader diff --git a/nimbus/validation.nim b/nimbus/validation.nim index 242ebea13..b6b71e159 100644 --- a/nimbus/validation.nim +++ b/nimbus/validation.nim @@ -7,7 +7,7 @@ import strformat, - errors, constants, stint, eth_common + errors, constants, eth_common template validateCanonicalAddress*(value: EthAddress, title: string = "Value") = # TODO: This needs to be removed diff --git a/nimbus/vm/code_stream.nim b/nimbus/vm/code_stream.nim index d3b46440a..f7cfed64c 100644 --- a/nimbus/vm/code_stream.nim +++ b/nimbus/vm/code_stream.nim @@ -7,6 +7,7 @@ import strformat, strutils, sequtils, parseutils, sets, macros, + eth_common, ../logging, ../constants, ./interpreter/opcode_values type @@ -51,6 +52,17 @@ proc read*(c: var CodeStream, size: int): seq[byte] = result = @[] c.pc = c.bytes.len +proc readVmWord*(c: var CodeStream, n: int): UInt256 = + ## Reads `n` bytes bytes from the code stream and pads + ## the remaining bytes with zeros. + let result_bytes = cast[ptr array[32, byte]](addr result) + + let last = min(c.pc + n, c.bytes.len) + let toWrite = last - c.pc + for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 1] + for j in toWrite ..< 32: result_bytes[j] = 0 + c.pc = last + proc len*(c: CodeStream): int = len(c.bytes) @@ -78,16 +90,15 @@ proc peek*(c: var CodeStream): Op = proc updatePc*(c: var CodeStream, value: int) = c.pc = min(value, len(c)) -macro seek*(c: var CodeStream, pc: int, handler: untyped): untyped = - let c2 = ident("c") - result = quote: - var anchorPc = `c`.pc - `c`.pc = `pc` +when false: + template seek*(cs: var CodeStream, pc: int, handler: untyped): untyped = + var anchorPc = cs.pc + cs.pc = pc try: - var `c2` = `c` - `handler` + var c {.inject.} = cs + handler finally: - `c`.pc = anchorPc + cs.pc = anchorPc proc isValidOpcode*(c: var CodeStream, position: int): bool = if position >= len(c): diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index 2d6cda063..a41f29b1a 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - strformat, strutils, sequtils, macros, stint, terminal, math, eth_common, byteutils, tables, + strformat, strutils, sequtils, macros, terminal, math, tables, + eth_common, byteutils, ../constants, ../errors, ../validation, ../vm_state, ../logging, ../vm_types, ./interpreter/[opcode_values,gas_meter, gas_costs], ./code_stream, ./memory, ./message, ./stack, diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim b/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim index 57f6969e1..e541f3ea8 100644 --- a/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim +++ b/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - strformat, stint, + strformat, + eth_common/eth_types, ../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header proc validateFrontierTransaction*(vmState: BaseVmState, transaction: BaseTransaction) = diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim b/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim index 7ddb3a0a1..d34ec5439 100644 --- a/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim +++ b/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim @@ -6,13 +6,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, eth_common/eth_types, + eth_common/eth_types, ../../../logging, ../../../constants, ../../../errors, ../../../block_types, ../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header, ./frontier_block, ./frontier_vm_state, ./frontier_validation - type FrontierVM* = ref object of VM @@ -34,4 +33,6 @@ proc newFrontierVM*(header: BlockHeader, chainDB: BaseChainDB): FrontierVM = result.chainDB = chainDB result.isStateless = true result.state = newFrontierVMState() + result.state.chaindb = result.chainDB + result.state.blockHeader = header result.`block` = makeFrontierBlock(header, @[]) diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim index 8cc26a305..d784236f1 100644 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim +++ b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - strformat, stint, + strformat, + eth_common/eth_types, ../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header proc validateTangerineTransaction*(vmState: BaseVmState, transaction: BaseTransaction) = diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim index 4f1fadfe9..63462c975 100644 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim +++ b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim @@ -6,8 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + eth_common/eth_types, ../../../logging, ../../../constants, ../../../errors, - stint, ../../../block_types, ../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header, ./tangerine_block, ./tangerine_vm_state, ./tangerine_validation @@ -34,4 +34,6 @@ proc newTangerineVM*(header: BlockHeader, chainDB: BaseChainDB): TangerineVM = result.chainDB = chainDB result.isStateless = true result.state = newTangerineVMState() + result.state.chaindb = result.chainDB + result.state.blockHeader = header result.`block` = makeTangerineBlock(header, @[]) diff --git a/nimbus/vm/interpreter/gas_costs.nim b/nimbus/vm/interpreter/gas_costs.nim index 55c092762..a4d25ac6b 100644 --- a/nimbus/vm/interpreter/gas_costs.nim +++ b/nimbus/vm/interpreter/gas_costs.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, math, eth_common, # GasInt + math, eth_common/eth_types, ../utils/[macros_gen_opcodes, utils_numeric], ./opcode_values diff --git a/nimbus/vm/interpreter/opcode_table.nim b/nimbus/vm/interpreter/opcode_table.nim index e9c2ec27e..393238192 100644 --- a/nimbus/vm/interpreter/opcode_table.nim +++ b/nimbus/vm/interpreter/opcode_table.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - tables, stint, + tables, eth_common/eth_types, ../../vm_types, ./opcode_values, opcodes_impl/[arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops] @@ -51,8 +51,8 @@ const CallDataLoad: callDataLoad, CallDataSize: callDataSize, CallDataCopy: callDataCopy, - CodeSize: codesize, - CodeCopy: codecopy, + CodeSize: codeSize, + CodeCopy: codeCopy, GasPrice: gasPrice, # TODO this wasn't used previously ExtCodeSize: extCodeSize, ExtCodeCopy: extCodeCopy, diff --git a/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim b/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim index a778c70fa..54cc207dd 100644 --- a/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim +++ b/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim @@ -6,7 +6,9 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - helpers, stint, strutils, ./impl_std_import + strutils, + eth_common/eth_types, + helpers, ./impl_std_import proc add*(computation: var BaseComputation) = # Addition diff --git a/nimbus/vm/interpreter/opcodes_impl/block.nim b/nimbus/vm/interpreter/opcodes_impl/block.nim index 16391574f..f9267846b 100644 --- a/nimbus/vm/interpreter/opcodes_impl/block.nim +++ b/nimbus/vm/interpreter/opcodes_impl/block.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../constants, ../computation, ../vm/stack, ../vm_state, stint + eth_common/eth_types, + ../constants, ../computation, ../vm/stack, ../vm_state proc blockhash*(computation: var BaseComputation) = var blockNumber = computation.stack.popInt() diff --git a/nimbus/vm/interpreter/opcodes_impl/call.nim b/nimbus/vm/interpreter/opcodes_impl/call.nim index 59eb943fe..04dc74674 100644 --- a/nimbus/vm/interpreter/opcodes_impl/call.nim +++ b/nimbus/vm/interpreter/opcodes_impl/call.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - strformat, eth_common, stint, + strformat, eth_common, # ./impl_std_import # Cannot do that due to recursive dependencies # .../vm/interpreter/opcodes_impl/impl_std_import.nim imports .../vm/computation.nim # .../vm/computation.nim imports .../vm/interpreter/opcodes_impl/call.nim diff --git a/nimbus/vm/interpreter/opcodes_impl/comparison.nim b/nimbus/vm/interpreter/opcodes_impl/comparison.nim index 752414eb3..569c92b95 100644 --- a/nimbus/vm/interpreter/opcodes_impl/comparison.nim +++ b/nimbus/vm/interpreter/opcodes_impl/comparison.nim @@ -18,11 +18,17 @@ quasiBoolean(sgt, `>`, signed=true) # Signed Greater Comparison quasiBoolean(eq, `==`) # Equality -quasiBoolean(andOp, `and`, nonzero=true) # Bitwise And +proc andOp*(computation: var BaseComputation) = + let (lhs, rhs) = computation.stack.popInt(2) + computation.stack.push(lhs and rhs) -quasiBoolean(orOp, `or`, nonzero=true) # Bitwise Or +proc orOp*(computation: var BaseComputation) = + let (lhs, rhs) = computation.stack.popInt(2) + computation.stack.push(lhs or rhs) -quasiBoolean(xorOp, `xor`, nonzero=true) # Bitwise XOr +proc xorOp*(computation: var BaseComputation) = + let (lhs, rhs) = computation.stack.popInt(2) + computation.stack.push(lhs xor rhs) # TODO use isZero from Stint proc iszero*(computation: var BaseComputation) = diff --git a/nimbus/vm/interpreter/opcodes_impl/context.nim b/nimbus/vm/interpreter/opcodes_impl/context.nim index 00fc85ffa..61c77615b 100644 --- a/nimbus/vm/interpreter/opcodes_impl/context.nim +++ b/nimbus/vm/interpreter/opcodes_impl/context.nim @@ -7,14 +7,14 @@ import strformat, - ./impl_std_import + ranges/typedranges, + ./impl_std_import, + ../../../db/state_db proc balance*(computation: var BaseComputation) = let address = computation.stack.popAddress() - var balance: Int256 - # TODO computation.vmState.stateDB(read_only=True): - # balance = db.getBalance(address) - # computation.stack.push(balance) + let balance = computation.vmState.readOnlyStateDB.getBalance(address) + computation.stack.push balance proc origin*(computation: var BaseComputation) = computation.stack.push(computation.msg.origin) @@ -25,18 +25,53 @@ proc address*(computation: var BaseComputation) = proc caller*(computation: var BaseComputation) = computation.stack.push(computation.msg.sender) - proc callValue*(computation: var BaseComputation) = computation.stack.push(computation.msg.value) +proc writePaddedResult(mem: var Memory, + data: openarray[byte], + memPos, dataPos, len: Natural, + paddingValue = 0.byte) = + mem.extend(memPos, len) + + let dataEndPosition = dataPos + len - 1 + if dataEndPosition < data.len: + mem.write(memPos, data[dataPos .. dataEndPosition]) + else: + var presentElements = data.len - dataPos + if presentElements > 0: + mem.write(memPos, data.toOpenArray(dataPos, data.len - 1)) + else: + presentElements = 0 + + mem.writePaddingBytes(memPos + presentElements, + len - presentElements, + paddingValue) + proc callDataLoad*(computation: var BaseComputation) = # Load call data into memory - let startPosition = computation.stack.popInt.toInt - let value = computation.msg.data[startPosition ..< startPosition + 32] - let paddedValue = padRight(value, 32, 0.byte) - let normalizedValue = paddedValue.lStrip(0.byte) - computation.stack.push(normalizedValue) + let origDataPos = computation.stack.popInt + if origDataPos >= computation.msg.data.len: + computation.stack.push(0) + return + let + dataPos = origDataPos.toInt + dataEndPosition = dataPos + 31 + + if dataEndPosition < computation.msg.data.len: + computation.stack.push(computation.msg.data[dataPos .. dataEndPosition]) + else: + var bytes: array[32, byte] + var presentBytes = min(computation.msg.data.len - dataPos, 32) + + if presentBytes > 0: + copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes) + else: + presentBytes = 0 + + for i in presentBytes ..< 32: bytes[i] = 0 + computation.stack.push(bytes) proc callDataSize*(computation: var BaseComputation) = let size = computation.msg.data.len.u256 @@ -52,19 +87,15 @@ proc callDataCopy*(computation: var BaseComputation) = reason="CALLDATACOPY fee") let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt) - computation.memory.extend(memPos, len) - let value = computation.msg.data[callPos ..< callPos + len] - let paddedValue = padRight(value, len, 0.byte) - computation.memory.write(memPos, paddedValue) + computation.memory.writePaddedResult(computation.msg.data, + memPos, callPos, len) - -proc codesize*(computation: var BaseComputation) = +proc codeSize*(computation: var BaseComputation) = let size = computation.code.len.u256 computation.stack.push(size) - -proc codecopy*(computation: var BaseComputation) = +proc codeCopy*(computation: var BaseComputation) = let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3) @@ -74,26 +105,16 @@ proc codecopy*(computation: var BaseComputation) = reason="CODECOPY: word gas cost") let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt) - computation.memory.extend(memPos, len) - # TODO - # with computation.code.seek(code_start_position): - # code_bytes = computation.code.read(size) - # padded_code_bytes = pad_right(code_bytes, size, b'\x00') - # computation.memory.write(mem_start_position, size, padded_code_bytes) + computation.memory.writePaddedResult(computation.code.bytes, memPos, codePos, len) - -proc gasprice*(computation: var BaseComputation) = +proc gasPrice*(computation: var BaseComputation) = computation.stack.push(computation.msg.gasPrice.u256) - proc extCodeSize*(computation: var BaseComputation) = let account = computation.stack.popAddress() - # TODO - # with computation.vm_state.state_db(read_only=True) as state_db: - # code_size = len(state_db.get_code(account)) - - # computation.stack.push(code_size) + let codeSize = computation.vmState.readOnlyStateDB.getCode(account).len + computation.stack.push uint(codeSize) proc extCodeCopy*(computation: var BaseComputation) = let account = computation.stack.popAddress() @@ -105,15 +126,9 @@ proc extCodeCopy*(computation: var BaseComputation) = ) let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt) - computation.memory.extend(memPos, len) + let codeBytes = computation.vmState.readOnlyStateDB.getCode(account) - - # TODO: - # with computation.vm_state.state_db(read_only=True) as state_db: - # code = state_db.get_code(account) - # code_bytes = code[code_start_position:code_start_position + size] - # padded_code_bytes = pad_right(code_bytes, size, b'\x00') - # computation.memory.write(mem_start_position, size, padded_code_bytes) + computation.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len) proc returnDataSize*(computation: var BaseComputation) = let size = computation.returnData.len.u256 diff --git a/nimbus/vm/interpreter/opcodes_impl/helpers.nim b/nimbus/vm/interpreter/opcodes_impl/helpers.nim index 27f70ac67..efcd1f19a 100644 --- a/nimbus/vm/interpreter/opcodes_impl/helpers.nim +++ b/nimbus/vm/interpreter/opcodes_impl/helpers.nim @@ -5,7 +5,9 @@ # * 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 macros, stint +import + macros, + eth_common/eth_types template pushRes*: untyped = computation.stack.push(res) diff --git a/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim b/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim index 3672d6b54..b5d12854c 100644 --- a/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim +++ b/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, stint/lenient_stint, + eth_common/eth_types, stint/lenient_stint, ../../../constants, ../../../vm_state, ../../../vm_types, ../../../vm_types, ../../../errors, ../../../logging, ../../../utils/padding, ../../../utils/bytes, ../../stack, ../../computation, ../../stack, ../../memory, ../../message, @@ -14,7 +14,7 @@ import ../opcode_values, ../gas_meter, ../gas_costs export - stint, lenient_stint, + eth_types, lenient_stint, constants, vm_state, vm_types, vm_types, errors, logging, padding, bytes, stack, computation, stack, memory, message, diff --git a/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim b/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim index fb491b4d3..2400e8193 100644 --- a/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim +++ b/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim @@ -23,14 +23,7 @@ macro pushXX(size: static[int]): untyped = let name = ident(&"push{size}") result = quote: proc `name`*(`computation`: var BaseComputation) = - let `value` = `computation`.code.read(`size`) - let stripped = `value`.toString.strip(0.char) - if stripped.len == 0: - `computation`.stack.push(0.u256) - else: - let paddedValue = `value`.padRight(`size`, 0.byte) - `computation`.stack.push(paddedValue) - + `computation`.stack.push `computation`.code.readVmWord(`size`) pushXX(1) pushXX(2) diff --git a/nimbus/vm/interpreter/opcodes_impl/storage.nim b/nimbus/vm/interpreter/opcodes_impl/storage.nim index 18f97e26d..0da67cabe 100644 --- a/nimbus/vm/interpreter/opcodes_impl/storage.nim +++ b/nimbus/vm/interpreter/opcodes_impl/storage.nim @@ -10,7 +10,6 @@ import ../../../utils/header, ../../../db/[db_chain, state_db] - {.this: computation.} {.experimental.} @@ -20,32 +19,26 @@ using proc sstore*(computation) = let (slot, value) = stack.popInt(2) - var currentValue = 0.u256 - var existing = false - - computation.vmState.db(readOnly=false): - (currentValue, existing) = db.getStorage(computation.msg.storageAddress, slot) + var (currentValue, existing) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) let gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing) (gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam) - computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})") + computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[{slot}] -> {value} ({currentValue})") if gasRefund > 0: computation.gasMeter.refundGas(gasRefund) - computation.vmState.db(readOnly=false): + computation.vmState.mutateStateDB: db.setStorage(computation.msg.storageAddress, slot, value) proc sload*(computation) = let slot = stack.popInt() - var value = 2.u256 + let (value, found) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) + if found: + computation.stack.push value + else: + # XXX: raise exception? + discard - # TODO: with - # with computation.vm_state.state_db(read_only=True) as state_db: - # value = state_db.get_storage( - # address=computation.msg.storage_address, - # slot=slot, - # ) - computation.stack.push(value) diff --git a/nimbus/vm/interpreter/opcodes_impl/system_ops.nim b/nimbus/vm/interpreter/opcodes_impl/system_ops.nim index 21c56673d..d8f226029 100644 --- a/nimbus/vm/interpreter/opcodes_impl/system_ops.nim +++ b/nimbus/vm/interpreter/opcodes_impl/system_ops.nim @@ -8,7 +8,7 @@ import strformat, ./call, ./impl_std_import, - stint, byteutils, eth_common + byteutils, eth_common {.this: computation.} {.experimental.} @@ -32,7 +32,7 @@ method runLogic*(create: Create, computation) = let (pos, len) = (startPosition.toInt, size.toInt) computation.memory.extend(pos, len) - # TODO: with + # TODO: with ZZZZ # with computation.vm_state.state_db(read_only=True) as state_db: # insufficient_funds = state_db.get_balance( # computation.msg.storage_address) < value @@ -94,7 +94,7 @@ method runLogic*(create: CreateByzantium, computation) = proc selfdestructEIP150(computation) = let beneficiary = stack.popAddress() - # TODO: with + # TODO: with ZZZZ # with computation.vm_state.state_db(read_only=True) as state_db: # if not state_db.account_exists(beneficiary): # computation.gas_meter.consume_gas( @@ -105,7 +105,7 @@ proc selfdestructEIP150(computation) = proc selfdestructEIP161(computation) = let beneficiary = stack.popAddress() - # TODO: with + # TODO: with ZZZZ # with computation.vm_state.state_db(read_only=True) as state_db: # is_dead = ( # not state_db.account_exists(beneficiary) or @@ -119,7 +119,7 @@ proc selfdestructEIP161(computation) = # _selfdestruct(computation, beneficiary) proc selfdestruct(computation; beneficiary: EthAddress) = - discard # TODO: with + discard # TODO: with ZZZZ # with computation.vm_state.state_db() as state_db: # local_balance = state_db.get_balance(computation.msg.storage_address) # beneficiary_balance = state_db.get_balance(beneficiary) diff --git a/nimbus/vm/interpreter/vm_forks.nim b/nimbus/vm/interpreter/vm_forks.nim index c189f8561..dd98715dd 100644 --- a/nimbus/vm/interpreter/vm_forks.nim +++ b/nimbus/vm/interpreter/vm_forks.nim @@ -6,12 +6,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + eth_common/eth_types, ../../db/db_chain, ../../constants, ../../utils/header, ../base, ../forks/f20150730_frontier/frontier_vm, - ../forks/f20161018_tangerine_whistle/tangerine_vm, - stint + ../forks/f20161018_tangerine_whistle/tangerine_vm # Note (mamy): refactoring is in progress (2018-05-23), this is redundant with # - `Chain` in src/chain.nim, to be honest I don't understand the need of this abstraction at the moment diff --git a/nimbus/vm/memory.nim b/nimbus/vm/memory.nim index be47fdfdf..f9e66186e 100644 --- a/nimbus/vm/memory.nim +++ b/nimbus/vm/memory.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - sequtils, stint, + sequtils, + eth_common/eth_types, ../constants, ../errors, ../logging, ../validation, ../utils/bytes, ./utils/utils_numeric @@ -24,10 +25,10 @@ proc len*(memory: Memory): int = result = memory.bytes.len -proc extend*(memory: var Memory; startPosition: Natural; size: Natural) = +proc extend*(memory: var Memory; startPos: Natural; size: Natural) = if size == 0: return - var newSize = ceil32(startPosition + size) + var newSize = ceil32(startPos + size) if newSize <= len(memory): return var sizeToExtend = newSize - len(memory) @@ -37,27 +38,35 @@ proc newMemory*(size: Natural): Memory = result = newMemory() result.extend(0, size) -proc read*(memory: var Memory, startPosition: Natural, size: Natural): seq[byte] = - result = memory.bytes[startPosition ..< (startPosition + size)] +proc read*(memory: var Memory, startPos: Natural, size: Natural): seq[byte] = + result = memory.bytes[startPos ..< (startPos + size)] -proc write*(memory: var Memory, startPosition: Natural, value: openarray[byte]) = +proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) = let size = value.len if size == 0: return #echo size - #echo startPosition - #validateGte(startPosition, 0) + #echo startPos + #validateGte(startPos, 0) #validateGte(size, 0) - validateLte(startPosition + size, memory.len) + validateLte(startPos + size, memory.len) let index = memory.len - if memory.len < startPosition + size: - memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPosition + size))) # TODO: better logarithmic scaling? + if memory.len < startPos + size: + memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPos + size))) # TODO: better logarithmic scaling? for z, b in value: - memory.bytes[z + startPosition] = b + memory.bytes[z + startPos] = b -template write*(memory: var Memory, startPosition: Natural, size: Natural, value: cstring) = - memory.write(startPosition, value.toBytes) +proc writePaddingBytes*(memory: var Memory, + startPos, numberOfBytes: Natural, + paddingValue = 0.byte) = + let endPos = startPos + numberOfBytes + assert endPos < memory.len + for i in startPos ..< endPos: + memory.bytes[i] = paddingValue + +template write*(memory: var Memory, startPos: Natural, size: Natural, value: cstring) = + memory.write(startPos, value.toBytes) # TODO ~ O(n^3): # - there is a string allocation with $ (?) # - then a conversion to seq (= new allocation) with toBytes diff --git a/nimbus/vm/message.nim b/nimbus/vm/message.nim index 77f803252..64c833100 100644 --- a/nimbus/vm/message.nim +++ b/nimbus/vm/message.nim @@ -6,8 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, eth_common, - ../logging, ../constants, ../validation, ../vm_types + eth_common, + ../logging, ../constants, ../validation, ../vm_types proc `origin=`*(message: var Message, value: EthAddress) = message.internalOrigin = value @@ -59,7 +59,10 @@ proc newMessage*( result.data = data - result.internalOrigin = options.origin + if options.origin != ZERO_ADDRESS: + result.internalOrigin = options.origin + else: + result.internalOrigin = sender validateGte(options.depth, minimum=0, title="Message.depth") result.depth = options.depth diff --git a/nimbus/vm/stack.nim b/nimbus/vm/stack.nim index 76d924465..b8342d9da 100644 --- a/nimbus/vm/stack.nim +++ b/nimbus/vm/stack.nim @@ -7,7 +7,7 @@ import strformat, strutils, sequtils, macros, rlp, eth_common, nimcrypto, - ../errors, ../validation, ./utils/utils_numeric, ../constants, stint, ../logging, .. / utils / bytes + ../errors, ../validation, ./utils/utils_numeric, ../constants, ../logging, .. / utils / bytes type Stack* = ref object of RootObj @@ -29,12 +29,12 @@ proc toStackElement(v: EthAddress, elem: var StackElement) {.inline.} = elem = b proc toStackElement(v: MDigest, elem: var StackElement) {.inline.} = elem = readUintBE[256](v.data) proc fromStackElement(elem: StackElement, v: var UInt256) {.inline.} = v = elem -proc fromStackElement(elem: StackElement, v: var EthAddress) {.inline.} = v[0 .. ^1] = elem.toByteArrayBE().toOpenArray(0, 19) +proc fromStackElement(elem: StackElement, v: var EthAddress) {.inline.} = v[0 .. ^1] = elem.toByteArrayBE().toOpenArray(12, 31) proc fromStackElement(elem: StackElement, v: var Hash256) {.inline.} = v.data = elem.toByteArrayBE() -proc toStackElement(v: seq[byte], elem: var StackElement) {.inline.} = +proc toStackElement(v: openarray[byte], elem: var StackElement) {.inline.} = # TODO: This needs to go - validateStackItem(v) + # validateStackItem(v) elem = bigEndianToInt(v) proc pushAux[T](stack: var Stack, value: T) = @@ -45,7 +45,7 @@ proc pushAux[T](stack: var Stack, value: T) = proc push*(stack: var Stack, value: uint | UInt256 | EthAddress | Hash256) {.inline.} = pushAux(stack, value) -proc push*(stack: var Stack, value: seq[byte]) {.inline.} = +proc push*(stack: var Stack, value: openarray[byte]) {.inline.} = # TODO: This needs to go... pushAux(stack, value) diff --git a/nimbus/vm/utils/utils_numeric.nim b/nimbus/vm/utils/utils_numeric.nim index 804f00b10..e01b31f76 100644 --- a/nimbus/vm/utils/utils_numeric.nim +++ b/nimbus/vm/utils/utils_numeric.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - stint, strformat, strutils, sequtils, endians, macros, rlp, + strformat, strutils, sequtils, endians, macros, + eth_common/eth_types, rlp, ../../constants, ../../utils/padding # some methods based on py-evm utils/numeric diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index 25758a72b..8cdc59bb7 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -7,7 +7,7 @@ import macros, strformat, tables, - stint, eth_common, + eth_common, ./logging, ./constants, ./errors, ./transaction, ./db/[db_chain, state_db], ./utils/header @@ -77,31 +77,46 @@ method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 var header = vmState.prevHeaders[idx] result = header.hash -macro db*(vmState: untyped, readOnly: untyped, handler: untyped): untyped = - # vm.state.db: - # setupStateDB(fixture{"pre"}, stateDb) - # code = db.getCode(fixture{"exec"}{"address"}.getStr) - let db = ident("db") - result = quote: - block: - var `db` = `vmState`.chaindb.getStateDB(`vmState`.blockHeader.stateRoot, `readOnly`) - `handler` - if `readOnly`: - # This acts as a secondary check that no mutation took place for - # read_only databases. - assert `db`.rootHash == `vmState`.blockHeader.stateRoot - elif `vmState`.blockHeader.stateRoot != `db`.rootHash: - `vmState`.blockHeader.stateRoot = `db`.rootHash +when false: + # this was an older version of `mutateStateDB`, kept here for reference + # until `mutateStateDB` is fully implemented. + macro db*(vmState: untyped, readOnly: bool, handler: untyped): untyped = + # vm.state.db: + # setupStateDB(fixture{"pre"}, stateDb) + # code = db.getCode(fixture{"exec"}{"address"}.getStr) + let db = ident("db") + result = quote: + block: + var `db` = `vmState`.chaindb.getStateDB(`vmState`.blockHeader.stateRoot, `readOnly`) + `handler` + if `readOnly`: + # This acts as a secondary check that no mutation took place for + # read_only databases. + assert `db`.rootHash == `vmState`.blockHeader.stateRoot + elif `vmState`.blockHeader.stateRoot != `db`.rootHash: + `vmState`.blockHeader.stateRoot = `db`.rootHash - # TODO - # `vmState`.accessLogs.reads.update(`db`.db.accessLogs.reads) - # `vmState`.accessLogs.writes.update(`db`.db.accessLogs.writes) + # TODO + # `vmState`.accessLogs.reads.update(`db`.db.accessLogs.reads) + # `vmState`.accessLogs.writes.update(`db`.db.accessLogs.writes) - # remove the reference to the underlying `db` object to ensure that no - # further modifications can occur using the `State` object after - # leaving the context. - # TODO `db`.db = nil - # state._trie = None + # remove the reference to the underlying `db` object to ensure that no + # further modifications can occur using the `State` object after + # leaving the context. + # TODO `db`.db = nil + # state._trie = None + +template mutateStateDB*(vmState: BaseVMState, body: untyped) = + # This should provide more clever change handling in the future + block: + let initialStateRoot = vmState.blockHeader.stateRoot + var db {.inject.} = vmState.chaindb.getStateDB(initialStateRoot, false) + + body + + let finalStateRoot = db.rootHash + if finalStateRoot != initialStateRoot: + vmState.blockHeader.stateRoot = finalStateRoot proc readOnlyStateDB*(vmState: BaseVMState): AccountStateDB {.inline.}= - vmState.chaindb.getStateDb(Hash256(), readOnly = true) + vmState.chaindb.getStateDb(vmState.blockHeader.stateRoot, readOnly = true) diff --git a/nimbus/vm_types.nim b/nimbus/vm_types.nim index 5a4c3c518..315eeebc9 100644 --- a/nimbus/vm_types.nim +++ b/nimbus/vm_types.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - tables, stint, eth_common, + tables, + eth_common, ./constants, ./vm_state, ./logging, ./vm/[memory, stack, code_stream], ./vm/interpreter/[gas_costs, opcode_values] # TODO - will be hidden at a lower layer diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 461037de7..80e2ced06 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -5,10 +5,13 @@ # * 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 ./test_code_stream, - ./test_gas_meter, - ./test_memory, - ./test_stack, - ./test_opcode, - ./test_storage_backends - # ./test_vm_json +when false: + import ./test_code_stream, + ./test_gas_meter, + ./test_memory, + ./test_stack, + ./test_opcode, + ./test_storage_backends + +when true: + import ./test_vm_json diff --git a/tests/test_gas_meter.nim b/tests/test_gas_meter.nim index 542b2bb47..fbd870eb1 100644 --- a/tests/test_gas_meter.nim +++ b/tests/test_gas_meter.nim @@ -5,9 +5,10 @@ # * 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 unittest, macros, strformat, - stint, - ../nimbus/[vm_types, errors, logging, vm/interpreter] +import + unittest, macros, strformat, + eth_common/eth_types, + ../nimbus/[vm_types, errors, logging, vm/interpreter] # TODO: quicktest # PS: parametrize can be easily immitated, but still quicktests would be even more useful diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index 472e35805..b21818c29 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -7,7 +7,7 @@ import os, macros, json, strformat, strutils, parseutils, ospaths, tables, - stint, byteutils, eth_common, eth_keys, + byteutils, eth_common, eth_keys, ranges/typedranges, ../nimbus/utils/[address, padding], ../nimbus/[vm_state, constants], ../nimbus/db/[db_chain, state_db], @@ -19,10 +19,15 @@ type proc validTest*(folder: string, name: string): bool = # tests we want to skip or which segfault will be skipped here # TODO fix + #if true: + # return "or0" in name + #if true: + # return folder == "vmEnvironmentalInfo" + result = "calldatacopy" notin name and "balanceAddressInputTooBigRightMyAddress." notin name and "callstatelessToReturn1" notin name and - folder notin @["vmRandomTest", "vmSystemOperations", "vmPerformance", "vmEnvironmentalInfo"] + folder notin @["vmRandomTest", "vmSystemOperations", "vmPerformance"] #result = name == "exp2.json" macro jsonTest*(s: static[string], handler: untyped): untyped = @@ -76,22 +81,48 @@ macro jsonTest*(s: static[string], handler: untyped): untyped = raw.add("OK: " & $okCount & "/" & $sum & " Fail: " & $failCount & "/" & $sum & " Skip: " & $skipCount & "/" & $sum & "\n") writeFile(`s` & ".md", raw) -proc accountFromHex(s: string): EthAddress = hexToByteArray(s, result) +proc ethAddressFromHex(s: string): EthAddress = hexToByteArray(s, result) -proc setupStateDB*(desiredState: JsonNode, stateDB: var AccountStateDB) = - for ac, accountData in desiredState: - let account = accountFromHex(ac) +proc setupStateDB*(wantedState: JsonNode, stateDB: var AccountStateDB) = + for ac, accountData in wantedState: + let account = ethAddressFromHex(ac) for slot, value in accountData{"storage"}: - stateDB.setStorage(account, slot.parseInt.u256, value.getInt.u256) + stateDB.setStorage(account, slot.parseHexInt.u256, value.getInt.u256) let nonce = accountData{"nonce"}.getInt.u256 - let code = accountData{"code"}.getStr + let code = hexToSeqByte(accountData{"code"}.getStr).toRange let balance = accountData{"balance"}.getInt.u256 stateDB.setNonce(account, nonce) stateDB.setCode(account, code) stateDB.setBalance(account, balance) +proc verifyStateDB*(wantedState: JsonNode, stateDB: AccountStateDB) = + for ac, accountData in wantedState: + let account = ethAddressFromHex(ac) + for slot, value in accountData{"storage"}: + let + slotId = slot.parseHexInt.u256 + wantedValue = UInt256.fromHex value.getStr + + let (actualValue, found) = stateDB.getStorage(account, slotId) + # echo "FOUND ", found + # echo "ACTUAL VALUE ", actualValue.toHex + doAssert found and actualValue == wantedValue + + let + wantedCode = hexToSeqByte(accountData{"code"}.getStr).toRange + wantedBalance = accountData{"balance"}.getInt.u256 + wantedNonce = accountData{"nonce"}.getInt.u256 + + actualCode = stateDB.getCode(account) + actualBalance = stateDB.getBalance(account) + actualNonce = stateDB.getNonce(account) + + doAssert wantedCode == actualCode + doAssert wantedBalance == actualBalance + doAssert wantedNonce == actualNonce + proc getHexadecimalInt*(j: JsonNode): int = discard parseHex(j.getStr, result) diff --git a/tests/test_memory.nim b/tests/test_memory.nim index c8e4efce2..374be9c01 100644 --- a/tests/test_memory.nim +++ b/tests/test_memory.nim @@ -5,9 +5,10 @@ # * 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 unittest, sequtils, - stint, - ../nimbus/[constants, errors, vm/memory] +import + unittest, sequtils, + eth_common/eth_types, + ../nimbus/[constants, errors, vm/memory] proc memory32: Memory = result = newMemory() diff --git a/tests/test_opcode.nim b/tests/test_opcode.nim index ab2c9eb75..a54a101de 100644 --- a/tests/test_opcode.nim +++ b/tests/test_opcode.nim @@ -6,7 +6,8 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - unittest, stint, tables, parseutils, + unittest, tables, parseutils, + eth_trie/[types, memdb], eth_common/eth_types, ../nimbus/[constants, vm_types, logging], ../nimbus/vm/interpreter, ../nimbus/utils/header, @@ -17,7 +18,8 @@ from eth_common import GasInt proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputation = let header = BlockHeader(blockNumber: blockNum) - var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB())) + var memDb = newMemDB() + var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb)) # coinbase: "", # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, # blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, diff --git a/tests/test_stack.nim b/tests/test_stack.nim index 587ae65d7..78cc80d8c 100644 --- a/tests/test_stack.nim +++ b/tests/test_stack.nim @@ -5,8 +5,10 @@ # * 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 unittest, stint, - ../nimbus/[constants, errors, vm/interpreter, utils/bytes] +import + unittest, + eth_common/eth_types, + ../nimbus/[constants, errors, vm/interpreter, utils/bytes] template testPush(value: untyped, expected: untyped): untyped = diff --git a/tests/test_storage_backends.nim b/tests/test_storage_backends.nim index 3869295eb..e8de17b66 100644 --- a/tests/test_storage_backends.nim +++ b/tests/test_storage_backends.nim @@ -1,6 +1,6 @@ import unittest, macros, - stint, nimcrypto/[keccak, hash], ranges, eth_common/eth_types, + nimcrypto/[keccak, hash], ranges, eth_common/eth_types, ../nimbus/db/[storage_types], ../nimbus/db/backends/[sqlite_backend, rocksdb_backend] diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index 9f669eeaf..e0676f61b 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -6,51 +6,64 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - unittest, strformat, strutils, sequtils, tables, stint, json, ospaths, times, + unittest, strformat, strutils, sequtils, tables, json, ospaths, times, + byteutils, ranges/typedranges, nimcrypto/[keccak, hash], + rlp, eth_trie/[types, memdb], eth_common, ./test_helpers, ../nimbus/[constants, errors, logging], ../nimbus/[vm_state, vm_types], ../nimbus/utils/[header, padding], ../nimbus/vm/interpreter, - ../nimbus/db/[db_chain, state_db, backends/memory_backend], - eth_common + ../nimbus/db/[db_chain, state_db, backends/memory_backend] proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) suite "vm json tests": jsonTest("VMTests", testFixture) + +proc stringFromBytes(x: ByteRange): string = + result = newString(x.len) + for i in 0 ..< x.len: + result[i] = char(x[i]) + proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = var fixture: JsonNode for label, child in fixtures: fixture = child break let fenv = fixture["env"] + var emptyRlpHash = keccak256.digest(rlp.encode("").toOpenArray) let header = BlockHeader( coinbase: fenv{"currentCoinbase"}.getStr.parseAddress, difficulty: fenv{"currentDifficulty"}.getHexadecimalInt.u256, blockNumber: fenv{"currentNumber"}.getHexadecimalInt.u256, gasLimit: fenv{"currentGasLimit"}.getHexadecimalInt.GasInt, - timestamp: fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix + timestamp: fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix, + stateRoot: emptyRlpHash ) - var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB())) + var memDb = newMemDB() + var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb)) let fexec = fixture["exec"] var code = "" - vm.state.db(readOnly=false): + vm.state.mutateStateDB: setupStateDB(fixture{"pre"}, db) - code = db.getCode(fexec{"address"}.getStr.parseAddress) + let address = fexec{"address"}.getStr.parseAddress + code = stringFromBytes db.getCode(address) code = fexec{"code"}.getStr + let toAddress = fexec{"address"}.getStr.parseAddress let message = newMessage( - to = fexec{"address"}.getStr.parseAddress, + to = toAddress, sender = fexec{"caller"}.getStr.parseAddress, value = cast[uint](fexec{"value"}.getHexadecimalInt).u256, # Cast workaround for negative value - data = fexec{"data"}.getStr.mapIt(it.byte), + data = fexec{"data"}.getStr.hexToSeqByte, code = code, gas = fexec{"gas"}.getHexadecimalInt, gasPrice = fexec{"gasPrice"}.getHexadecimalInt, - options = newMessageOptions(origin=fexec{"origin"}.getStr.parseAddress)) + options = newMessageOptions(origin=fexec{"origin"}.getStr.parseAddress, + createAddress = toAddress)) #echo fixture{"exec"} var c = newCodeStreamFromUnescaped(code) @@ -101,7 +114,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for child in zip(computation.children, callCreates): var (childComputation, createdCall) = child let toAddress = createdCall{"destination"}.getStr.parseAddress - let data = createdCall{"data"}.getStr.mapIt(it.byte) + let data = createdCall{"data"}.getStr.hexToSeqByte let gasLimit = createdCall{"gasLimit"}.getHexadecimalInt let value = createdCall{"value"}.getHexadecimalInt.u256 @@ -110,10 +123,11 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = check(gasLimit == childComputation.msg.gas) check(value == childComputation.msg.value) # TODO postState = fixture{"post"} + + if not fixture{"post"}.isNil: + verifyStateDb(fixture{"post"}, computation.vmState.readOnlyStateDB) else: # Error checks check(computation.isError) # TODO postState = fixture{"pre"} - - # TODO with vm.state.stateDb(readOnly=True) as stateDb: - # verifyStateDb(postState, stateDb) +