mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
786263c0b8
* CoreDB: Re-org API details: Legacy API internally uses vertex ID for root node abstraction * Cosmetics: Move some unit test helpers to common sub-directory * Extract constant from `accouns_cache.nim` => `constants.nim` * Fix tracer methods why: Logger dump data were wrongly dumped from the production database. This caused an assert exception when iterating over the persistent database (instead of the memory logger.) This event in turn was enabled after fixing another inconsistency which just set up an empty iterator. Unit tests failed to detect that.
279 lines
9.0 KiB
Nim
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.logDb
|
|
|
|
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.processBlock(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())
|