reduce code duplication in multiple test runners

This commit is contained in:
jangko 2023-01-11 00:02:21 +07:00
parent 735d780424
commit a232c7eb1d
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 95 additions and 257 deletions

View File

@ -20,7 +20,8 @@ import
../nimbus/utils/utils,
../nimbus/core/[executor, validate, pow/header],
../stateless/[tree_from_witness, witness_types],
../tools/common/helpers,
../tools/common/helpers as chp,
../tools/evmstate/helpers,
../nimbus/common/common
type
@ -344,7 +345,7 @@ proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = fal
# - mine block
# 3 - diff resulting state with expected state
# 4 - check that all previous blocks were valid
let specifyIndex = test_config.getConfiguration().index
let specifyIndex = test_config.getConfiguration().index.get(0)
var fixtureIndex = 0
var fixtureTested = false

View File

@ -1,7 +1,6 @@
import
std/[parseopt, strutils, tables],
../nimbus/common/evmforks,
./test_helpers
std/[parseopt, strutils, options],
../nimbus/common/evmforks
type
ConfigStatus* = enum
@ -15,8 +14,8 @@ type
Configuration = ref object
testSubject*: string
fork*: EVMFork
index*: int
fork*: string
index*: Option[int]
trace*: bool
legacy*: bool
pruning*: bool
@ -25,8 +24,6 @@ var testConfig {.threadvar.}: Configuration
proc initConfiguration(): Configuration =
result = new Configuration
result.fork = FkFrontier
result.index = 0
result.trace = true
result.pruning = true
@ -47,8 +44,8 @@ proc processArguments*(msg: var string): ConfigStatus =
config.testSubject = key
of cmdLongOption, cmdShortOption:
case key.toLowerAscii()
of "fork": config.fork = nameToFork[strip(value)]
of "index": config.index = parseInt(value)
of "fork": config.fork = value
of "index": config.index = some(parseInt(value))
of "trace": config.trace = parseBool(value)
of "legacy": config.legacy = parseBool(value)
of "pruning": config.pruning = parseBool(value)

View File

@ -14,6 +14,8 @@ import
../nimbus/db/accounts_cache,
../nimbus/common/common,
../nimbus/utils/utils,
../tools/common/helpers as chp,
../tools/evmstate/helpers,
chronicles,
eth/rlp,
eth/trie/trie_defs,
@ -26,9 +28,9 @@ type
header: BlockHeader
pre: JsonNode
tx: Transaction
expectedHash: string
expectedLogs: string
fork: EVMFork
expectedHash: Hash256
expectedLogs: Hash256
chainConfig: ChainConfig
debugMode: bool
trace: bool
index: int
@ -83,18 +85,9 @@ proc dumpDebugData(tester: Tester, vmState: BaseVMState, sender: EthAddress, gas
let status = if success: "_success" else: "_failed"
writeFile("debug_" & tester.name & "_" & $tester.index & status & ".json", debugData.pretty())
# using only one networkParams will reduce execution
# time ~90% instead of create it for every test
let params = chainConfigForNetwork(MainNet)
proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
if tester.fork == FkParis:
params.terminalTotalDifficulty = some(0.u256)
else:
params.terminalTotalDifficulty = none(BlockNumber)
let
com = CommonRef.new(newMemoryDB(), params, getConfiguration().pruning)
com = CommonRef.new(newMemoryDB(), tester.chainConfig, getConfiguration().pruning)
parent = BlockHeader(stateRoot: emptyRlpHash)
let vmState = BaseVMState.new(
@ -106,6 +99,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
var gasUsed: GasInt
let sender = tester.tx.getSender()
let fork = com.toEVMFork(tester.header.blockNumber)
vmState.mutateStateDB:
setupStateDB(tester.pre, db)
@ -116,18 +110,17 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
db.persist()
defer:
let obtainedHash = "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii
let obtainedHash = vmState.readOnlyStateDB.rootHash
check obtainedHash == tester.expectedHash
let logEntries = vmState.getAndClearLogEntries()
let actualLogsHash = hashLogEntries(logEntries)
let expectedLogsHash = toLowerAscii(tester.expectedLogs)
check(expectedLogsHash == actualLogsHash)
check(tester.expectedLogs == actualLogsHash)
if tester.debugMode:
let success = expectedLogsHash == actualLogsHash and obtainedHash == tester.expectedHash
let success = tester.expectedLogs == actualLogsHash and obtainedHash == tester.expectedHash
tester.dumpDebugData(vmState, sender, gasUsed, success)
let rc = vmState.processTransaction(
tester.tx, sender, tester.header, tester.fork)
tester.tx, sender, tester.header, fork)
if rc.isOk:
gasUsed = rc.value
@ -144,7 +137,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
if miner in vmState.selfDestructs:
vmState.mutateStateDB:
db.addBalance(miner, 0.u256)
if tester.fork >= FkSpurious:
if fork >= FkSpurious:
if db.isEmptyAccount(miner):
db.deleteAccount(miner)
@ -155,7 +148,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
db.persist()
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus,
trace = false, debugMode = false, supportedForks: set[EVMFork] = supportedForks) =
trace = false, debugMode = false) =
var tester: Tester
var fixture: JsonNode
for label, child in fixtures:
@ -163,57 +156,53 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus,
tester.name = label
break
let fenv = fixture["env"]
tester.header = BlockHeader(
coinbase : fenv["currentCoinbase"].getStr.ethAddressFromHex,
difficulty : fromHex(UInt256, fenv{"currentDifficulty"}.getStr),
blockNumber: fenv{"currentNumber"}.getHexadecimalInt.u256,
gasLimit : fenv{"currentGasLimit"}.getHexadecimalInt.GasInt,
timestamp : fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix,
stateRoot : emptyRlpHash
)
if "currentBaseFee" in fenv:
tester.header.baseFee = fromHex(UInt256, fenv{"currentBaseFee"}.getStr)
if "currentRandom" in fenv:
tester.header.prevRandao.data = hexToByteArray[32](fenv{"currentRandom"}.getStr)
let specifyIndex = getConfiguration().index
tester.pre = fixture["pre"]
tester.header = parseHeader(fixture["env"])
tester.trace = trace
tester.debugMode = debugMode
let ftrans = fixture["transaction"]
var testedInFork = false
var numIndex = -1
for fork in supportedForks:
if fixture["post"].hasKey(forkNames[fork]):
numIndex = fixture["post"][forkNames[fork]].len
for expectation in fixture["post"][forkNames[fork]]:
inc tester.index
if specifyIndex > 0 and tester.index != specifyIndex:
continue
testedInFork = true
tester.expectedHash = expectation["hash"].getStr
tester.expectedLogs = expectation["logs"].getStr
let
indexes = expectation["indexes"]
dataIndex = indexes["data"].getInt
gasIndex = indexes["gas"].getInt
valueIndex = indexes["value"].getInt
tester.tx = ftrans.getFixtureTransaction(dataIndex, gasIndex, valueIndex)
tester.pre = fixture["pre"]
tester.fork = fork
testFixtureIndexes(tester, testStatusIMPL)
if not testedInFork:
echo "test subject '", tester.name, "' not tested in any forks/subtests"
if specifyIndex <= 0 or specifyIndex > numIndex:
echo "Maximum subtest available: ", numIndex
let
post = fixture["post"]
txData = fixture["transaction"]
conf = getConfiguration()
template prepareFork(forkName: string) =
try:
tester.chainConfig = getChainConfig(forkName)
except ValueError as ex:
debugEcho ex.msg
return
template runSubTest(subTest: JsonNode) =
tester.expectedHash = Hash256.fromJson(subTest["hash"])
tester.expectedLogs = Hash256.fromJson(subTest["logs"])
tester.tx = parseTx(txData, subTest["indexes"])
tester.testFixtureIndexes(testStatusIMPL)
if conf.fork.len > 0:
if not post.hasKey(conf.fork):
debugEcho "selected fork not available: " & conf.fork
return
let forkData = post[conf.fork]
prepareFork(conf.fork)
if conf.index.isNone:
for subTest in forkData:
runSubTest(subTest)
else:
echo "available forks in this test:"
for fork in test_helpers.supportedForks:
if fixture["post"].hasKey(forkNames[fork]):
echo fork
let index = conf.index.get()
if index > forkData.len or index < 0:
debugEcho "selected index out of range(0-$1), requested $2" %
[$forkData.len, $index]
return
let subTest = forkData[index]
runSubTest(subTest)
else:
for forkName, forkData in post:
prepareFork(forkName)
for subTest in forkData:
runSubTest(subTest)
proc generalStateJsonMain*(debugMode = false) =
const
@ -239,9 +228,7 @@ proc generalStateJsonMain*(debugMode = false) =
let path = "tests" / "fixtures" / folder
let n = json.parseFile(path / config.testSubject)
var testStatusIMPL: TestStatus
var forks: set[EVMFork] = {}
forks.incl config.fork
testFixture(n, testStatusIMPL, config.trace, true, forks)
testFixture(n, testStatusIMPL, config.trace, true)
when isMainModule:
var message: string

View File

@ -34,19 +34,6 @@ const
FkParis: "Merge"
}.toTable
supportedForks* = {
FkFrontier,
FkHomestead,
FkTangerine,
FkSpurious,
FkByzantium,
FkConstantinople,
FkPetersburg,
FkIstanbul,
FkBerlin,
FkLondon,
FkParis}
nameToFork* = revmap(forkNames)
func skipNothing*(folder: string, name: string): bool = false
@ -121,20 +108,6 @@ func getHexadecimalInt*(j: JsonNode): int64 =
data = fromHex(StUint[64], j.getStr)
result = cast[int64](data)
proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
for ac, accountData in wantedState:
let account = ethAddressFromHex(ac)
for slot, value in accountData{"storage"}:
stateDB.setStorage(account, fromHex(UInt256, slot), fromHex(UInt256, value.getStr))
let nonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
let code = accountData{"code"}.getStr.safeHexToSeqByte
let balance = UInt256.fromHex accountData{"balance"}.getStr
stateDB.setNonce(account, nonce)
stateDB.setCode(account, code)
stateDB.setBalance(account, balance)
proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
for ac, accountData in wantedState:
let account = ethAddressFromHex(ac)
@ -165,93 +138,8 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
if wantedNonce != actualNonce:
raise newException(ValidationError, &"{ac} nonceDiff {wantedNonce.toHex} != {actualNonce.toHex}")
proc parseAccessList(n: JsonNode): AccessList =
if n.kind == JNull:
return
for x in n:
var ap = AccessPair(
address: parseAddress(x["address"].getStr)
)
let sks = x["storageKeys"]
for sk in sks:
ap.storageKeys.add hexToByteArray[32](sk.getStr())
result.add ap
proc getFixtureTransaction*(j: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
let dynamicFeeTx = "gasPrice" notin j
let nonce = j["nonce"].getHexadecimalInt.AccountNonce
let gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt
var toAddr: Option[EthAddress]
# Fixture transactions with `"to": ""` are contract creations.
#
# Fixture transactions with `"to": "0x..."` or `"to": "..."` where `...` are
# 40 hex digits are call/transfer transactions. Even if the digits are all
# zeros, because the all-zeros address is a legitimate account.
#
# There are no other formats. The number of digits if present is always 40,
# "0x" prefix is used in some but not all fixtures, and upper case hex digits
# occur in a few.
let rawTo = j["to"].getStr
if rawTo != "":
toAddr = some(rawTo.parseAddress)
let hexStr = j["value"][valueIndex].getStr
# stTransactionTest/ValueOverflow.json
# prevent parsing exception and subtitute it with max uint256
let value = if ':' in hexStr: high(UInt256) else: fromHex(UInt256, hexStr)
let payload = j["data"][dataIndex].getStr.safeHexToSeqByte
var secretKey = j["secretKey"].getStr
removePrefix(secretKey, "0x")
let privateKey = PrivateKey.fromHex(secretKey).tryGet()
if dynamicFeeTx:
let accList = j["accessLists"][dataIndex]
var tx = Transaction(
txType: TxEip1559,
nonce: nonce,
maxFee: j["maxFeePerGas"].getHexadecimalInt,
maxPriorityFee: j["maxPriorityFeePerGas"].getHexadecimalInt,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload,
accessList: parseAccessList(accList),
chainId: ChainId(1)
)
return signTransaction(tx, privateKey, ChainId(1), false)
let gasPrice = j["gasPrice"].getHexadecimalInt
if j.hasKey("accessLists"):
let accList = j["accessLists"][dataIndex]
var tx = Transaction(
txType: TxEip2930,
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload,
accessList: parseAccessList(accList),
chainId: ChainId(1)
)
signTransaction(tx, privateKey, ChainId(1), false)
else:
var tx = Transaction(
txType: TxLegacy,
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload
)
signTransaction(tx, privateKey, ChainId(1), false)
proc hashLogEntries*(logs: seq[Log]): string =
toLowerAscii("0x" & $keccakHash(rlp.encode(logs)))
proc hashLogEntries*(logs: seq[Log]): Hash256 =
keccakHash(rlp.encode(logs))
proc setupEthNode*(
conf: NimbusConf, ctx: EthContext,

View File

@ -20,6 +20,7 @@ import
../../nimbus/transaction,
../../nimbus/core/executor,
../../nimbus/common/common,
../common/helpers as chp,
"."/[config, helpers]
type
@ -29,7 +30,7 @@ type
tx: Transaction
expectedHash: Hash256
expectedLogs: Hash256
fork: EVMFork
chainConfig: ChainConfig
index: int
tracerFlags: set[TracerFlags]
error: string
@ -64,13 +65,6 @@ proc extractNameAndFixture(ctx: var StateContext, n: JsonNode): JsonNode =
return
doAssert(false, "unreachable")
proc parseTx(txData, index: JsonNode): Transaction =
let
dataIndex = index["data"].getInt
gasIndex = index["gas"].getInt
valIndex = index["value"].getInt
parseTx(txData, dataIndex, gasIndex, valIndex)
proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte
@ -190,7 +184,6 @@ proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
if pretty:
stderr.writeLine(trace.pretty)
else:
var gasUsed = 0
let logs = trace["structLogs"]
trace.delete("structLogs")
for x in logs:
@ -204,14 +197,9 @@ proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let
params = chainConfigForNetwork(MainNet)
if ctx.fork == FkParis:
params.terminalTotalDifficulty = some(0.u256)
let
com = CommonRef.new(newMemoryDB(), params, pruneTrie = false)
com = CommonRef.new(newMemoryDB(), ctx.chainConfig, pruneTrie = false)
parent = BlockHeader(stateRoot: emptyRlpHash)
fork = com.toEVMFork(ctx.header.blockNumber)
let vmState = TestVMState()
vmState.init(
@ -233,7 +221,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
name : ctx.name,
pass : ctx.error.len == 0,
root : vmState.stateDB.rootHash,
fork : toString(ctx.fork),
fork : $fork,
error: ctx.error
)
if conf.dumpEnabled:
@ -242,7 +230,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
writeTraceToStderr(vmState, conf.pretty)
let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header, ctx.fork)
ctx.tx, sender, ctx.header, fork)
if rc.isOk:
gasUsed = rc.value
@ -250,7 +238,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
if miner in vmState.selfDestructs:
vmState.mutateStateDB:
db.addBalance(miner, 0.u256)
if ctx.fork >= FkSpurious:
if fork >= FkSpurious:
if db.isEmptyAccount(miner):
db.deleteAccount(miner)
db.persist(clearCache = false)
@ -289,9 +277,11 @@ proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
hasError = false
template prepareFork(forkName: string) =
let fork = parseFork(forkName)
doAssert(fork.isSome, "unsupported fork: " & forkName)
ctx.fork = fork.get()
try:
ctx.chainConfig = getChainConfig(forkName)
except ValueError as ex:
debugEcho ex.msg
return false
ctx.index = index
inc index
@ -332,14 +322,15 @@ proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
not hasError
when defined(chronicles_runtime_filtering):
proc toLogLevel(v: int): LogLevel =
type Lev = chronicles.LogLevel
proc toLogLevel(v: int): Lev =
case v
of 1: LogLevel.ERROR
of 2: LogLevel.WARN
of 3: LogLevel.INFO
of 4: LogLevel.DEBUG
of 5: LogLevel.TRACE
else: LogLevel.NONE
of 1: Lev.ERROR
of 2: Lev.WARN
of 3: Lev.INFO
of 4: Lev.DEBUG
of 5: Lev.TRACE
else: Lev.NONE
proc setVerbosity(v: int) =
let level = v.toLogLevel

View File

@ -131,6 +131,13 @@ proc parseTx*(n: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
let secretKey = required(PrivateKey, "secretKey")
signTransaction(tx, secretKey, tx.chainId, false)
proc parseTx*(txData, index: JsonNode): Transaction =
let
dataIndex = index["data"].getInt
gasIndex = index["gas"].getInt
valIndex = index["value"].getInt
parseTx(txData, dataIndex, gasIndex, valIndex)
proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
for ac, accountData in wantedState:
let account = hexToByteArray[20](ac)
@ -140,36 +147,3 @@ proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
stateDB.setNonce(account, fromJson(AccountNonce, accountData["nonce"]))
stateDB.setCode(account, fromJson(Blob, accountData["code"]))
stateDB.setBalance(account, fromJson(UInt256, accountData["balance"]))
proc parseFork*(x: string): Option[EVMFork] =
case x
of "Frontier" : some(FkFrontier)
of "Homestead" : some(FkHomestead)
of "EIP150" : some(FkTangerine)
of "EIP158" : some(FkSpurious)
of "Byzantium" : some(FkByzantium)
of "Constantinople" : some(FkConstantinople)
of "ConstantinopleFix": some(FkPetersburg)
of "Istanbul" : some(FkIstanbul)
of "Berlin" : some(FkBerlin)
of "London" : some(FkLondon)
of "Merge" : some(FkParis)
of "Shanghai" : some(FkShanghai)
of "Cancun" : some(FkCancun)
else: none(EVMFork)
proc toString*(x: EVMFork): string =
case x
of FkFrontier : "Frontier"
of FkHomestead : "Homestead"
of FkTangerine : "EIP150"
of FkSpurious : "EIP158"
of FkByzantium : "Byzantium"
of FkConstantinople: "Constantinople"
of FkPetersburg : "ConstantinopleFix"
of FkIstanbul : "Istanbul"
of FkBerlin : "Berlin"
of FkLondon : "London"
of FkParis : "Merge"
of FkShanghai : "Shanghai"
of FkCancun : "Cancun"