nimbus-eth1/nimbus/tracer.nim
Jordan Hrycaj 221e6c9e2f
Unified database frontend integration (#1670)
* Nimbus folder environment update

details:
* Integrated `CoreDbRef` for the sources in the `nimbus` sub-folder.
* The `nimbus` program does not compile yet as it needs the updates
  in the parallel `stateless` sub-folder.

* Stateless environment update

details:
* Integrated `CoreDbRef` for the sources in the `stateless` sub-folder.
* The `nimbus` program compiles now.

* Premix environment update

details:
* Integrated `CoreDbRef` for the sources in the `premix` sub-folder.

* Fluffy environment update

details:
* Integrated `CoreDbRef` for the sources in the `fluffy` sub-folder.

* Tools environment update

details:
* Integrated `CoreDbRef` for the sources in the `tools` sub-folder.

* Nodocker environment update

details:
* Integrated `CoreDbRef` for the sources in the
  `hive_integration/nodocker` sub-folder.

* Tests environment update

details:
* Integrated `CoreDbRef` for the sources in the `tests` sub-folder.
* The unit tests compile and run cleanly now.

* Generalise `CoreDbRef` to any `select_backend` supported database

why:
  Generalisation was just missed due to overcoming some compiler oddity
  which was tied to rocksdb for testing.

* Suppress compiler warning for `newChainDB()`

why:
  Warning was added to this function which must be wrapped so that
  any `CatchableError` is re-raised as `Defect`.

* Split off persistent `CoreDbRef` constructor into separate file

why:
  This allows to compile a memory only database version without linking
  the backend library.

* Use memory `CoreDbRef` database by default

detail:
 Persistent DB constructor needs to import `db/core_db/persistent

why:
 Most tests use memory DB anyway. This avoids linking `-lrocksdb` or
 any other backend by default.

* fix `toLegacyBackend()` availability check

why:
  got garbled after memory/persistent split.

* Clarify raw access to MPT for snap sync handler

why:
  Logically, `kvt` is not the raw access for the hexary trie (although
  this holds for the legacy database)
2023-08-04 12:10:09 +01:00

279 lines
9.0 KiB
Nim

import
std/[strutils, json],
./common/common,
./db/[core_db, accounts_cache],
./utils/utils,
./evm/tracer/legacy_tracer,
"."/[constants, vm_state, vm_types, transaction, core/executor],
nimcrypto/utils as ncrutils,
./rpc/hexstrings, ./launcher,
results
when defined(geth):
import db/geth_db
proc getParentHeader(db: CoreDbRef, header: BlockHeader): BlockHeader =
db.blockHeader(header.blockNumber.truncate(uint64) - 1)
else:
proc getParentHeader(self: CoreDbRef, header: BlockHeader): BlockHeader =
self.getBlockHeader(header.parentHash)
proc `%`(x: openArray[byte]): JsonNode =
result = %toHex(x, false)
proc toJson(receipt: Receipt): JsonNode =
result = newJObject()
result["cumulativeGasUsed"] = %receipt.cumulativeGasUsed
result["bloom"] = %receipt.bloom
result["logs"] = %receipt.logs
if receipt.hasStateRoot:
result["root"] = %($receipt.stateRoot)
else:
result["status"] = %receipt.status
proc dumpReceipts*(chainDB: CoreDbRef, header: BlockHeader): JsonNode =
result = newJArray()
for receipt in chainDB.getReceipts(header.receiptRoot):
result.add receipt.toJson
proc toJson*(receipts: seq[Receipt]): JsonNode =
result = newJArray()
for receipt in receipts:
result.add receipt.toJson
proc captureAccount(n: JsonNode, db: AccountsCache, address: EthAddress, name: string) =
var jaccount = newJObject()
jaccount["name"] = %name
jaccount["address"] = %("0x" & $address)
let nonce = db.getNonce(address)
let balance = db.getBalance(address)
let codeHash = db.getCodeHash(address)
let storageRoot = db.getStorageRoot(address)
jaccount["nonce"] = %(encodeQuantity(nonce).string.toLowerAscii)
jaccount["balance"] = %("0x" & balance.toHex)
let code = db.getCode(address)
jaccount["codeHash"] = %("0x" & ($codeHash).toLowerAscii)
jaccount["code"] = %("0x" & toHex(code, true))
jaccount["storageRoot"] = %("0x" & ($storageRoot).toLowerAscii)
var storage = newJObject()
for key, value in db.storage(address):
storage["0x" & key.dumpHex] = %("0x" & value.dumpHex)
jaccount["storage"] = storage
n.add jaccount
proc dumpMemoryDB*(node: JsonNode, db: CoreDbRef) =
var n = newJObject()
for k, v in db.kvt:
n[k.toHex(false)] = %v
node["state"] = n
proc dumpMemoryDB*(node: JsonNode, capture: CoreDbCaptRef) =
node.dumpMemoryDB capture.recorder
const
senderName = "sender"
recipientName = "recipient"
minerName = "miner"
uncleName = "uncle"
internalTxName = "internalTx"
proc traceTransaction*(com: CommonRef, header: BlockHeader,
body: BlockBody, txIndex: int, tracerFlags: set[TracerFlags] = {}): JsonNode =
let
# parent = com.db.getParentHeader(header) -- notused
# we add a memory layer between backend/lower layer db
# and capture state db snapshot during transaction execution
capture = com.db.capture()
tracerInst = newLegacyTracer(tracerFlags)
captureCom = com.clone(capture.recorder)
vmState = BaseVMState.new(header, captureCom)
var stateDb = vmState.stateDB
if header.txRoot == EMPTY_ROOT_HASH: return newJNull()
doAssert(body.transactions.calcTxRoot == header.txRoot)
doAssert(body.transactions.len != 0)
var
gasUsed: GasInt
before = newJArray()
after = newJArray()
stateDiff = %{"before": before, "after": after}
beforeRoot: Hash256
let
miner = vmState.coinbase()
for idx, tx in body.transactions:
let sender = tx.getSender
let recipient = tx.getRecipient(sender)
if idx == txIndex:
vmState.tracer = tracerInst # only enable tracer on target tx
before.captureAccount(stateDb, sender, senderName)
before.captureAccount(stateDb, recipient, recipientName)
before.captureAccount(stateDb, miner, minerName)
stateDb.persist()
stateDiff["beforeRoot"] = %($stateDb.rootHash)
beforeRoot = stateDb.rootHash
let rc = vmState.processTransaction(tx, sender, header)
gasUsed = if rc.isOk: rc.value else: 0
if idx == txIndex:
after.captureAccount(stateDb, sender, senderName)
after.captureAccount(stateDb, recipient, recipientName)
after.captureAccount(stateDb, miner, minerName)
tracerInst.removeTracedAccounts(sender, recipient, miner)
stateDb.persist()
stateDiff["afterRoot"] = %($stateDb.rootHash)
break
# internal transactions:
var stateBefore = AccountsCache.init(capture.recorder, beforeRoot, com.pruneTrie)
for idx, acc in tracedAccountsPairs(tracerInst):
before.captureAccount(stateBefore, acc, internalTxName & $idx)
for idx, acc in tracedAccountsPairs(tracerInst):
after.captureAccount(stateDb, acc, internalTxName & $idx)
result = tracerInst.getTracingResult()
result["gas"] = %gasUsed
if TracerFlags.DisableStateDiff notin tracerFlags:
result["stateDiff"] = stateDiff
# now we dump captured state db
if TracerFlags.DisableState notin tracerFlags:
result.dumpMemoryDB(capture)
proc dumpBlockState*(com: CommonRef, header: BlockHeader, body: BlockBody, dumpState = false): JsonNode =
let
parent = com.db.getParentHeader(header)
capture = com.db.capture()
captureCom = com.clone(capture.recorder)
# we only need stack dump if we want to scan for internal transaction address
captureFlags = {DisableMemory, DisableStorage, EnableAccount}
tracerInst = newLegacyTracer(captureFlags)
vmState = BaseVMState.new(header, captureCom, tracerInst)
miner = vmState.coinbase()
var
before = newJArray()
after = newJArray()
stateBefore = AccountsCache.init(capture.recorder, parent.stateRoot, com.pruneTrie)
for idx, tx in body.transactions:
let sender = tx.getSender
let recipient = tx.getRecipient(sender)
before.captureAccount(stateBefore, sender, senderName & $idx)
before.captureAccount(stateBefore, recipient, recipientName & $idx)
before.captureAccount(stateBefore, miner, minerName)
for idx, uncle in body.uncles:
before.captureAccount(stateBefore, uncle.coinbase, uncleName & $idx)
discard vmState.processBlockNotPoA(header, body)
var stateAfter = vmState.stateDB
for idx, tx in body.transactions:
let sender = tx.getSender
let recipient = tx.getRecipient(sender)
after.captureAccount(stateAfter, sender, senderName & $idx)
after.captureAccount(stateAfter, recipient, recipientName & $idx)
tracerInst.removeTracedAccounts(sender, recipient)
after.captureAccount(stateAfter, miner, minerName)
tracerInst.removeTracedAccounts(miner)
for idx, uncle in body.uncles:
after.captureAccount(stateAfter, uncle.coinbase, uncleName & $idx)
tracerInst.removeTracedAccounts(uncle.coinbase)
# internal transactions:
for idx, acc in tracedAccountsPairs(tracerInst):
before.captureAccount(stateBefore, acc, internalTxName & $idx)
for idx, acc in tracedAccountsPairs(tracerInst):
after.captureAccount(stateAfter, acc, internalTxName & $idx)
result = %{"before": before, "after": after}
if dumpState:
result.dumpMemoryDB(capture)
proc traceBlock*(com: CommonRef, header: BlockHeader, body: BlockBody, tracerFlags: set[TracerFlags] = {}): JsonNode =
let
# parent = com.db.getParentHeader(header) -- notused
capture = com.db.capture()
captureCom = com.clone(capture.recorder)
tracerInst = newLegacyTracer(tracerFlags)
vmState = BaseVMState.new(header, captureCom, tracerInst)
if header.txRoot == EMPTY_ROOT_HASH: return newJNull()
doAssert(body.transactions.calcTxRoot == header.txRoot)
doAssert(body.transactions.len != 0)
var gasUsed = GasInt(0)
for tx in body.transactions:
let
sender = tx.getSender
rc = vmState.processTransaction(tx, sender, header)
if rc.isOk:
gasUsed = gasUsed + rc.value
result = tracerInst.getTracingResult()
result["gas"] = %gasUsed
if TracerFlags.DisableState notin tracerFlags:
result.dumpMemoryDB(capture)
proc traceTransactions*(com: CommonRef, header: BlockHeader, blockBody: BlockBody): JsonNode =
result = newJArray()
for i in 0 ..< blockBody.transactions.len:
result.add traceTransaction(com, header, blockBody, i, {DisableState})
proc dumpDebuggingMetaData*(com: CommonRef, header: BlockHeader,
blockBody: BlockBody, vmState: BaseVMState, launchDebugger = true) =
let
blockNumber = header.blockNumber
var
capture = com.db.capture()
captureCom = com.clone(capture.recorder)
bloom = createBloom(vmState.receipts)
let blockSummary = %{
"receiptsRoot": %("0x" & toHex(calcReceiptRoot(vmState.receipts).data)),
"stateRoot": %("0x" & toHex(vmState.stateDB.rootHash.data)),
"logsBloom": %("0x" & toHex(bloom))
}
var metaData = %{
"blockNumber": %blockNumber.toHex,
"txTraces": traceTransactions(captureCom, header, blockBody),
"stateDump": dumpBlockState(captureCom, header, blockBody),
"blockTrace": traceBlock(captureCom, header, blockBody, {DisableState}),
"receipts": toJson(vmState.receipts),
"block": blockSummary
}
metaData.dumpMemoryDB(capture)
let jsonFileName = "debug" & $blockNumber & ".json"
if launchDebugger:
launchPremix(jsonFileName, metaData)
else:
writeFile(jsonFileName, metaData.pretty())