diff --git a/src/block_obj.nim b/src/block_types.nim similarity index 81% rename from src/block_obj.nim rename to src/block_types.nim index 3c0d169d6..71ed60eea 100644 --- a/src/block_obj.nim +++ b/src/block_types.nim @@ -6,13 +6,15 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - logging, constants, utils / header, ttmath + ttmath, + ./logging, ./constants, + ./utils/header type CountableList*[T] = ref object elements: seq[T] # TODO Block* = ref object of RootObj - header*: Header - uncles*: CountableList[Header] + header*: BlockHeader + uncles*: CountableList[BlockHeader] blockNumber*: UInt256 diff --git a/src/chain.nim b/src/chain.nim index a7d588572..ea85cc178 100644 --- a/src/chain.nim +++ b/src/chain.nim @@ -7,12 +7,10 @@ import tables, ttmath, - logging, constants, errors, validation, utils / hexadecimal, vm / base, db / db_chain + ./logging, ./constants, ./errors, ./validation, ./utils/hexadecimal, ./vm/base, ./db/db_chain, + ./utils/header, ./vm/forks/frontier/vm type - BlockHeader* = ref object - # Placeholder TODO - Chain* = ref object ## An Chain is a combination of one or more VM classes. Each VM is associated ## with a range of blocks. The Chain class acts as a wrapper around these other @@ -21,7 +19,7 @@ type header*: BlockHeader logger*: Logger networkId*: string - vmsByRange*: seq[tuple[blockNumber: Int256, vm: VM]] # TODO + vmsByRange*: seq[tuple[blockNumber: UInt256, vmk: VMkind]] # TODO: VM should actually be a runtime typedesc(VM) importBlock*: bool validateBlock*: bool db*: BaseChainDB @@ -30,9 +28,9 @@ type fundedAddressPrivateKey*: string GenesisParams* = ref object - blockNumber*: Int256 - difficulty*: Int256 - gasLimit*: Int256 + blockNumber*: UInt256 + difficulty*: UInt256 + gasLimit*: UInt256 parentHash*: string coinbase*: string nonce*: string @@ -47,9 +45,9 @@ type code*: string -proc configureChain*(name: string, blockNumber: Int256, vm: VM, importBlock: bool = true, validateBlock: bool = true): Chain = +proc configureChain*(name: string, blockNumber: UInt256, vmk: VMKind, importBlock: bool = true, validateBlock: bool = true): Chain = new(result) - result.vmsByRange = @[(blockNumber: blockNumber, vm: vm)] + result.vmsByRange = @[(blockNumber: blockNumber, vmk: vmk)] result.importBlock = importBlock result.validateBlock = validateBlock @@ -75,3 +73,29 @@ proc fromGenesis*( result.vmsByRange = chain.vmsByRange # TODO # chainDB.persistBlockToDB(result.getBlock) + +proc getVMClassForBlockNumber*(chain: Chain, blockNumber: UInt256): VMKind = + ## Returns the VM class for the given block number + # TODO should the return value be a typedesc? + + # TODO: validate_block_number + for idx in countdown(chain.vmsByRange.high, chain.vmsByRange.low): + let (n, vmk) = chain.vmsByRange[idx] + if blockNumber >= n: + return vmk + + raise newException(ValueError, "VM not found for block #" & $blockNumber) # TODO: VMNotFound exception + +proc getVM*(chain: Chain, header: BlockHeader = nil): VM = + ## Returns the VM instance for the given block number + + # shadowing input param + let header = if header.isNil: chain.header + else: header + + let vm_class = chain.getVMClassForBlockNumber(header.blockNumber) + + case vm_class: + of vmkFrontier: result = newFrontierVM(header, chain.db) + else: + raise newException(ValueError, "Chain: only FrontierVM is implemented") diff --git a/src/runner.nim b/src/runner.nim index c1fde5f53..1c52f5edf 100644 --- a/src/runner.nim +++ b/src/runner.nim @@ -33,7 +33,7 @@ var c = BaseComputation( vmState: BaseVMState( prevHeaders: @[], chaindb: BaseChainDB(), - blockHeader: Header(), + blockHeader: BlockHeader(), name: "zero"), msg: msg, memory: mem, diff --git a/src/utils/header.nim b/src/utils/header.nim index ae5d4ebdb..e2b0eb425 100644 --- a/src/utils/header.nim +++ b/src/utils/header.nim @@ -9,7 +9,8 @@ import ../constants, ttmath, strformat, times, ../validation type - Header* = ref object + BlockHeader* = ref object + # Note: this is defined in evm/rlp/headers in the original repo timestamp*: EthTime difficulty*: UInt256 blockNumber*: UInt256 @@ -17,27 +18,26 @@ type unclesHash*: string coinbase*: string stateRoot*: string + # TODO: incomplete - # TODO +proc hasUncles*(header: BlockHeader): bool = header.uncles_hash != EMPTY_UNCLE_HASH -proc hasUncles*(header: Header): bool = header.uncles_hash != EMPTY_UNCLE_HASH - -proc gasUsed*(header: Header): UInt256 = +proc gasUsed*(header: BlockHeader): UInt256 = # TODO # Should this be calculated/a proc? Parity and Py-Evm just have it as a field. 0.u256 -proc gasLimit*(header: Header): UInt256 = +proc gasLimit*(header: BlockHeader): UInt256 = # TODO 0.u256 -proc `$`*(header: Header): string = +proc `$`*(header: BlockHeader): string = if header.isNil: result = "nil" else: - result = &"Header(timestamp: {header.timestamp} difficulty: {header.difficulty} blockNumber: {header.blockNumber} gasLimit: {header.gasLimit})" + result = &"BlockHeader(timestamp: {header.timestamp} difficulty: {header.difficulty} blockNumber: {header.blockNumber} gasLimit: {header.gasLimit})" -proc gasLimitBounds*(parent: Header): (UInt256, UInt256) = +proc gasLimitBounds*(parent: BlockHeader): (UInt256, UInt256) = ## Compute the boundaries for the block gas limit based on the parent block. let boundary_range = parent.gasLimit div GAS_LIMIT_ADJUSTMENT_FACTOR @@ -46,7 +46,7 @@ proc gasLimitBounds*(parent: Header): (UInt256, UInt256) = return (lower_bound, upper_bound) #[ -proc validate_gaslimit(header: Header): +proc validate_gaslimit(header: BlockHeader): let parent_header = getBlockHeaderByHash(header.parent_hash) low_bound, high_bound = compute_gas_limit_bounds(parent_header) if header.gas_limit < low_bound: @@ -59,7 +59,7 @@ proc validate_gaslimit(header: Header): encode_hex(header.hash), header.gas_limit, high_bound)) ]# -proc computeGasLimit*(parent: Header, gasLimitFloor: UInt256): UInt256 = +proc computeGasLimit*(parent: BlockHeader, gasLimitFloor: UInt256): UInt256 = #[ For each block: - decrease by 1/1024th of the gas limit from the previous block @@ -98,13 +98,13 @@ proc computeGasLimit*(parent: Header, gasLimitFloor: UInt256): UInt256 = return gas_limit proc generateHeaderFromParentHeader*( - computeDifficultyFn: proc(parentHeader: Header, timestamp: int): int, - parent: Header, + computeDifficultyFn: proc(parentHeader: BlockHeader, timestamp: int): int, + parent: BlockHeader, coinbase: string, timestamp: int = -1, - extraData: string = ""): Header = + extraData: string = ""): BlockHeader = # TODO: validateGt(timestamp, parent.timestamp) - result = Header( + result = BlockHeader( timestamp: max(getTime(), parent.timestamp + 1.milliseconds), # Note: Py-evm uses +1 second, not ms block_number: (parent.block_number + u256(1)), # TODO: difficulty: parent.computeDifficulty(parent.timestamp), diff --git a/src/vm/base.nim b/src/vm/base.nim index 275878e98..8a5fb11a1 100644 --- a/src/vm/base.nim +++ b/src/vm/base.nim @@ -6,9 +6,14 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../logging, ../constants, ../errors, ../transaction, ../types, ../computation, ../block_obj, ../vm_state, ../vm_state_transactions, ../db/db_chain, ../utils/header + ../logging, ../constants, ../errors, ../transaction, ../types, ../computation, ../block_types, ../vm_state, ../vm_state_transactions, ../db/db_chain, ../utils/header type + VMkind* = enum + ## List of VMs forks (py-evm vm_class) of the Ethereum network + # TODO: used in Chain.vmsByRange: can we store the runtimetime in a seq/tuple instead? + vmkFrontier, vmkHomestead, vmkTangerineWhistle, vmkSpuriousDragon, vmkByzantium + VM* = ref object of RootObj # The VM class represents the Chain rules for a specific protocol definition # such as the Frontier or Homestead network. Defining an Chain defining @@ -20,7 +25,7 @@ type state*: BaseVMState `block`*: Block -proc newVM*(header: Header, chainDB: BaseChainDB): VM = +proc newVM*(header: BlockHeader, chainDB: BaseChainDB): VM = new(result) result.chainDB = chainDB diff --git a/src/vm/forks/frontier/frontier_block.nim b/src/vm/forks/frontier/frontier_block.nim index 468eb2708..fb4084196 100644 --- a/src/vm/forks/frontier/frontier_block.nim +++ b/src/vm/forks/frontier/frontier_block.nim @@ -7,11 +7,11 @@ import ../../../logging, ../../../constants, ../../../errors, ../../../transaction, - ../../../block_obj, + ../../../block_types, ../../../utils/header type - FrontierBlock* = object of Block + FrontierBlock* = ref object of Block # bloomFilter*: BloomFilter # header*: BlockHeader transactions*: seq[BaseTransaction] @@ -25,13 +25,13 @@ type # rlp, rlp.sedes, eth_bloom, evm.constants, evm.rlp.receipts, evm.rlp.blocks, # evm.rlp.headers, evm.utils.keccak, transactions -# method makeFrontierBlock*(header: auto; transactions: auto; uncles: void): auto = -# if transactions is None: -# transactions = @[] -# if uncles is None: -# uncles = @[] -# result.bloomFilter = BloomFilter(header.bloom) -# super(FrontierBlock, result).__init__() +proc makeFrontierBlock*(header: BlockHeader; transactions: seq[BaseTransaction]; uncles: void): FrontierBlock = + new result + if transactions.len == 0: + result.transactions = @[] + # if uncles is None: + # uncles = @[] + # result.bloomFilter = BloomFilter(header.bloom) # method number*(self: FrontierBlock): int = # return self.header.blockNumber diff --git a/src/vm/forks/frontier/frontier_headers.nim b/src/vm/forks/frontier/frontier_headers.nim index 56cdb85c9..0b4b5d061 100644 --- a/src/vm/forks/frontier/frontier_headers.nim +++ b/src/vm/forks/frontier/frontier_headers.nim @@ -8,21 +8,21 @@ import logging, constants, errors, validation, utils/header, vm / forks / frontier / vm -method computeDifficulty*(parentHeader: Header, timestamp: int): Int256 = - validateGt(timestamp, parentHeader.timestamp, title="Header timestamp") +method computeDifficulty*(parentHeader: BlockHeader, timestamp: int): Int256 = + validateGt(timestamp, parentHeader.timestamp, title="BlockHeader timestamp") let offset = parentHeader.difficulty div DIFFICULTY_ADJUSTMENT_DENOMINATOR # We set the minimum to the lowest of the protocol minimum and the parent # minimum to allow for the initial frontier *warming* period during which # the difficulty begins lower than the protocol minimum let difficultyMinimum = min(parentHeader.difficulty, DIFFICULTY_MINIMUM) # let test = (timestamp - parentHeader.timestamp).Int256 < FRONTIER_DIFFICULTY_ADJUSTMENT_CUTOFF - # let baseDifficulty = max(parent.Header.difficulty + (if test: offset else: -offset), difficultyMinimum) + # let baseDifficulty = max(parent.BlockHeader.difficulty + (if test: offset else: -offset), difficultyMinimum) # # Adjust for difficulty bomb # let numBombPeriods = ((parentHeader.blockNumber + 1) div BOMB_EXPONENTIAL_PERIOD) - BOMB_EXPONENTIAL_FREE_PERIODS # result = if numBombPeriods >= 0: max(baseDifficulty + 2.Int256 ^ numBombPeriods, DIFFICULTY_MINIMUM) else: baseDifficulty result = 0.Int256 -method createHeaderFromParent*(parentHeader: Header): Header = +method createHeaderFromParent*(parentHeader: BlockHeader): BlockHeader = # TODO - result = Header() + result = BlockHeader() diff --git a/src/vm/forks/frontier/frontier_vm_state.nim b/src/vm/forks/frontier/frontier_vm_state.nim index d0cfe96f2..74437b026 100644 --- a/src/vm/forks/frontier/frontier_vm_state.nim +++ b/src/vm/forks/frontier/frontier_vm_state.nim @@ -20,7 +20,7 @@ proc newFrontierVMState*: FrontierVMState = result.prevHeaders = @[] result.name = "FrontierVM" result.accessLogs = newAccessLogs() - result.blockHeader = Header(hash: "TODO", coinbase: "TODO", stateRoot: "TODO") + result.blockHeader = BlockHeader(hash: "TODO", coinbase: "TODO", stateRoot: "TODO") # import # py2nim_helpers, __future__, rlp, evm, evm.constants, evm.exceptions, evm.rlp.logs, diff --git a/src/vm/forks/frontier/vm.nim b/src/vm/forks/frontier/vm.nim index 38a79b281..1f0c43a09 100644 --- a/src/vm/forks/frontier/vm.nim +++ b/src/vm/forks/frontier/vm.nim @@ -8,7 +8,7 @@ import ../../../logging, ../../../constants, ../../../errors, ttmath, - ../../../block_obj, + ../../../block_types, ../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header, ./frontier_block, ./frontier_vm_state, ./frontier_validation @@ -29,8 +29,9 @@ method getUncleReward(vm: FrontierVM, blockNumber: UInt256, uncle: Block): UInt2 method getNephewReward(vm: FrontierVM): UInt256 = vm.getBlockReward() div 32 -proc newFrontierVM*(header: Header, chainDB: BaseChainDB): FrontierVM = +proc newFrontierVM*(header: BlockHeader, chainDB: BaseChainDB): FrontierVM = new(result) result.chainDB = chainDB result.isStateless = true result.state = newFrontierVMState() + result.`block` = makeFrontierBlock(header, @[]) diff --git a/src/vm_state.nim b/src/vm_state.nim index 53c584a16..f032101ce 100644 --- a/src/vm_state.nim +++ b/src/vm_state.nim @@ -7,15 +7,16 @@ import macros, strformat, tables, - logging, constants, ttmath, errors, transaction, db/db_chain, utils/state, utils/header + ttmath, + ./logging, ./constants, ./errors, ./transaction, ./db/[db_chain, state_db], ./utils/state, ./utils/header type BaseVMState* = ref object of RootObj - prevHeaders*: seq[Header] + prevHeaders*: seq[BlockHeader] # receipts*: chaindb*: BaseChainDB accessLogs*: AccessLogs - blockHeader*: Header + blockHeader*: BlockHeader name*: string AccessLogs* = ref object @@ -40,7 +41,7 @@ proc newBaseVMState*: BaseVMState = result.prevHeaders = @[] result.name = "BaseVM" result.accessLogs = newAccessLogs() - result.blockHeader = Header(hash: "TODO", coinbase: "TODO", stateRoot: "TODO") + result.blockHeader = BlockHeader(hash: "TODO", coinbase: "TODO", stateRoot: "TODO") method logger*(vmState: BaseVMState): Logger = logging.getLogger(&"evm.vmState.{vmState.name}") @@ -97,3 +98,6 @@ macro db*(vmState: untyped, readOnly: untyped, handler: untyped): untyped = # leaving the context. # TODO `db`.db = nil # state._trie = None + +proc readOnlyStateDB*(vmState: BaseVMState): AccountStateDB {.inline.}= + vmState.chaindb.getStateDb("", readOnly = true) diff --git a/src/vm_state_transactions.nim b/src/vm_state_transactions.nim index bb4889214..660cf0367 100644 --- a/src/vm_state_transactions.nim +++ b/src/vm_state_transactions.nim @@ -7,10 +7,13 @@ import strformat, tables, - logging, constants, errors, computation, transaction, types, vm_state, block_obj, db / db_chain, utils / [state, header] + logging, constants, errors, computation, transaction, types, vm_state, block_types, db / db_chain, utils / [state, header] -method executeTransaction(vmState: var BaseVMState, transaction: BaseTransaction): (BaseComputation, Header) = +method executeTransaction(vmState: var BaseVMState, transaction: BaseTransaction): (BaseComputation, BlockHeader) {.base.}= # Execute the transaction in the vm + # TODO: introduced here: https://github.com/ethereum/py-evm/commit/21c57f2d56ab91bb62723c3f9ebe291d0b132dde + # Refactored/Removed here: https://github.com/ethereum/py-evm/commit/cc991bf + # Deleted here: https://github.com/ethereum/py-evm/commit/746defb6f8e83cee2c352a0ab8690e1281c4227c raise newException(ValueError, "Must be implemented by subclasses") diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 236ce65a4..75fd830f1 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -10,5 +10,6 @@ import ./test_code_stream, ./test_memory, ./test_stack, ./test_opcode + # ./test_vm diff --git a/tests/fixtures.nim b/tests/fixtures.nim index a354abe17..165607278 100644 --- a/tests/fixtures.nim +++ b/tests/fixtures.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 unittest, strformat, tables, constants, chain, ttmath, vm / forks / frontier / vm, utils / [header, address], db / db_chain, db / backends / memory_backend +import + unittest, strformat, tables, times, + ttmath, + ../src/[constants, chain, vm/base, vm/forks/frontier/vm, utils/header, utils/address, db/db_chain, db/backends/memory_backend] -proc chainWithoutBlockValidation: Chain = - result = configureChain("TestChain", GENESIS_BLOCK_NUMBER, newFrontierVM(Header(), newBaseChainDB(newMemoryDB())), false, false) +proc chainWithoutBlockValidation*: Chain = + result = configureChain("TestChain", GENESIS_BLOCK_NUMBER, vmkFrontier, false, false) let privateKey = "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" # TODO privateKey(decodeHex("0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")) let fundedAddr = privateKey # privateKey.publicKey.toCanonicalAddress let initialBalance = 100_000_000 @@ -21,7 +24,7 @@ proc chainWithoutBlockValidation: Chain = nonce: GENESIS_NONCE, mixHash: GENESIS_MIX_HASH, extraData: GENESIS_EXTRA_DATA, - timestamp: 1501851927, + timestamp: fromUnix 1501851927, stateRoot: "0x9d354f9b5ba851a35eced279ef377111387197581429cfcc7f744ef89a30b5d4") #.decodeHex) let genesisState = {"fundedAddr": FundedAddress(balance: initialBalance.int256, nonce: 0, code: "")}.toTable() result = fromGenesis( diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index 3710b352a..10fbd9c41 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -10,7 +10,8 @@ import ttmath, ../src/utils/[hexadecimal, address, padding], ../src/[chain, vm_state, constants], - ../src/db/[db_chain, state_db], ../src/vm/forks/frontier/vm + ../src/db/[db_chain, state_db], ../src/vm/forks/frontier/vm, + ../src/vm/base, ../src/transaction type Status* {.pure.} = enum OK, Fail, Skip @@ -91,3 +92,21 @@ proc setupStateDB*(desiredState: JsonNode, stateDB: var AccountStateDB) = proc getHexadecimalInt*(j: JsonNode): int = discard parseHex(j.getStr, result) + +method newTransaction*( + vm: VM, addr_from, addr_to: string, + amount: UInt256, + private_key: string, + gas_price = 10.u256, + gas = 100000.u256, + data: seq[byte] = @[] +): BaseTransaction = + # TODO: amount should be an Int to deal with negatives + new result + + # Todo getStateDB is incomplete + let nonce = vm.state.readOnlyStateDB.getNonce(addr_from) + + # TODO + # if !private key: create_unsigned_transaction + # else: create_signed_transaction diff --git a/tests/test_opcode.nim b/tests/test_opcode.nim index 3dff6afa7..e90f55425 100644 --- a/tests/test_opcode.nim +++ b/tests/test_opcode.nim @@ -17,8 +17,8 @@ import proc testCode(code: string, gas: UInt256): BaseComputation = - var vm = newFrontierVM(Header(), newBaseChainDB(newMemoryDB())) - let header = Header() + var vm = newFrontierVM(BlockHeader(), newBaseChainDB(newMemoryDB())) + let header = BlockHeader() # coinbase: "", # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, # blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, diff --git a/tests/test_vm.nim b/tests/test_vm.nim index b33bc323e..0538263f3 100644 --- a/tests/test_vm.nim +++ b/tests/test_vm.nim @@ -6,30 +6,39 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - unittest, - test_helpers, .. / src / [db / backends / memory, db / chain, constants, utils / hexadecimal] + unittest, ttmath, + ./test_helpers, ./fixtures, + ../src/[db/backends/memory_backend, db/state_db, chain, constants, utils/hexadecimal, vm_state], + ../src/[vm/base, computation] -suite "vm": - test "apply no validation": +import typetraits + +suite "VM": + test "Apply transaction with no validation": var - chain = testChain() + chain = chainWithoutBlockValidation() vm = chain.getVM() - txIdx = len(vm.`block`.transactions) + # txIdx = len(vm.`block`.transactions) # Can't take len of a runtime field + let recipient = decodeHex("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c") - amount = 100.Int256 + amount = 100.u256 + ethaddr_from = chain.fundedAddress + tx = newTransaction(vm, ethaddr_from, recipient, amount, chain.fundedAddressPrivateKey) + # (computation, _) = vm.applyTransaction(tx) + # accessLogs = computation.vmState.accessLogs - var from = chain.fundedAddress - var tx = newTransaction(vm, from, recipient, amount, chain.fundedAddressPrivateKey) - var (computation, _) = vm.applyTransaction(tx) - var accessLogs = computation.vmState.accessLogs + # check(not computation.isError) - check(not computation.isError) + let + txGas = tx.gasPrice * constants.GAS_TX + state_db = vm.state.readOnlyStateDB + b = vm.`block` - var txGas = tx.gasPrice * constants.GAS_TX - inDb(vm.state.stateDb(readOnly=true)): - check(db.getBalance(from) == chain.fundedAddressInitialBalance - amount - txGas) - check(db.getBalance(recipient) == amount) - var b = vm.`block` - check(b.transactions[txIdx] == tx) - check(b.header.gasUsed == constants.GAS_TX) + echo state_db.getBalance(ethaddr_from).type.name + + # check: + # state_db.getBalance(ethaddr_from) == chain.fundedAddressInitialBalance - amount - txGas # TODO: this really should be i256 + # state_db.getBalance(recipient) == amount + # b.transactions[txIdx] == tx + # b.header.gasUsed == constants.GAS_TX diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index b07fab085..bb9e9f268 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -21,8 +21,8 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for label, child in fixtures: fixture = child break - var vm = newFrontierVM(Header(), newBaseChainDB(newMemoryDB())) - let header = Header( + var vm = newFrontierVM(BlockHeader(), newBaseChainDB(newMemoryDB())) + let header = BlockHeader( coinbase: fixture{"env"}{"currentCoinbase"}.getStr, difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256,