2018-09-07 19:44:17 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 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
|
2020-01-10 11:18:36 +00:00
|
|
|
unittest2, strformat, strutils, tables, json, times, os, sets,
|
2019-11-13 14:49:39 +00:00
|
|
|
stew/ranges/typedranges, nimcrypto, options,
|
2019-03-18 03:05:24 +00:00
|
|
|
eth/[rlp, common], eth/trie/[db, trie_defs], chronicles,
|
2019-11-20 15:25:09 +00:00
|
|
|
./test_helpers, ./test_allowed_to_fail,
|
|
|
|
../nimbus/p2p/executor, test_config,
|
2019-11-13 14:49:39 +00:00
|
|
|
../nimbus/transaction,
|
2019-02-26 07:04:12 +00:00
|
|
|
../nimbus/[vm_state, vm_types, vm_state_transactions, utils],
|
2018-09-07 19:44:17 +00:00
|
|
|
../nimbus/vm/interpreter,
|
|
|
|
../nimbus/db/[db_chain, state_db]
|
|
|
|
|
2019-03-18 01:55:02 +00:00
|
|
|
type
|
|
|
|
Tester = object
|
|
|
|
name: string
|
|
|
|
header: BlockHeader
|
|
|
|
pre: JsonNode
|
|
|
|
tx: Transaction
|
|
|
|
expectedHash: string
|
|
|
|
expectedLogs: string
|
|
|
|
fork: Fork
|
|
|
|
debugMode: bool
|
2019-08-19 14:12:32 +00:00
|
|
|
trace: bool
|
2019-03-21 09:01:26 +00:00
|
|
|
index: int
|
2019-02-28 08:22:07 +00:00
|
|
|
|
2019-03-18 14:18:04 +00:00
|
|
|
GST_VMState = ref object of BaseVMState
|
|
|
|
|
|
|
|
proc toBytes(x: string): seq[byte] =
|
|
|
|
result = newSeq[byte](x.len)
|
|
|
|
for i in 0..<x.len: result[i] = x[i].byte
|
|
|
|
|
|
|
|
proc newGST_VMState(prevStateRoot: Hash256, header: BlockHeader, chainDB: BaseChainDB, tracerFlags: set[TracerFlags]): GST_VMState =
|
|
|
|
new result
|
|
|
|
result.init(prevStateRoot, header, chainDB, tracerFlags)
|
|
|
|
|
|
|
|
method getAncestorHash*(vmState: GST_VMState, blockNumber: BlockNumber): Hash256 {.gcsafe.} =
|
|
|
|
if blockNumber >= vmState.blockNumber:
|
|
|
|
return
|
|
|
|
elif blockNumber < 0:
|
|
|
|
return
|
|
|
|
elif blockNumber < vmState.blockNumber - 256:
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
return keccakHash(toBytes($blockNumber))
|
|
|
|
|
2019-03-18 01:55:02 +00:00
|
|
|
proc dumpAccount(accountDb: ReadOnlyStateDB, address: EthAddress, name: string): JsonNode =
|
|
|
|
result = %{
|
|
|
|
"name": %name,
|
|
|
|
"address": %($address),
|
|
|
|
"nonce": %toHex(accountDb.getNonce(address)),
|
|
|
|
"balance": %accountDb.getBalance(address).toHex(),
|
|
|
|
"codehash": %($accountDb.getCodeHash(address)),
|
|
|
|
"storageRoot": %($accountDb.getStorageRoot(address))
|
|
|
|
}
|
2018-09-07 19:44:17 +00:00
|
|
|
|
2019-03-21 09:01:26 +00:00
|
|
|
proc dumpDebugData(tester: Tester, vmState: BaseVMState, sender: EthAddress, gasUsed: GasInt, success: bool) =
|
2019-03-18 01:55:02 +00:00
|
|
|
let recipient = tester.tx.getRecipient()
|
|
|
|
let miner = tester.header.coinbase
|
|
|
|
var accounts = newJObject()
|
2018-09-07 19:44:17 +00:00
|
|
|
|
2019-03-18 01:55:02 +00:00
|
|
|
accounts[$miner] = dumpAccount(vmState.readOnlyStateDB, miner, "miner")
|
|
|
|
accounts[$sender] = dumpAccount(vmState.readOnlyStateDB, sender, "sender")
|
|
|
|
accounts[$recipient] = dumpAccount(vmState.readOnlyStateDB, recipient, "recipient")
|
|
|
|
|
|
|
|
let accountList = [sender, miner, recipient]
|
|
|
|
var i = 0
|
|
|
|
for ac, _ in tester.pre:
|
|
|
|
let account = ethAddressFromHex(ac)
|
|
|
|
if account notin accountList:
|
|
|
|
accounts[$account] = dumpAccount(vmState.readOnlyStateDB, account, "pre" & $i)
|
|
|
|
inc i
|
|
|
|
|
2019-08-19 14:12:32 +00:00
|
|
|
let tracingResult = if tester.trace: vmState.getTracingResult() else: %[]
|
2019-03-18 01:55:02 +00:00
|
|
|
let debugData = %{
|
|
|
|
"gasUsed": %gasUsed,
|
2019-08-19 14:12:32 +00:00
|
|
|
"structLogs": tracingResult,
|
2019-03-18 01:55:02 +00:00
|
|
|
"accounts": accounts
|
|
|
|
}
|
2019-03-21 09:01:26 +00:00
|
|
|
let status = if success: "_success" else: "_failed"
|
|
|
|
writeFile("debug_" & tester.name & "_" & $tester.index & status & ".json", debugData.pretty())
|
2019-03-18 01:55:02 +00:00
|
|
|
|
|
|
|
proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
|
2019-08-19 14:12:32 +00:00
|
|
|
var tracerFlags: set[TracerFlags] = if tester.trace: {TracerFlags.EnableTracing} else : {}
|
2019-11-28 10:02:11 +00:00
|
|
|
|
|
|
|
# TODO: do we need another test with pruneTrie = false?
|
|
|
|
var chainDB = newBaseChainDB(newMemoryDb(), pruneTrie = true)
|
|
|
|
var vmState = newGST_VMState(emptyRlpHash, tester.header, chainDB, tracerFlags)
|
2019-03-21 09:01:26 +00:00
|
|
|
var gasUsed: GasInt
|
|
|
|
let sender = tester.tx.getSender()
|
|
|
|
|
2018-09-19 16:46:14 +00:00
|
|
|
vmState.mutateStateDB:
|
2019-03-18 01:55:02 +00:00
|
|
|
setupStateDB(tester.pre, db)
|
2018-09-19 16:46:14 +00:00
|
|
|
|
2019-11-19 06:12:13 +00:00
|
|
|
vmState.accountDB.updateOriginalRoot()
|
|
|
|
|
2018-09-19 16:46:14 +00:00
|
|
|
defer:
|
2018-11-28 19:02:21 +00:00
|
|
|
let obtainedHash = "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii
|
2019-03-18 01:55:02 +00:00
|
|
|
check obtainedHash == tester.expectedHash
|
2019-02-28 08:22:07 +00:00
|
|
|
let logEntries = vmState.getAndClearLogEntries()
|
|
|
|
let actualLogsHash = hashLogEntries(logEntries)
|
2019-03-18 01:55:02 +00:00
|
|
|
let expectedLogsHash = toLowerAscii(tester.expectedLogs)
|
2019-02-28 08:22:07 +00:00
|
|
|
check(expectedLogsHash == actualLogsHash)
|
2019-03-21 09:01:26 +00:00
|
|
|
if tester.debugMode:
|
|
|
|
let success = expectedLogsHash == actualLogsHash and obtainedHash == tester.expectedHash
|
|
|
|
tester.dumpDebugData(vmState, sender, gasUsed, success)
|
2018-09-19 16:46:14 +00:00
|
|
|
|
2019-08-26 15:19:18 +00:00
|
|
|
if not validateTransaction(vmState, tester.tx, sender, tester.fork):
|
2018-09-19 16:46:14 +00:00
|
|
|
vmState.mutateStateDB:
|
|
|
|
# pre-EIP158 (e.g., Byzantium) should ensure currentCoinbase exists
|
|
|
|
# in later forks, don't create at all
|
2019-03-18 01:55:02 +00:00
|
|
|
db.addBalance(tester.header.coinbase, 0.u256)
|
2019-04-18 00:56:57 +00:00
|
|
|
|
|
|
|
# TODO: this feels not right to be here
|
|
|
|
# perhaps the entire validateTransaction block
|
|
|
|
# should be moved into processTransaction
|
|
|
|
if tester.fork >= FkSpurious:
|
|
|
|
let miner = tester.header.coinbase
|
2019-11-13 14:49:39 +00:00
|
|
|
let touchedAccounts = [miner]
|
2019-04-18 00:56:57 +00:00
|
|
|
for account in touchedAccounts:
|
|
|
|
debug "state clearing", account
|
|
|
|
if db.accountExists(account) and db.isEmptyAccount(account):
|
|
|
|
db.deleteAccount(account)
|
|
|
|
|
2018-09-19 16:46:14 +00:00
|
|
|
return
|
|
|
|
|
2019-04-23 12:50:45 +00:00
|
|
|
gasUsed = tester.tx.processTransaction(sender, vmState, tester.fork)
|
2018-09-18 00:35:41 +00:00
|
|
|
|
2020-01-10 11:18:36 +00:00
|
|
|
# This is necessary due to the manner in which the state tests are
|
|
|
|
# generated. State tests are generated from the BlockChainTest tests
|
|
|
|
# in which these transactions are included in the larger context of a
|
|
|
|
# block and thus, the mechanisms which would touch/create/clear the
|
|
|
|
# coinbase account based on the mining reward are present during test
|
|
|
|
# generation, but not part of the execution, thus we must artificially
|
|
|
|
# create the account in VMs prior to the state clearing rules,
|
|
|
|
# as well as conditionally cleaning up the coinbase account when left
|
|
|
|
# empty in VMs after the state clearing rules came into effect.
|
|
|
|
let miner = tester.header.coinbase
|
|
|
|
if miner in vmState.suicides:
|
|
|
|
vmState.mutateStateDB:
|
|
|
|
db.addBalance(miner, 0.u256)
|
|
|
|
if tester.fork >= FkSpurious:
|
|
|
|
if db.isEmptyAccount(miner):
|
|
|
|
db.deleteAccount(miner)
|
|
|
|
|
2019-03-18 03:05:24 +00:00
|
|
|
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus,
|
2019-08-19 14:12:32 +00:00
|
|
|
trace = false, debugMode = false, supportedForks: set[Fork] = supportedForks) =
|
2019-03-18 01:55:02 +00:00
|
|
|
var tester: Tester
|
2018-09-19 16:46:14 +00:00
|
|
|
var fixture: JsonNode
|
|
|
|
for label, child in fixtures:
|
|
|
|
fixture = child
|
2019-03-18 01:55:02 +00:00
|
|
|
tester.name = label
|
2018-09-19 16:46:14 +00:00
|
|
|
break
|
2018-09-07 19:44:17 +00:00
|
|
|
|
2018-09-19 16:46:14 +00:00
|
|
|
let fenv = fixture["env"]
|
2019-03-18 01:55:02 +00:00
|
|
|
tester.header = BlockHeader(
|
2018-09-19 16:46:14 +00:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2019-03-21 09:01:26 +00:00
|
|
|
let specifyIndex = getConfiguration().index
|
2019-08-19 14:12:32 +00:00
|
|
|
tester.trace = trace
|
2019-03-18 01:55:02 +00:00
|
|
|
tester.debugMode = debugMode
|
2018-09-19 16:46:14 +00:00
|
|
|
let ftrans = fixture["transaction"]
|
2019-03-18 03:05:24 +00:00
|
|
|
var testedInFork = false
|
2018-11-28 19:02:21 +00:00
|
|
|
for fork in supportedForks:
|
2019-03-21 14:35:25 +00:00
|
|
|
if fixture["post"].hasKey(forkNames[fork]):
|
2018-11-28 19:02:21 +00:00
|
|
|
for expectation in fixture["post"][forkNames[fork]]:
|
2019-03-21 09:01:26 +00:00
|
|
|
inc tester.index
|
2019-03-21 14:35:25 +00:00
|
|
|
if specifyIndex > 0 and tester.index != specifyIndex:
|
2019-03-21 09:01:26 +00:00
|
|
|
continue
|
|
|
|
testedInFork = true
|
2019-03-18 01:55:02 +00:00
|
|
|
tester.expectedHash = expectation["hash"].getStr
|
|
|
|
tester.expectedLogs = expectation["logs"].getStr
|
2018-11-28 19:02:21 +00:00
|
|
|
let
|
|
|
|
indexes = expectation["indexes"]
|
|
|
|
dataIndex = indexes["data"].getInt
|
|
|
|
gasIndex = indexes["gas"].getInt
|
|
|
|
valueIndex = indexes["value"].getInt
|
2019-03-18 01:55:02 +00:00
|
|
|
tester.tx = ftrans.getFixtureTransaction(dataIndex, gasIndex, valueIndex)
|
|
|
|
tester.pre = fixture["pre"]
|
|
|
|
tester.fork = fork
|
|
|
|
testFixtureIndexes(tester, testStatusIMPL)
|
|
|
|
|
2019-03-18 03:05:24 +00:00
|
|
|
if not testedInFork:
|
|
|
|
echo "test subject '", tester.name, "' not tested in any forks"
|
|
|
|
|
2019-09-21 05:45:23 +00:00
|
|
|
proc generalStateJsonMain*(debugMode = false) =
|
|
|
|
if paramCount() == 0 or not debugMode:
|
2019-03-18 01:55:02 +00:00
|
|
|
# run all test fixtures
|
2019-12-09 10:08:41 +00:00
|
|
|
suite "generalstate json tests":
|
|
|
|
jsonTest("GeneralStateTests", testFixture, skipGSTTests)
|
2019-11-13 11:39:35 +00:00
|
|
|
suite "new generalstate json tests":
|
2019-11-20 15:25:09 +00:00
|
|
|
jsonTest("newGeneralStateTests", testFixture, skipNewGSTTests)
|
2019-03-18 01:55:02 +00:00
|
|
|
else:
|
|
|
|
# execute single test in debug mode
|
2019-03-18 03:05:24 +00:00
|
|
|
let config = getConfiguration()
|
|
|
|
if config.testSubject.len == 0:
|
|
|
|
echo "missing test subject"
|
|
|
|
quit(QuitFailure)
|
|
|
|
|
2019-11-14 15:37:58 +00:00
|
|
|
let path = "tests" / "fixtures" / "newGeneralStateTests"
|
2019-03-18 03:05:24 +00:00
|
|
|
let n = json.parseFile(path / config.testSubject)
|
2019-03-18 01:55:02 +00:00
|
|
|
var testStatusIMPL: TestStatus
|
2019-03-18 03:05:24 +00:00
|
|
|
var forks: set[Fork] = {}
|
|
|
|
forks.incl config.fork
|
2019-08-19 14:12:32 +00:00
|
|
|
testFixture(n, testStatusIMPL, config.trace, true, forks)
|
2019-03-18 03:05:24 +00:00
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
var message: string
|
|
|
|
|
|
|
|
## Processing command line arguments
|
|
|
|
if processArguments(message) != Success:
|
|
|
|
echo message
|
|
|
|
quit(QuitFailure)
|
|
|
|
else:
|
|
|
|
if len(message) > 0:
|
|
|
|
echo message
|
|
|
|
quit(QuitSuccess)
|
2019-09-21 05:45:23 +00:00
|
|
|
generalStateJsonMain(true)
|