general-state testsuite changes and better fork selection

- skipped the tests in allowedFailInCurrentBuild()
- replaced doAssert() with check() in testFixtureIndexes() so we can see
  both hash values on failure
- checking filename extension for JSON tests to avoid editor swap files
- replaced the duplicated block values in the main net's ChainConfig
  with values from forkBlocks
- allowed overriding the current fork in computations, because the old
  strategy of only looking at the block number doesn't work with JSON tests
  where the block number is usually 1
- explicitly pass the fork to gasCosts() and use it for conditional cost
  calculation
- fixed a logic error in the CREATE opcode
- fixed VM selection based on current fork in updateOpcodeExec()
- single point of control for supported forks in tests (just one fork, at the
  moment)
- 44 new test failures (that were probably passing for the wrong reasons)
This commit is contained in:
Ștefan Talpalaru 2018-11-28 20:02:21 +01:00
parent 8f0a78e52f
commit 115843487c
No known key found for this signature in database
GPG Key ID: CBF7934204F1B6F9
12 changed files with 1281 additions and 1173 deletions

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,8 @@
import import
parseopt, strutils, macros, os, parseopt, strutils, macros, os,
asyncdispatch2, eth_keys, eth_p2p, eth_common, chronicles, nimcrypto/hash asyncdispatch2, eth_keys, eth_p2p, eth_common, chronicles, nimcrypto/hash,
./vm/interpreter/vm_forks
const const
NimbusName* = "Nimbus" NimbusName* = "Nimbus"
@ -175,14 +176,14 @@ proc publicChainConfig*(id: PublicNetwork): ChainConfig =
of MainNet: of MainNet:
ChainConfig( ChainConfig(
chainId: MainNet.uint, chainId: MainNet.uint,
homesteadBlock: 1150000.u256, homesteadBlock: forkBlocks[FkHomestead],
daoForkBlock: 1920000.u256, daoForkBlock: forkBlocks[FkDao],
daoForkSupport: true, daoForkSupport: true,
eip150Block: 2463000.u256, eip150Block: forkBlocks[FkTangerine],
eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), eip150Hash: toDigest("2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
eip155Block: 2675000.u256, eip155Block: forkBlocks[FkSpurious],
eip158Block: 2675000.u256, eip158Block: forkBlocks[FkSpurious],
byzantiumBlock: 4370000.u256 byzantiumBlock: forkBlocks[FkByzantium]
) )
of RopstenNet: of RopstenNet:
ChainConfig( ChainConfig(

View File

@ -6,7 +6,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
chronicles, strformat, strutils, sequtils, macros, terminal, math, tables, chronicles, strformat, strutils, sequtils, macros, terminal, math, tables, options,
eth_common, eth_common,
../constants, ../errors, ../validation, ../vm_state, ../vm_types, ../constants, ../errors, ../validation, ../vm_state, ../vm_types,
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks], ./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
@ -17,7 +17,7 @@ import
logScope: logScope:
topics = "vm computation" topics = "vm computation"
proc newBaseComputation*(vmState: BaseVMState, blockNumber: UInt256, message: Message): BaseComputation = proc newBaseComputation*(vmState: BaseVMState, blockNumber: UInt256, message: Message, forkOverride=none(Fork)): BaseComputation =
new result new result
result.vmState = vmState result.vmState = vmState
result.msg = message result.msg = message
@ -29,7 +29,12 @@ proc newBaseComputation*(vmState: BaseVMState, blockNumber: UInt256, message: Me
result.logEntries = @[] result.logEntries = @[]
result.code = newCodeStream(message.code) result.code = newCodeStream(message.code)
# result.rawOutput = "0x" # result.rawOutput = "0x"
result.gasCosts = blockNumber.toFork.forkToSchedule result.gasCosts =
if forkOverride.isSome:
forkOverride.get.forkToSchedule
else:
blockNumber.toFork.forkToSchedule
result.forkOverride = forkOverride
proc isOriginComputation*(c: BaseComputation): bool = proc isOriginComputation*(c: BaseComputation): bool =
# Is this computation the computation initiated by a transaction # Is this computation the computation initiated by a transaction
@ -209,7 +214,8 @@ proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMs
var childComp = newBaseComputation( var childComp = newBaseComputation(
computation.vmState, computation.vmState,
computation.vmState.blockHeader.blockNumber, computation.vmState.blockHeader.blockNumber,
childMsg) childMsg,
some(fork))
# Copy the fork op code executor proc (assumes child computation is in the same fork) # Copy the fork op code executor proc (assumes child computation is in the same fork)
childComp.opCodeExec = computation.opCodeExec childComp.opCodeExec = computation.opCodeExec
@ -235,9 +241,16 @@ proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseCo
computation.returnData = child.output computation.returnData = child.output
computation.children.add(child) computation.children.add(child)
proc getFork*(computation: BaseComputation): Fork =
result =
if computation.forkOverride.isSome:
computation.forkOverride.get
else:
computation.vmState.blockHeader.blockNumber.toFork
proc applyChildComputation*(computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation = proc applyChildComputation*(computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
## Apply the vm message childMsg as a child computation. ## Apply the vm message childMsg as a child computation.
let fork = computation.vmState.blockHeader.blockNumber.toFork let fork = computation.getFork
result = fork.generateChildComputation(computation, childMsg, opCode) result = fork.generateChildComputation(computation, childMsg, opCode)
fork.addChildComputation(computation, result) fork.addChildComputation(computation, result)

View File

@ -13,7 +13,7 @@ import
# Gas Fee Schedule # Gas Fee Schedule
# Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf # Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf
type type
GasFeeKind = enum GasFeeKind* = enum
GasZero, # Nothing paid for operations of the set Wzero. GasZero, # Nothing paid for operations of the set Wzero.
GasBase, # Amount of gas to pay for operations of the set Wbase. GasBase, # Amount of gas to pay for operations of the set Wbase.
GasVeryLow, # Amount of gas to pay for operations of the set Wverylow. GasVeryLow, # Amount of gas to pay for operations of the set Wverylow.
@ -102,11 +102,13 @@ type
GasCosts* = array[Op, GasCost] GasCosts* = array[Op, GasCost]
template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyped) = template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
## Generate the gas cost for each forks and store them in a const ## Generate the gas cost for each forks and store them in a const
## named `ResultGasCostsName` ## named `ResultGasCostsName`
const FeeSchedule = gasFees[fork]
# ############### Helper functions ############################## # ############### Helper functions ##############################
func `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength: Natural): GasInt {.inline.} = func `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength: Natural): GasInt {.inline.} =
@ -151,7 +153,6 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
## Computes all but 1/64th ## Computes all but 1/64th
## L(n) ≡ n ⌊n/64⌋ - (floored(n/64)) ## L(n) ≡ n ⌊n/64⌋ - (floored(n/64))
# Introduced in EIP-150 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md # Introduced in EIP-150 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
# TODO: deactivate it pre-EIP150
# Note: The all-but-one-64th calculation should occur after the memory expansion fee is taken # Note: The all-but-one-64th calculation should occur after the memory expansion fee is taken
# https://github.com/ethereum/yellowpaper/pull/442 # https://github.com/ethereum/yellowpaper/pull/442
@ -291,10 +292,17 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
gasParams.c_memLength gasParams.c_memLength
) )
# Cnew_account - TODO - pre-EIP158 zero-value call consumed 25000 gas # Cnew_account
# https://github.com/ethereum/eips/issues/158 if gasParams.c_isNewAccount:
if gasParams.c_isNewAccount and not value.isZero: if fork < FkSpurious:
result.gasCost += static(FeeSchedule[GasNewAccount]) # Pre-EIP161 all account creation calls consumed 25000 gas.
result.gasCost += static(FeeSchedule[GasNewAccount])
else:
# Afterwards, only those transfering value:
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-158.md
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
if not value.isZero:
result.gasCost += static(FeeSchedule[GasNewAccount])
# Cxfer # Cxfer
if not value.isZero: if not value.isZero:
@ -305,13 +313,16 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp
let cextra = result.gasCost let cextra = result.gasCost
# Cgascap # Cgascap
result.gasCost = if gasParams.c_gasBalance >= result.gasCost: if fork >= FkTangerine:
min( # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
`prefix all_but_one_64th`(gasParams.c_gasBalance - result.gasCost), result.gasCost =
gasParams.c_contract_gas if gasParams.c_gasBalance >= result.gasCost:
) min(
else: `prefix all_but_one_64th`(gasParams.c_gasBalance - result.gasCost),
gasParams.c_contract_gas gasParams.c_contract_gas
)
else:
gasParams.c_contract_gas
# Ccallgas - Gas sent to the child message # Ccallgas - Gas sent to the child message
result.gasRefund = result.gasCost result.gasRefund = result.gasCost
@ -574,9 +585,20 @@ const
TangerineGasFees = HomesteadGasFees.tangerineGasFees TangerineGasFees = HomesteadGasFees.tangerineGasFees
SpuriousGasFees = TangerineGasFees.spuriousGasFees SpuriousGasFees = TangerineGasFees.spuriousGasFees
gasCosts(BaseGasFees, base, BaseGasCosts) gasFees*: array[Fork, GasFeeSchedule] = [
gasCosts(HomesteadGasFees, homestead, HomesteadGasCosts) FkFrontier: BaseGasFees,
gasCosts(TangerineGasFees, tangerine, TangerineGasCosts) FkThawing: BaseGasFees,
FkHomestead: HomesteadGasFees,
FkDao: HomesteadGasFees,
FkTangerine: TangerineGasFees,
FkSpurious: SpuriousGasFees,
FkByzantium: SpuriousGasFees, # not supported yet
]
gasCosts(FkFrontier, base, BaseGasCosts)
gasCosts(FkHomestead, homestead, HomesteadGasCosts)
gasCosts(FkTangerine, tangerine, TangerineGasCosts)
proc forkToSchedule*(fork: Fork): GasCosts = proc forkToSchedule*(fork: Fork): GasCosts =
if fork < FkHomestead: if fork < FkHomestead:

View File

@ -522,7 +522,7 @@ op create, inline = false, value, startPosition, size:
computation.vmState.blockHeader.rlphash, false). computation.vmState.blockHeader.rlphash, false).
getBalance(computation.msg.sender) getBalance(computation.msg.sender)
if senderBalance >= value: if senderBalance < value:
debug "Computation Failure", reason = "Insufficient funds available to transfer", required = computation.msg.value, balance = senderBalance debug "Computation Failure", reason = "Insufficient funds available to transfer", required = computation.msg.value, balance = senderBalance
push: 0 push: 0
return return

View File

@ -5,11 +5,10 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import stint import stint
type type
Fork* = enum Fork* = enum
# FkGenesis
FkFrontier, FkFrontier,
FkThawing, FkThawing,
FkHomestead, FkHomestead,
@ -18,17 +17,16 @@ type
FkSpurious, FkSpurious,
FkByzantium FkByzantium
UInt256Pair = tuple[a: Uint256, b: Uint256] const
forkBlocks*: array[Fork, Uint256] = [
let forkBlocks: array[Fork, Uint256] = [ FkFrontier: 1.u256, # 30/07/2015 19:26:28
FkFrontier: 1.u256, # 30/07/2015 19:26:28 FkThawing: 200_000.u256, # 08/09/2015 01:33:09
FkThawing: 200_000.u256, # 08/09/2015 01:33:09 FkHomestead: 1_150_000.u256, # 14/03/2016 20:49:53
FkHomestead: 1_150_000.u256, # 14/03/2016 20:49:53 FkDao: 1_920_000.u256, # 20/07/2016 17:20:40
FkDao: 1_920_000.u256, # 20/07/2016 17:20:40 FkTangerine: 2_463_000.u256, # 18/10/2016 17:19:31
FkTangerine: 2_463_000.u256, # 18/10/2016 17:19:31 FkSpurious: 2_675_000.u256, # 22/11/2016 18:15:44
FkSpurious: 2_675_000.u256, # 22/11/2016 18:15:44 FkByzantium: 4_370_000.u256 # 16/10/2017 09:22:11
FkByzantium: 4_370_000.u256 # 16/10/2017 09:22:11 ]
]
proc toFork*(blockNumber: UInt256): Fork = proc toFork*(blockNumber: UInt256): Fork =

View File

@ -17,7 +17,7 @@ func invalidInstruction*(computation: var BaseComputation) {.inline.} =
raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.") raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.")
let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block: let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block:
fill_enum_table_holes(Op, newIdentNode"invalidInstruction"): fill_enum_table_holes(Op, newIdentNode("invalidInstruction")):
[ [
Stop: newIdentNode "toBeReplacedByBreak", Stop: newIdentNode "toBeReplacedByBreak",
Add: newIdentNode "add", Add: newIdentNode "add",
@ -235,20 +235,22 @@ proc frontierVM(computation: var BaseComputation) =
proc updateOpcodeExec*(computation: var BaseComputation, fork: Fork) = proc updateOpcodeExec*(computation: var BaseComputation, fork: Fork) =
case fork case fork
of FkFrontier: of FkFrontier..FkSpurious:
computation.opCodeExec = frontierVM computation.opCodeExec = frontierVM
computation.frontierVM() computation.frontierVM()
else: else:
raise newException(VMError, "Unknown or not implemented fork: " & $fork) raise newException(VMError, "Unknown or not implemented fork: " & $fork)
proc updateOpcodeExec*(computation: var BaseComputation) = proc updateOpcodeExec*(computation: var BaseComputation) =
let fork = computation.vmState.blockHeader.blockNumber.toFork let fork = computation.getFork
computation.updateOpcodeExec(fork) computation.updateOpcodeExec(fork)
proc executeOpcodes*(computation: var BaseComputation) = proc executeOpcodes*(computation: var BaseComputation) =
# TODO: Optimise getting fork and updating opCodeExec only when necessary # TODO: Optimise getting fork and updating opCodeExec only when necessary
let fork = computation.vmState.blockHeader.blockNumber.toFork let fork = computation.getFork
try: try:
computation.updateOpcodeExec(fork) computation.updateOpcodeExec(fork)
except VMError: except VMError:
computation.error = Error(info: getCurrentExceptionMsg()) computation.error = Error(info: getCurrentExceptionMsg())
debug "executeOpcodes() failed", error = getCurrentExceptionMsg()

View File

@ -6,11 +6,11 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
ranges/typedranges, sequtils, strformat, tables, ranges/typedranges, sequtils, strformat, tables, options,
eth_common, chronicles, eth_common, chronicles,
./constants, ./errors, ./vm/computation, ./constants, ./errors, ./vm/computation,
./transaction, ./vm_types, ./vm_state, ./block_types, ./db/[db_chain, state_db], ./utils/header, ./transaction, ./vm_types, ./vm_state, ./block_types, ./db/[db_chain, state_db], ./utils/header,
./vm/interpreter, ./utils/addresses ./vm/interpreter, ./vm/interpreter/gas_costs, ./utils/addresses
func intrinsicGas*(data: openarray[byte]): GasInt = func intrinsicGas*(data: openarray[byte]): GasInt =
result = 21_000 result = 21_000
@ -33,7 +33,7 @@ proc validateTransaction*(vmState: BaseVMState, transaction: Transaction, sender
transaction.accountNonce == readOnlyDB.getNonce(sender) and transaction.accountNonce == readOnlyDB.getNonce(sender) and
readOnlyDB.getBalance(sender) >= gas_cost readOnlyDB.getBalance(sender) >= gas_cost
proc setupComputation*(header: BlockHeader, vmState: BaseVMState, transaction: Transaction, sender: EthAddress) : BaseComputation = proc setupComputation*(header: BlockHeader, vmState: BaseVMState, transaction: Transaction, sender: EthAddress, forkOverride=none(Fork)) : BaseComputation =
let message = newMessage( let message = newMessage(
gas = transaction.gasLimit - transaction.payload.intrinsicGas, gas = transaction.gasLimit - transaction.payload.intrinsicGas,
gasPrice = transaction.gasPrice, gasPrice = transaction.gasPrice,
@ -45,7 +45,7 @@ proc setupComputation*(header: BlockHeader, vmState: BaseVMState, transaction: T
options = newMessageOptions(origin = sender, options = newMessageOptions(origin = sender,
createAddress = transaction.to)) createAddress = transaction.to))
result = newBaseComputation(vmState, header.blockNumber, message) result = newBaseComputation(vmState, header.blockNumber, message, forkOverride)
doAssert result.isOriginComputation doAssert result.isOriginComputation
proc execComputation*(computation: var BaseComputation): bool = proc execComputation*(computation: var BaseComputation): bool =
@ -59,19 +59,24 @@ proc execComputation*(computation: var BaseComputation): bool =
except ValueError: except ValueError:
result = false result = false
proc applyCreateTransaction*(db: var AccountStateDB, t: Transaction, vmState: BaseVMState, sender: EthAddress, useHomestead: bool = false): UInt256 = proc applyCreateTransaction*(db: var AccountStateDB, t: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): UInt256 =
doAssert t.isContractCreation doAssert t.isContractCreation
# TODO: clean up params # TODO: clean up params
trace "Contract creation" trace "Contract creation"
let gasUsed = t.payload.intrinsicGas.GasInt + (if useHomestead: 32000 else: 0) let fork =
if forkOverride.isSome:
forkOverride.get
else:
vmState.blockNumber.toFork
let gasUsed = t.payload.intrinsicGas.GasInt + gasFees[fork][GasTXCreate]
# TODO: setupComputation refactoring # TODO: setupComputation refactoring
let contractAddress = generateAddress(sender, t.accountNonce) let contractAddress = generateAddress(sender, t.accountNonce)
let msg = newMessage(t.gasLimit - gasUsed, t.gasPrice, t.to, sender, t.value, @[], t.payload, let msg = newMessage(t.gasLimit - gasUsed, t.gasPrice, t.to, sender, t.value, @[], t.payload,
options = newMessageOptions(origin = sender, options = newMessageOptions(origin = sender,
createAddress = contractAddress)) createAddress = contractAddress))
var c = newBaseComputation(vmState, vmState.blockNumber, msg) var c = newBaseComputation(vmState, vmState.blockNumber, msg, forkOverride)
if execComputation(c): if execComputation(c):
db.addBalance(contractAddress, t.value) db.addBalance(contractAddress, t.value)

View File

@ -6,10 +6,10 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
tables, eth_common, tables, eth_common, options,
./constants, json, ./constants, json,
./vm/[memory, stack, code_stream], ./vm/[memory, stack, code_stream],
./vm/interpreter/[gas_costs, opcode_values], # TODO - will be hidden at a lower layer ./vm/interpreter/[gas_costs, opcode_values, vm_forks], # TODO - will be hidden at a lower layer
./db/db_chain ./db/db_chain
type type
@ -60,6 +60,7 @@ type
gasCosts*: GasCosts # TODO - will be hidden at a lower layer gasCosts*: GasCosts # TODO - will be hidden at a lower layer
opCodeExec*: OpcodeExecutor opCodeExec*: OpcodeExecutor
lastOpCodeHasRetVal*: bool lastOpCodeHasRetVal*: bool
forkOverride*: Option[Fork]
Error* = ref object Error* = ref object
info*: string info*: string

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
import import
unittest, strformat, strutils, tables, json, ospaths, times, unittest, strformat, strutils, tables, json, ospaths, times,
byteutils, ranges/typedranges, nimcrypto/[keccak, hash], byteutils, ranges/typedranges, nimcrypto/[keccak, hash], options,
rlp, eth_trie/db, eth_common, rlp, eth_trie/db, eth_common,
eth_keys, eth_keys,
./test_helpers, ./test_helpers,
@ -23,14 +23,15 @@ suite "generalstate json tests":
jsonTest("GeneralStateTests", testFixture) jsonTest("GeneralStateTests", testFixture)
proc testFixtureIndexes(header: BlockHeader, pre: JsonNode, transaction: Transaction, sender: EthAddress, expectedHash: string) = proc testFixtureIndexes(header: BlockHeader, pre: JsonNode, transaction: Transaction, sender: EthAddress, expectedHash: string, testStatusIMPL: var TestStatus, fork: Fork) =
var vmState = newBaseVMState(header, newBaseChainDB(newMemoryDb())) var vmState = newBaseVMState(header, newBaseChainDB(newMemoryDb()))
vmState.mutateStateDB: vmState.mutateStateDB:
setupStateDB(pre, db) setupStateDB(pre, db)
defer: defer:
#echo vmState.readOnlyStateDB.dumpAccount("c94f5374fce5edbc8e2a8697c15331677e6ebf0b") #echo vmState.readOnlyStateDB.dumpAccount("c94f5374fce5edbc8e2a8697c15331677e6ebf0b")
doAssert "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii == expectedHash let obtainedHash = "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii
check obtainedHash == expectedHash
if not validateTransaction(vmState, transaction, sender): if not validateTransaction(vmState, transaction, sender):
vmState.mutateStateDB: vmState.mutateStateDB:
@ -56,10 +57,10 @@ proc testFixtureIndexes(header: BlockHeader, pre: JsonNode, transaction: Transac
# fixtures/GeneralStateTests/stTransactionTest/TransactionSendingToEmpty.json # fixtures/GeneralStateTests/stTransactionTest/TransactionSendingToEmpty.json
#db.addBalance(generateAddress(sender, transaction.accountNonce), transaction.value) #db.addBalance(generateAddress(sender, transaction.accountNonce), transaction.value)
let createGasUsed = applyCreateTransaction(db, transaction, vmState, sender, true) let createGasUsed = applyCreateTransaction(db, transaction, vmState, sender, some(fork))
db.addBalance(header.coinbase, createGasUsed) db.addBalance(header.coinbase, createGasUsed)
return return
var computation = setupComputation(header, vmState, transaction, sender) var computation = setupComputation(header, vmState, transaction, sender, some(fork))
vmState.mutateStateDB: vmState.mutateStateDB:
# contract creation transaction.to == 0, so ensure happens after # contract creation transaction.to == 0, so ensure happens after
@ -108,13 +109,16 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
) )
let ftrans = fixture["transaction"] let ftrans = fixture["transaction"]
for expectation in fixture["post"]["Homestead"]: for fork in supportedForks:
let if fixture["post"].has_key(forkNames[fork]):
expectedHash = expectation["hash"].getStr # echo "[fork: ", forkNames[fork], "]"
indexes = expectation["indexes"] for expectation in fixture["post"][forkNames[fork]]:
dataIndex = indexes["data"].getInt let
gasIndex = indexes["gas"].getInt expectedHash = expectation["hash"].getStr
valueIndex = indexes["value"].getInt indexes = expectation["indexes"]
let transaction = ftrans.getFixtureTransaction(dataIndex, gasIndex, valueIndex) dataIndex = indexes["data"].getInt
let sender = ftrans.getFixtureTransactionSender gasIndex = indexes["gas"].getInt
testFixtureIndexes(header, fixture["pre"], transaction, sender, expectedHash) valueIndex = indexes["value"].getInt
let transaction = ftrans.getFixtureTransaction(dataIndex, gasIndex, valueIndex)
let sender = ftrans.getFixtureTransactionSender
testFixtureIndexes(header, fixture["pre"], transaction, sender, expectedHash, testStatusIMPL, fork)

View File

@ -14,23 +14,36 @@ import
../nimbus/vm/interpreter/[gas_costs, vm_forks], ../nimbus/vm/interpreter/[gas_costs, vm_forks],
../tests/test_generalstate_failing ../tests/test_generalstate_failing
const
# from https://ethereum-tests.readthedocs.io/en/latest/test_types/state_tests.html
forkNames* = {
FkFrontier: "Frontier",
FkHomestead: "Homestead",
FkTangerine: "EIP150",
FkSpurious: "EIP158",
FkByzantium: "Byzantium",
}.toTable
supportedForks* = [FkHomestead]
type type
Status* {.pure.} = enum OK, Fail, Skip Status* {.pure.} = enum OK, Fail, Skip
func slowTest*(folder: string, name: string): bool = func slowTest*(folder: string, name: string): bool =
# TODO: add vmPerformance and loop check here result =
result = folder == "stQuadraticComplexityTest" or (folder == "vmPerformance" and "loop" in name) or
name in @["randomStatetest352.json", "randomStatetest1.json", folder == "stQuadraticComplexityTest" or
"randomStatetest32.json", "randomStatetest347.json", name in @["randomStatetest352.json", "randomStatetest1.json",
"randomStatetest393.json", "randomStatetest626.json", "randomStatetest32.json", "randomStatetest347.json",
"CALLCODE_Bounds.json", "DELEGATECALL_Bounds3.json", "randomStatetest393.json", "randomStatetest626.json",
"CALLCODE_Bounds4.json", "CALL_Bounds.json", "CALLCODE_Bounds.json", "DELEGATECALL_Bounds3.json",
"DELEGATECALL_Bounds2.json", "CALL_Bounds3.json", "CALLCODE_Bounds4.json", "CALL_Bounds.json",
"CALLCODE_Bounds2.json", "CALLCODE_Bounds3.json", "DELEGATECALL_Bounds2.json", "CALL_Bounds3.json",
"DELEGATECALL_Bounds.json", "CALL_Bounds2a.json", "CALLCODE_Bounds2.json", "CALLCODE_Bounds3.json",
"CALL_Bounds2.json", "DELEGATECALL_Bounds.json", "CALL_Bounds2a.json",
"CallToNameRegistratorMemOOGAndInsufficientBalance.json", "CALL_Bounds2.json",
"CallToNameRegistratorTooMuchMemory0.json"] "CallToNameRegistratorMemOOGAndInsufficientBalance.json",
"CallToNameRegistratorTooMuchMemory0.json"]
func failIn32Bits(folder, name: string): bool = func failIn32Bits(folder, name: string): bool =
return name in @[ return name in @[
@ -82,7 +95,7 @@ func failIn32Bits(folder, name: string): bool =
"returndatasize_initial_zero_read.json", "returndatasize_initial_zero_read.json",
"call_then_create_successful_then_returndatasize.json", "call_then_create_successful_then_returndatasize.json",
"call_outsize_then_create_successful_then_returndatasize.json", "call_outsize_then_create_successful_then_returndatasize.json",
"returndatacopy_following_create.json", "returndatacopy_following_create.json",
"returndatacopy_following_revert_in_create.json", "returndatacopy_following_revert_in_create.json",
"returndatacopy_following_successful_create.json", "returndatacopy_following_successful_create.json",
@ -99,26 +112,29 @@ func allowedFailInCurrentBuild(folder, name: string): bool =
return allowedFailingGeneralStateTest(folder, name) return allowedFailingGeneralStateTest(folder, name)
func validTest*(folder: string, name: string): bool = func validTest*(folder: string, name: string): bool =
# tests we want to skip or which segfault will be skipped here # we skip tests that are slow or expected to fail for now
result = (folder != "vmPerformance" or "loop" notin name) and result =
not slowTest(folder, name) not slowTest(folder, name) and
not allowedFailInCurrentBuild(folder, name)
proc lacksHomesteadPostStates*(filename: string): bool = proc lacksSupportedForks*(filename: string): bool =
# XXX: Until Nimbus supports Byzantine or newer forks, as opposed # XXX: Until Nimbus supports Byzantine or newer forks, as opposed
# to Homestead, ~1k of ~2.5k GeneralStateTests won't work. Nimbus # to Homestead, ~1k of ~2.5k GeneralStateTests won't work.
# supporting Byzantine should trigger removal of this function. A
# possible alternate approach of avoiding double-reading fixtures
# seemed less than ideal, as by the time that happens, output has
# already appeared. Compatible with non-GST fixtures. Will become
# expensive once BlockchainTests appear, so try to remove first.
let fixtures = parseJSON(readFile(filename)) let fixtures = parseJSON(readFile(filename))
var fixture: JsonNode var fixture: JsonNode
for label, child in fixtures: for label, child in fixtures:
fixture = child fixture = child
break break
return fixture.kind == JObject and fixture.has_key("transaction") and # not all fixtures make a distinction between forks, so default to accepting
(fixture.has_key("post") and not fixture["post"].has_key("Homestead")) # them all, until we find the ones that specify forks in their "post" section
result = false
if fixture.kind == JObject and fixture.has_key("transaction") and fixture.has_key("post"):
result = true
for fork in supportedForks:
if fixture["post"].has_key(forkNames[fork]):
result = false
break
macro jsonTest*(s: static[string], handler: untyped): untyped = macro jsonTest*(s: static[string], handler: untyped): untyped =
let let
@ -128,30 +144,29 @@ macro jsonTest*(s: static[string], handler: untyped): untyped =
final = newIdentNode"final" final = newIdentNode"final"
name = newIdentNode"name" name = newIdentNode"name"
formatted = newStrLitNode"{symbol[final]} {name:<64}{$final}{'\n'}" formatted = newStrLitNode"{symbol[final]} {name:<64}{$final}{'\n'}"
result = quote: result = quote:
var filenames: seq[(string, string, string)] = @[] var filenames: seq[(string, string, string)] = @[]
var status = initOrderedTable[string, OrderedTable[string, Status]]() var status = initOrderedTable[string, OrderedTable[string, Status]]()
for filename in walkDirRec("tests" / "fixtures" / `s`): for filename in walkDirRec("tests" / "fixtures" / `s`):
if not filename.endsWith(".json"):
continue
var (folder, name) = filename.splitPath() var (folder, name) = filename.splitPath()
let last = folder.splitPath().tail let last = folder.splitPath().tail
if not status.hasKey(last): if not status.hasKey(last):
status[last] = initOrderedTable[string, Status]() status[last] = initOrderedTable[string, Status]()
status[last][name] = Status.Skip status[last][name] = Status.Skip
if last.validTest(name) and not filename.lacksHomesteadPostStates: if last.validTest(name) and not filename.lacksSupportedForks:
filenames.add((filename, last, name)) filenames.add((filename, last, name))
for child in filenames: for child in filenames:
let (filename, folder, name) = child let (filename, folder, name) = child
# we set this here because exceptions might be raised in the handler:
status[folder][name] = Status.Fail
test filename: test filename:
echo folder / name echo folder / name
status[folder][name] = Status.FAIL `handler`(parseJSON(readFile(filename)), `testStatusIMPL`)
try: if `testStatusIMPL` == OK:
`handler`(parseJSON(readFile(filename)), `testStatusIMPL`) status[folder][name] = Status.OK
if `testStatusIMPL` == OK:
status[folder][name] = Status.OK
except AssertionError:
status[folder][name] = Status.FAIL
if not allowedFailInCurrentBuild(folder, name):
raise
status.sort do (a: (string, OrderedTable[string, Status]), status.sort do (a: (string, OrderedTable[string, Status]),
b: (string, OrderedTable[string, Status])) -> int: cmp(a[0], b[0]) b: (string, OrderedTable[string, Status])) -> int: cmp(a[0], b[0])