nimbus-eth1/tools/t8n/t8n_test.nim

678 lines
20 KiB
Nim

# Nimbus
# Copyright (c) 2022-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * 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
std/[os, osproc, strutils, json, tables],
unittest2,
"."/[types]
type
T8nInput = object
inAlloc : string
inTxs : string
inEnv : string
stFork : string
stReward: string
chainid : string
T8nOutput = object
alloc : bool
result: bool
body : bool
trace : bool
TestSpec = object
name : string
base : string
input : T8nInput
output : T8nOutput
expExitCode: int
expOut : string
JsonComparator = object
path: string
error: string
proc t8nInput(alloc, txs, env, fork: string;
reward = "0"; chainid = ""): T8nInput =
T8nInput(
inAlloc : alloc,
inTxs : txs,
inEnv : env,
stFork : fork,
stReward: reward,
chainid : chainid,
)
proc get(opt: T8nInput, base : string): string =
result.add(" --input.alloc " & (base / opt.inAlloc))
result.add(" --input.txs " & (base / opt.inTxs))
result.add(" --input.env " & (base / opt.inEnv))
result.add(" --state.fork " & opt.stFork)
if opt.stReward.len > 0:
result.add(" --state.reward " & opt.stReward)
if opt.chainid.len > 0:
result.add(" --state.chainid " & opt.chainid)
proc get(opt: T8nOutput): string =
if opt.alloc and not opt.trace:
result.add(" --output.alloc stdout")
else:
result.add(" --output.alloc")
if opt.result and not opt.trace:
result.add(" --output.result stdout")
else:
result.add(" --output.result")
if opt.body and not opt.trace:
result.add(" --output.body stdout")
else:
result.add(" --output.body")
if opt.trace:
result.add(" --trace stdout")
template exit(jsc: var JsonComparator, msg: string) =
jsc.path = path
jsc.error = msg
return false
proc cmp(jsc: var JsonComparator; a, b: JsonNode, path: string): bool =
## Check two nodes for equality
if a.isNil:
if b.isNil: return true
jsc.exit("A nil, but B not nil")
elif b.isNil:
jsc.exit("A not nil, but B nil")
elif a.kind != b.kind:
jsc.exit("A($1) != B($2)" % [$a.kind, $b.kind])
else:
result = true
case a.kind
of JString:
if a.str != b.str:
jsc.exit("STRING A($1) != B($2)" % [a.str, b.str])
of JInt:
if a.num != b.num:
jsc.exit("INT A($1) != B($2)" % [$a.num, $b.num])
of JFloat:
if a.fnum != b.fnum:
jsc.exit("FLOAT A($1) != B($2)" % [$a.fnum, $b.fnum])
of JBool:
if a.bval != b.bval:
jsc.exit("BOOL A($1) != B($2)" % [$a.bval, $b.bval])
of JNull:
result = true
of JArray:
for i, x in a.elems:
if not jsc.cmp(x, b.elems[i], path & "/" & $i):
return false
of JObject:
# we cannot use OrderedTable's equality here as
# the order does not matter for equality here.
var
aFields = newSeqOfCap[string](a.fields.len)
bFields = newSeqOfCap[string](b.fields.len)
for key, val in a.fields:
if val.kind != JNull:
aFields.add key
for key, val in b.fields:
if val.kind != JNull:
bFields.add key
if aFields.len != bFields.len:
jsc.exit("OBJ LEN A($1) != B($2)" % [$aFields.len, $bFields.len])
for key in aFields:
let val = a.fields[key]
if not b.fields.hasKey(key):
jsc.exit("OBJ FIELD A($1) != B(none)" % [key])
if not jsc.cmp(val, b.fields[key], path & "/" & key):
return false
proc notRejectedError(path: string): bool =
# we only check error status, and not the error message
# because each implementation can have different error
# message
not (path.startsWith("root/result/rejected/") and
path.endsWith("/error"))
proc runTest(appDir: string, spec: TestSpec): bool =
when defined(evmc_enabled):
# TODO: test both evm?
# skip trace test if evmc_enabled
# because the error msg of trace output is
# different for nimvm and evmc
if spec.output.trace:
return true
let base = appDir / spec.base
let args = spec.input.get(base) & spec.output.get()
let cmd = appDir / "t8n" & args
let (res, exitCode) = execCmdEx(cmd)
if exitCode != spec.expExitCode:
echo "test $1: wrong exit code, have $2, want $3" %
[spec.name, $exitCode, $spec.expExitCode]
echo res
return false
if spec.expOut.len > 0:
if spec.expOut.endsWith(".json"):
let path = base / spec.expOut
try:
let want = json.parseFile(path)
let have = json.parseJson(res)
var jsc = JsonComparator()
if not jsc.cmp(want, have, "root") and notRejectedError(jsc.path):
echo "test $1: output wrong, have \n$2\nwant\n$3\n" %
[spec.name, have.pretty, want.pretty]
echo "path: $1, error: $2" %
[jsc.path, jsc.error]
return false
except JsonParsingError as exc:
echo "test $1: ERROR: $2" % [spec.name, exc.msg]
echo "test $1: OUTPUT: $2" % [spec.name, res]
return false
else:
# compare as regular text
let path = base / spec.expOut
let want = readFile(path)
if want.replace("\x0D\x0A", "\n") != res:
echo "test $1: output wrong, have \n$2\nwant\n$3\n" %
[spec.name, res, want]
return false
return true
const
testSpec = [
TestSpec(
name : "Test exit (3) on bad config",
base : "testdata/1",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Frontier+1346", "",
),
output: T8nOutput(alloc: true, result: true),
expExitCode: ErrorConfig.int,
),
TestSpec(
name : "baseline test",
base : "testdata/1",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Byzantium", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "blockhash test",
base : "testdata/3",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json"
),
TestSpec(
name : "missing blockhash test",
base : "testdata/4",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "",
),
output: T8nOutput(alloc: true, result: true),
expExitCode: ErrorMissingBlockhash.int,
),
TestSpec(
name : "Uncle test",
base : "testdata/5",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Byzantium", "0x80",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Sign json transactions",
base : "testdata/13",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "",
),
output: T8nOutput(body: true),
expOut: "exp.json",
),
TestSpec(
name : "Already signed transactions",
base : "testdata/13",
input : t8nInput(
"alloc.json", "signed_txs.rlp", "env.json", "London", "",
),
output: T8nOutput(result: true),
expOut: "exp2.json",
),
TestSpec(
name : "Difficulty calculation - no uncles",
base : "testdata/14",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "",
),
output: T8nOutput(result: true),
expOut: "exp.json",
),
TestSpec(
name : "Difficulty calculation - with uncles",
base : "testdata/14",
input : t8nInput(
"alloc.json", "txs.json", "env.uncles.json", "London", "",
),
output: T8nOutput(result: true),
expOut: "exp2.json",
),
TestSpec(
name : "Difficulty calculation - with ommers + Berlin",
base : "testdata/14",
input : t8nInput(
"alloc.json", "txs.json", "env.uncles.json", "Berlin", "",
),
output: T8nOutput(result: true),
expOut: "exp_berlin.json",
),
TestSpec(
name : "Difficulty calculation on london",
base : "testdata/19",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "",
),
output: T8nOutput(result: true),
expOut: "exp_london.json",
),
TestSpec(
name : "Difficulty calculation on arrow glacier",
base : "testdata/19",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "ArrowGlacier", "",
),
output: T8nOutput(result: true),
expOut: "exp_arrowglacier.json",
),
TestSpec(
name : "Difficulty calculation on gray glacier",
base : "testdata/19",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "GrayGlacier", "",
),
output: T8nOutput(result: true),
expOut: "exp_grayglacier.json",
),
TestSpec(
name : "Sign unprotected (pre-EIP155) transaction",
base : "testdata/23",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "",
),
output: T8nOutput(result: true),
expOut: "exp.json",
),
TestSpec(
name : "Test post-merge transition",
base : "testdata/24",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Merge", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Test post-merge transition where input is missing random",
base : "testdata/24",
input : t8nInput(
"alloc.json", "txs.json", "env-missingrandom.json", "Merge", "",
),
output: T8nOutput(alloc: false, result: false),
expExitCode: ErrorConfig.int,
),
TestSpec(
name : "Test state-reward -1",
base : "testdata/3",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "-1"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "0-touch reward on pre EIP150 networks -1(txs.rlp)",
base : "testdata/00-501",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "EIP150", "-1"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "0-touch reward on pre EIP150 networks(txs.rlp)",
base : "testdata/00-502",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "EIP150", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "0-touch reward on pre EIP150 networks(txs.json)",
base : "testdata/00-502",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "EIP150", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "calculate basefee from parentBaseFee -1",
base : "testdata/00-503",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "-1"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "calculate basefee from parentBaseFee",
base : "testdata/00-504",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "BLOCKHASH opcode -1",
base : "testdata/00-505",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "-1"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "BLOCKHASH opcode",
base : "testdata/00-506",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "testOpcode 40 Berlin",
base : "testdata/00-507",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "2000000000000000000"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "suicideCoinbaseState Berlin",
base : "testdata/00-508",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "2000000000000000000"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "BLOCKHASH Bounds",
base : "testdata/00-509",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "2000000000000000000"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Suicides Mixing Coinbase",
base : "testdata/00-510",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "2000000000000000000"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Legacy Byzantium State Clearing",
base : "testdata/00-511",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Byzantium", "-1"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Test withdrawals transition",
base : "testdata/26",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Shanghai", ""
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Revert In Create In Init Create2",
base : "testdata/00-512",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Berlin", "0"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Revert In Create In Init",
base : "testdata/00-513",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Berlin", "0"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Init collision 3",
base : "testdata/00-514",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Berlin", "0"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Malicious withdrawals address",
base : "testdata/00-515",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Shanghai", "",
),
output: T8nOutput(alloc: false, result: false),
expExitCode: ErrorJson.int,
),
TestSpec(
name : "GasUsedHigherThanBlockGasLimitButNotWithRefundsSuicideLast_Frontier",
base : "testdata/00-516",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Frontier", "5000000000000000000"
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Cancun optional fields",
base : "testdata/00-517",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Blobhash list bounds",
base : "testdata/00-518",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "EVM tracer nil stack crash bug",
base : "testdata/00-519",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Shanghai", "0",
),
output: T8nOutput(trace: true),
expOut: "exp.txt",
),
TestSpec(
name : "EVM tracer wrong order for CALL family opcodes",
base : "testdata/00-520",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Merge", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "exp.txt",
),
TestSpec(
name : "EVM tracer CALL family exception",
base : "testdata/00-521",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Shanghai", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "exp.txt",
),
TestSpec(
name : "Cancun tests",
base : "testdata/28",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "More cancun tests",
base : "/testdata/29",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name : "Trace EIP-2929 Balance, Sload, ExtCodeSize, ExtCodeHash",
base : "testdata/00-522",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Berlin", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "exp.txt",
),
TestSpec(
name : "Trace Post EIP-2929 Balance, Sload, ExtCodeSize, ExtCodeHash",
base : "testdata/00-522",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "London", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "exp.txt",
),
TestSpec(
name : "Trace Pre EIP-2929 Balance, Sload, ExtCodeSize, ExtCodeHash",
base : "testdata/00-522",
input : t8nInput(
"alloc.json", "txs.json", "env.json", "Istanbul", "0",
),
output: T8nOutput(trace: true, result: true),
expOut: "istanbul.txt",
),
TestSpec(
name : "Blob gas used exceeds max allowance",
base : "testdata/00-523",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Cancun", "0",
),
output: T8nOutput(result: true),
expOut: "exp.json",
),
TestSpec(
name : "Calculate excessBlobGas if not supplied",
base : "testdata/00-524",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Cancun", "0",
),
output: T8nOutput(result: true),
expOut: "exp.json",
),
TestSpec(
name : "Different --state.chainid and tx.chainid",
base : "testdata/00-525",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Prague",
),
output: T8nOutput(result: true),
expOut: "exp1.json",
),
TestSpec(
name : "Prague execution requests",
base : "testdata/00-525",
input : t8nInput(
"alloc.json", "txs.rlp", "env.json", "Prague", "", "7078815900"
),
output: T8nOutput(result: true),
expOut: "exp2.json",
),
TestSpec(
name : "Prague depositContractAddress",
base : "testdata/00-525",
input : t8nInput(
"alloc.json", "txs.rlp", "env_dca.json", "Prague", "", "7078815900"
),
output: T8nOutput(result: true),
expOut: "exp3.json",
),
TestSpec(
name: "More cancun test, plus example of rlp-transaction that cannot be decoded properly",
base: "testdata/30",
input: t8nInput(
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
TestSpec(
name: "Prague test, EIP-7702 transaction",
base: "testdata/33",
input: t8nInput(
"alloc.json", "txs.json", "env.json", "Prague", "",
),
output: T8nOutput(alloc: true, result: true),
expOut: "exp.json",
),
]
proc main() =
suite "Transition tool (t8n) test suite":
let appDir = getAppDir()
for x in testSpec:
test x.name:
check runTest(appDir, x)
when isMainModule:
main()