Few steps forward vm_tests (#15)

* Enable vm_tests, remove usage of from keyword as a variable name

* Fix Genesis types and use the new EthTime

* Add a new VMKind enum (py-evm vmclass runtime type)

* renaming block_obj to block_types

* chain.BlockHeader and utils.header.Header are the same thing (BlockHeader in py-evm)

* For now blockNumbers are UInt256

* EoD merge: vm_test compiles and does not throw computation error

* running full transactions is too early
This commit is contained in:
Mamy Ratsimbazafy 2018-04-14 12:40:41 +02:00 committed by GitHub
parent 8cf8811310
commit 6654576c6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 153 additions and 82 deletions

View File

@ -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

View File

@ -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")

View File

@ -33,7 +33,7 @@ var c = BaseComputation(
vmState: BaseVMState(
prevHeaders: @[],
chaindb: BaseChainDB(),
blockHeader: Header(),
blockHeader: BlockHeader(),
name: "zero"),
msg: msg,
memory: mem,

View File

@ -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),

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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,

View File

@ -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, @[])

View File

@ -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)

View File

@ -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")

View File

@ -10,5 +10,6 @@ import ./test_code_stream,
./test_memory,
./test_stack,
./test_opcode
# ./test_vm

View File

@ -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(

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,