diff --git a/tests/test_blockchain_json.nim b/tests/test_blockchain_json.nim index f674566dd..513211b3b 100644 --- a/tests/test_blockchain_json.nim +++ b/tests/test_blockchain_json.nim @@ -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 diff --git a/tests/test_config.nim b/tests/test_config.nim index 349cbc9ed..ea12abb45 100644 --- a/tests/test_config.nim +++ b/tests/test_config.nim @@ -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) diff --git a/tests/test_generalstate_json.nim b/tests/test_generalstate_json.nim index ea91d69ef..104f97f8e 100644 --- a/tests/test_generalstate_json.nim +++ b/tests/test_generalstate_json.nim @@ -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 diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index be22c3bd7..1e98c7550 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -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, diff --git a/tools/evmstate/evmstate.nim b/tools/evmstate/evmstate.nim index 151591608..9368d4a6c 100644 --- a/tools/evmstate/evmstate.nim +++ b/tools/evmstate/evmstate.nim @@ -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..= 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 diff --git a/tools/evmstate/helpers.nim b/tools/evmstate/helpers.nim index ecb7d9589..cb0b3eed9 100644 --- a/tools/evmstate/helpers.nim +++ b/tools/evmstate/helpers.nim @@ -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"