Revive json tracer unit tests (#2538)

* Some `Aristo` clean-ups/updates

* Re-implemented core-db tracer functionality

* Rename nimbus tracer `no-tracer.nim` => `tracer.nim`

why:
  Restore original name for easy diff tracking with upcoming update

* Update nimbus tracer using new core-db tracer functionality

* Updating json tracer unit tests

* Enable json tracer unit tests
This commit is contained in:
Jordan Hrycaj 2024-08-01 10:41:20 +00:00 committed by GitHub
parent e331c9e9b7
commit 01b5c08763
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1445 additions and 224 deletions

View File

@ -14,3 +14,5 @@
of proof nodes is rather small. Also, a right boundary leaf node is
typically cleared. This needs to be re-checked when writing the `proof`
function mentioned above.
* `aristo_nearby` also qualifies for a re-write, now

View File

@ -559,7 +559,7 @@ proc pp*(
): string =
sTab.ppXTab(db.orDefault)
proc pp*(root: VertexID, leg: Leg; db = AristoDbRef(nil)): string =
proc pp*(leg: Leg; root: VertexID; db = AristoDbRef(nil)): string =
let db = db.orDefault()
result = "(" & leg.wp.vid.ppVid & ","
block:
@ -583,7 +583,7 @@ proc pp*(hike: Hike; db = AristoDbRef(nil); indent = 4): string =
else:
if hike.legs[0].wp.vid != hike.root:
result &= "(" & hike.root.ppVid & ")" & pfx
result &= hike.legs.mapIt(pp(hike.root, it, db)).join(pfx)
result &= hike.legs.mapIt(it.pp(hike.root, db)).join(pfx)
result &= pfx & "(" & hike.tail.ppPathPfx & ")"
result &= "]"

View File

@ -152,8 +152,10 @@ type
# Part/proof node errors
PartArgNotInCore
PartArgNotGenericRoot
PartArgRootAlreadyUsed
PartArgRootAlreadyOnDatabase
PartChkChangedKeyNotInKeyTab
PartChkChangedVtxMissing
PartChkCoreKeyLookupFailed
@ -188,7 +190,6 @@ type
PartRlpPayloadException
PartRootKeysDontMatch
PartRootVidsDontMatch
PartRootAlreadyOnDatabase
PartVtxSlotWasModified
PartVtxSlotWasNotModified

View File

@ -329,6 +329,11 @@ func to*(n: UInt256; T: type PathID): T =
## Representation of a scalar as `PathID` (preserving full information)
T(pfx: n, length: 64)
func to*(a: PathID; T: type UInt256): T =
if not a.pfx.isZero:
assert a.length < 64 # debugging only
result = a.pfx shr (4 * (64 - a.length))
# ------------------------------------------------------------------------------
# Public helpers: Miscellaneous mappings
# ------------------------------------------------------------------------------

View File

@ -151,11 +151,11 @@ proc zeroAdjust(
if 0 < hike.legs.len:
return ok(hike)
let root = db.getVtx (hike.root, hike.root)
if root.isValid:
let rootVtx = db.getVtx (hike.root, hike.root)
if rootVtx.isValid:
block fail:
var pfx: NibblesBuf
case root.vType:
case rootVtx.vType:
of Branch:
# Find first non-dangling link and assign it
let nibbleID = block:
@ -166,18 +166,28 @@ proc zeroAdjust(
if hike.tail.len == 0:
break fail
hike.tail[0].int8
let n = root.branchBorderNibble nibbleID
let n = rootVtx.branchBorderNibble nibbleID
if n < 0:
# Before or after the database range
return err((hike.root,NearbyBeyondRange))
pfx = root.ePfx & NibblesBuf.nibble(n.byte)
pfx = rootVtx.ePfx & NibblesBuf.nibble(n.byte)
of Leaf:
pfx = root.lPfx
pfx = rootVtx.lPfx
if not hike.accept pfx:
# Before or after the database range
return err((hike.root,NearbyBeyondRange))
# Pathological case: matching `rootVtx` which is a leaf
if hike.legs.len == 0 and hike.tail.len == 0:
return ok(Hike(
root: hike.root,
legs: @[Leg(
nibble: -1,
wp: VidVtxPair(
vid: hike.root,
vtx: rootVtx))]))
var newHike = pfx.toHike(hike.root, db)
if 0 < newHike.legs.len:
return ok(newHike)
@ -268,10 +278,6 @@ proc nearbyNext(
# Some easy cases
let hike = ? hike.zeroAdjust(db, doLeast=moveRight)
# if hike.legs[^1].wp.vtx.vType == Extension:
# let vid = hike.legs[^1].wp.vtx.eVid
# return hike.complete(vid, db, hikeLenMax, doLeast=moveRight)
var
uHike = hike
start = true

View File

@ -159,6 +159,16 @@ proc partPut*(
ok()
proc partGetSubTree*(ps: PartStateRef; rootHash: Hash256): VertexID =
## For the argument `roothash` retrieve the root vertex ID of a particular
## sub tree from the partial state descriptor argument `ps`. The function
## returns `VertexID(0)` if there is no match.
##
for vid in ps.core.keys:
if ps[vid].to(Hash256) == rootHash:
return vid
proc partReRoot*(
ps: PartStateRef;
frRoot: VertexID;
@ -166,9 +176,11 @@ proc partReRoot*(
): Result[void,AristoError] =
## Realign a generic root vertex (i.e `$2`..`$(LEAST_FREE_VID-1)`) for a
## `proof` state to a new root vertex.
if frRoot notin ps.core or frRoot == toRoot:
if frRoot == toRoot:
return ok() # nothing to do
if frRoot notin ps.core:
return err(PartArgNotInCore)
if frRoot < VertexID(2) or LEAST_FREE_VID <= frRoot.ord or
toRoot < VertexID(2) or LEAST_FREE_VID <= toRoot.ord:
return err(PartArgNotGenericRoot)
@ -176,7 +188,7 @@ proc partReRoot*(
if toRoot in ps.core:
return err(PartArgRootAlreadyUsed)
if ps.db.getVtx((toRoot,toRoot)).isValid:
return err(PartRootAlreadyOnDatabase)
return err(PartArgRootAlreadyOnDatabase)
# Migrate
for key in ps.byKey.keys:

View File

@ -128,7 +128,7 @@ proc pp*(
let
pfx0 = indent.toPfx()
pfx1 = indent.toPfx(1)
pfx2 = indent.toPfx(2)
var pfx = ""
if dbOk:
result &= pfx & "<db>" & pfx1 & ps.db.pp(
@ -147,9 +147,10 @@ proc pp*(
if 0 < len:
var qfx = ""
result &= pfx1 & "{"
for (vid,vLst) in ps.core.pairs:
for vid in ps.core.keys.toSeq.sorted:
let vLst = ps.core.getOrDefault vid
result &= qfx & "(" & vid.pp & ":" & vLst.pp(ps) & ")"
qfx = pfx1
qfx = pfx2
result &= "}"
pfx = pfx0
if byKeyOk:

View File

@ -11,14 +11,16 @@
{.push raises: [].}
import
eth/common,
../../aristo as use_ari,
../../aristo/aristo_desc/desc_identifiers,
../../aristo/[aristo_init/memory_only, aristo_walk],
../../kvt as use_kvt,
../../kvt/[kvt_init/memory_only, kvt_walk],
../base/[base_config, base_desc, base_helpers]
# ------------------------------------------------------------------------------
# Public constructor and helper
# Public constructors
# ------------------------------------------------------------------------------
proc create*(dbType: CoreDbType; kvt: KvtDbRef; mpt: AristoDbRef): CoreDbRef =
@ -51,6 +53,41 @@ proc newAristoVoidCoreDbRef*(): CoreDbRef =
KvtDbRef.init(use_kvt.VoidBackendRef),
AristoDbRef.init(use_ari.VoidBackendRef))
proc newCtxByKey*(
ctx: CoreDbCtxRef;
key: Hash256;
info: static[string];
): CoreDbRc[CoreDbCtxRef] =
const
rvid: RootedVertexID = (VertexID(1),VertexID(1))
let
db = ctx.parent
# Find `(vid,key)` on transaction stack
inx = block:
let rc = db.ariApi.call(findTx, ctx.mpt, rvid, key.to(HashKey))
if rc.isErr:
return err(rc.error.toError info)
rc.value
# Fork MPT descriptor that provides `(vid,key)`
newMpt = block:
let rc = db.ariApi.call(forkTx, ctx.mpt, inx)
if rc.isErr:
return err(rc.error.toError info)
rc.value
# Fork KVT descriptor parallel to `newMpt`
newKvt = block:
let rc = db.kvtApi.call(forkTx, ctx.kvt, inx)
if rc.isErr:
discard db.ariApi.call(forget, newMpt)
return err(rc.error.toError info)
rc.value
# Create new context
ok(db.bless CoreDbCtxRef(kvt: newKvt, mpt: newMpt))
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -0,0 +1,956 @@
# Nimbus
# Copyright (c) 2023-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.
##
## Database Backend Tracer
## =======================
##
{.push raises: [].}
import
std/[sequtils, tables, typetraits],
eth/common,
results,
../../aristo as use_aristo,
../../aristo/aristo_desc,
../../kvt as use_kvt,
../../kvt/kvt_desc,
../base/[base_config, base_desc]
const
LogJournalMax = 1_000_000
## Maximal size of a journal (organised as LRU)
type
TracePfx = enum
TrpOops = 0
TrpKvt
TrpAccounts
TrpGeneric
TrpStorage
TraceRequest* = enum
TrqOops = 0
TrqFind
TrqAdd
TrqModify
TrqDelete
TraceDataType* = enum
TdtOops = 0
TdtBlob ## Kvt and Aristo
TdtError ## Kvt and Aristo
TdtVoid ## Kvt and Aristo
TdtAccount ## Aristo only
TdtBigNum ## Aristo only
TdtHash ## Aristo only
TraceDataItemRef* = ref object
## Log journal entry
pfx*: TracePfx ## DB storage prefix
info*: int ## `KvtApiProfNames` or `AristoApiProfNames`
req*: TraceRequest ## Logged action request
case kind*: TraceDataType
of TdtBlob:
blob*: Blob
of TdtError:
error*: int ## `KvtError` or `AristoError`
of TdtAccount:
account*: AristoAccount
of TdtBigNum:
bigNum*: UInt256
of TdtHash:
hash*: Hash256
of TdtVoid, TdtOops:
discard
TraceLogInstRef* = ref object
## Logger instance
base: TraceRecorderRef
level: int
truncated: bool
journal: KeyedQueue[Blob,TraceDataItemRef]
TraceRecorderRef* = ref object of RootRef
log: seq[TraceLogInstRef] ## Production stack for log database
db: CoreDbRef
kvtSave: KvtApiRef ## Restore `KVT` data
ariSave: AristoApiRef ## Restore `Aristo` data
doAssert LEAST_FREE_VID <= 256 # needed for journal key byte prefix
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
when CoreDbNoisyCaptJournal:
import
std/strutils,
chronicles,
stew/byteutils
func squeezeHex(s: string; ignLen = false): string =
result = if s.len < 20: s else: s[0 .. 5] & ".." & s[s.len-8 .. ^1]
if not ignLen:
let n = (s.len + 1) div 2
result &= "[" & (if 0 < n: "#" & $n else: "") & "]"
func stripZeros(a: string; toExp = false): string =
if 0 < a.len:
result = a.toLowerAscii.strip(leading=true, trailing=false, chars={'0'})
if result.len == 0:
result = "0"
elif result[^1] == '0' and toExp:
var n = 0
while result[^1] == '0':
let w = result.len
result.setLen(w-1)
n.inc
if n == 1:
result &= "0"
elif n == 2:
result &= "00"
elif 2 < n:
result &= "" & $n
func `$$`(w: openArray[byte]): string =
w.toHex.squeezeHex
func `$`(w: Blob): string =
w.toHex.squeezeHex
func `$`(w: UInt256): string =
"#" & w.toHex.stripZeros.squeezeHex
func `$`(w: Hash256): string =
"£" & w.data.toHex.squeezeHex
func `$`(w: VertexID): string =
if 0 < w.uint64: "$" & w.uint64.toHex.stripZeros else: ""
func `$`(w: AristoAccount): string =
"(" & $w.nonce & "," & $w.balance & "," & $w.codeHash & ")"
func `$`(ti: TraceDataItemRef): string =
result = "(" &
(if ti.pfx == TrpKvt: $KvtApiProfNames(ti.info)
elif ti.pfx == TrpOops: "<oops>"
else: $AristoApiProfNames(ti.info))
result &= "," & (
case ti.req:
of TrqOops: "<oops>"
of TrqFind: ""
of TrqModify: "="
of TrqDelete: "-"
of TrqAdd: "+")
result &= (
case ti.kind:
of TdtOops: "<oops>"
of TdtBlob: $ti.blob
of TdtBigNum: $ti.bigNum
of TdtHash: $ti.hash
of TdtVoid: "ø"
of TdtError: (if ti.pfx == TrpKvt: $KvtError(ti.error)
elif ti.pfx == TrpOops: "<oops>"
else: $AristoError(ti.error))
of TdtAccount: $ti.account)
result &= ")"
func toStr(pfx: TracePfx, key: openArray[byte]): string =
case pfx:
of TrpOops:
"<oops>"
of TrpKvt:
$$(key.toOpenArray(0, key.len - 1))
of TrpAccounts:
"1:" & $$(key.toOpenArray(0, key.len - 1))
of TrpGeneric:
$key[0] & ":" & $$(key.toOpenArray(1, key.len - 1))
of TrpStorage:
"1:" & $$(key.toOpenArray(0, min(31, key.len - 1))) & ":" &
(if 32 < key.len: $$(key.toOpenArray(32, key.len - 1)) else: "")
func `$`(key: openArray[byte]; ti: TraceDataItemRef): string =
"(" &
TracePfx(key[0]).toStr(key.toOpenArray(1, key.len - 1)) & "," &
$ti & ")"
# -------------------------------
template logTxt(info: static[string]): static[string] =
"trace " & info
func topLevel(tr: TraceRecorderRef): int =
tr.log.len - 1
# --------------------
proc jLogger(
tr: TraceRecorderRef;
key: openArray[byte];
ti: TraceDataItemRef;
) =
## Add or update journal entry. The `tr.pfx` argument indicates the key type:
##
## * `TrpKvt`: followed by KVT key
## * `TrpAccounts`: followed by <account-path>
## * `TrpGeneric`: followed by <root-ID> + <path>
## * `TrpStorage`: followed by <account-path> + <storage-path>
##
doAssert ti.pfx != TrpOops
let
pfx = @[ti.pfx.byte]
lRec = tr.log[^1].journal.lruFetch(pfx & @key).valueOr:
if LogJournalMax <= tr.log[^1].journal.len:
tr.log[^1].truncated = true
discard tr.log[^1].journal.lruAppend(pfx & @key, ti, LogJournalMax)
return
if ti.req != TrqFind:
lRec[] = ti[]
proc jLogger(
tr: TraceRecorderRef;
accPath: Hash256;
ti: TraceDataItemRef;
) =
tr.jLogger(accPath.data.toSeq, ti)
proc jLogger(
tr: TraceRecorderRef;
ti: TraceDataItemRef;
) =
tr.jLogger(EmptyBlob, ti)
proc jLogger(
tr: TraceRecorderRef;
root: VertexID;
path: openArray[byte];
ti: TraceDataItemRef;
) =
tr.jLogger(@[root.byte] & @path, ti)
proc jLogger(
tr: TraceRecorderRef;
root: VertexID;
ti: TraceDataItemRef;
) =
tr.jLogger(@[root.byte], ti)
proc jLogger(
tr: TraceRecorderRef;
accPath: Hash256;
stoPath: Hash256;
ti: TraceDataItemRef;
) =
tr.jLogger(accPath.data.toSeq & stoPath.data.toSeq, ti)
# --------------------
func to(w: AristoApiProfNames; T: type TracePfx): T =
case w:
of AristoApiProfFetchAccountRecordFn,
AristoApiProfFetchAccountStateFn,
AristoApiProfDeleteAccountRecordFn,
AristoApiProfMergeAccountRecordFn:
return TrpAccounts
of AristoApiProfFetchGenericDataFn,
AristoApiProfFetchGenericStateFn,
AristoApiProfDeleteGenericDataFn,
AristoApiProfDeleteGenericTreeFn,
AristoApiProfMergeGenericDataFn:
return TrpGeneric
of AristoApiProfFetchStorageDataFn,
AristoApiProfFetchStorageStateFn,
AristoApiProfDeleteStorageDataFn,
AristoApiProfDeleteStorageTreeFn,
AristoApiProfMergeStorageDataFn:
return TrpStorage
else:
discard
raiseAssert "Unsupported AristoApiProfNames: " & $w
func to(w: KvtApiProfNames; T: type TracePfx): T =
TrpKvt
# --------------------
func logRecord(
info: KvtApiProfNames | AristoApiProfNames;
req: TraceRequest;
data: openArray[byte];
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtBlob,
blob: @data)
func logRecord(
info: KvtApiProfNames | AristoApiProfNames;
req: TraceRequest;
error: KvtError | AristoError;
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtError,
error: error.ord)
func logRecord(
info: KvtApiProfNames | AristoApiProfNames;
req: TraceRequest;
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtVoid)
# --------------------
func logRecord(
info: AristoApiProfNames;
req: TraceRequest;
accRec: AristoAccount;
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtAccount,
account: accRec)
func logRecord(
info: AristoApiProfNames;
req: TraceRequest;
state: Hash256;
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtHash,
hash: state)
func logRecord(
info: AristoApiProfNames;
req: TraceRequest;
sto: Uint256;
): TraceDataItemRef =
TraceDataItemRef(
pfx: info.to(TracePfx),
info: info.ord,
req: req,
kind: TdtBigNum,
bigNum: sto)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc kvtTraceRecorder(tr: TraceRecorderRef) =
let
api = tr.db.kvtApi
tracerApi = api.dup
# Set up new production api `tracerApi` and save the old one
tr.kvtSave = api
tr.db.kvtApi = tracerApi
# Update production api
tracerApi.get =
proc(kvt: KvtDbRef; key: openArray[byte]): Result[Blob,KvtError] =
const info = KvtApiProfGetFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
let data = api.get(kvt, key).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), error
tr.jLogger(key, logRecord(info, TrqFind, error))
return err(error) # No way
tr.jLogger(key, logRecord(info, TrqFind, data))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), data=($$data)
ok(data)
tracerApi.del =
proc(kvt: KvtDbRef; key: openArray[byte]): Result[void,KvtError] =
const info = KvtApiProfDelFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let tiRec = block:
let rc = api.get(kvt, key)
if rc.isOk:
logRecord(info, TrqDelete, rc.value)
elif rc.error == GetNotFound:
logRecord(info, TrqDelete)
else:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), error=rc.error
tr.jLogger(key, logRecord(info, TrqDelete, rc.error))
return err(rc.error)
# Delete from DB
api.del(kvt, key).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), error
tr.jLogger(key, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(key, tiRec)
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key)
ok()
tracerApi.put =
proc(kvt: KvtDbRef; key, data: openArray[byte]): Result[void,KvtError] =
const info = KvtApiProfPutFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let
hasKey = api.hasKey(kvt, key).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), error
tr.jLogger(key, logRecord(info, TrqAdd, error))
return err(error)
mode = if hasKey: TrqModify else: TrqAdd
# Store on DB
api.put(kvt, key, data).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), data=($$data)
tr.jLogger(key, logRecord(info, mode, error))
return err(error)
tr.jLogger(key, logRecord(info, mode, data))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, key=($$key), data=($$data)
ok()
assert tr.kvtSave != tr.db.kvtApi
assert tr.kvtSave.del != tr.db.kvtApi.del
assert tr.kvtSave.hasKey == tr.db.kvtApi.hasKey
proc ariTraceRecorder(tr: TraceRecorderRef) =
let
api = tr.db.ariApi
tracerApi = api.dup
# Set up new production api `tracerApi` and save the old one
tr.ariSave = api
tr.db.ariApi = tracerApi
tracerApi.fetchAccountRecord =
proc(mpt: AristoDbRef;
accPath: Hash256;
): Result[AristoAccount,AristoError] =
const info = AristoApiProfFetchAccountRecordFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let accRec = api.fetchAccountRecord(mpt, accPath).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, error
tr.jLogger(accPath, logRecord(info, TrqFind, error))
return err(error)
tr.jLogger(accPath, logRecord(info, TrqFind, accRec))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, accRec
ok accRec
tracerApi.fetchAccountState =
proc(mpt: AristoDbRef;
updateOk: bool;
): Result[Hash256,AristoError] =
const info = AristoApiProfFetchAccountStateFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let state = api.fetchAccountState(mpt, updateOk).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, updateOk, error
tr.jLogger logRecord(info, TrqFind, error)
return err(error)
tr.jLogger logRecord(info, TrqFind, state)
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, updateOk, state
ok state
tracerApi.fetchGenericData =
proc(mpt: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[Blob,AristoError] =
const info = AristoApiProfFetchGenericDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let data = api.fetchGenericData(mpt, root, path).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path=($$path), error
tr.jLogger(root, path, logRecord(info, TrqFind, error))
return err(error)
tr.jLogger(root, path, logRecord(info, TrqFind, data))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path=($$path), data
ok data
tracerApi.fetchGenericState =
proc(mpt: AristoDbRef;
root: VertexID;
updateOk: bool;
): Result[Hash256,AristoError] =
const info = AristoApiProfFetchGenericStateFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let state = api.fetchAccountState(mpt, updateOk).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, updateOk, error
tr.jLogger(root, logRecord(info, TrqFind, error))
return err(error)
tr.jLogger(root, logRecord(info, TrqFind, state))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, updateOk, state
ok state
tracerApi.fetchStorageData =
proc(mpt: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[Uint256,AristoError] =
const info = AristoApiProfFetchStorageDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let stoData = api.fetchStorageData(mpt, accPath, stoPath).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, error
tr.jLogger(accPath, stoPath, logRecord(info, TrqFind, error))
return err(error)
tr.jLogger(accPath, stoPath, logRecord(info, TrqFind, stoData))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, stoData
ok stoData
tracerApi.fetchStorageState =
proc(mpt: AristoDbRef;
accPath: Hash256;
updateOk: bool;
): Result[Hash256,AristoError] =
const info = AristoApiProfFetchStorageStateFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB
let state = api.fetchStorageState(mpt, accPath, updateOk).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, updateOk, error
tr.jLogger(accPath, logRecord(info, TrqFind, error))
return err(error)
tr.jLogger(accPath, logRecord(info, TrqFind, state))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, updateOk, state
ok state
tracerApi.deleteAccountRecord =
proc(mpt: AristoDbRef;
accPath: Hash256;
): Result[void,AristoError] =
const info = AristoApiProfDeleteAccountRecordFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let tiRec = block:
let rc = api.fetchAccountRecord(mpt, accPath)
if rc.isOk:
logRecord(info, TrqDelete, rc.value)
elif rc.error == FetchPathNotFound:
logRecord(info, TrqDelete)
else:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, error=rc.error
tr.jLogger(accPath, logRecord(info, TrqDelete, rc.error))
return err(rc.error)
# Delete from DB
api.deleteAccountRecord(mpt, accPath).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, error
tr.jLogger(accPath, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(accPath, tiRec)
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath
ok()
tracerApi.deleteGenericData =
proc(mpt: AristoDbRef;
root: VertexID;
path: openArray[byte];
): Result[bool,AristoError] =
const info = AristoApiProfDeleteGenericDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let tiRec = block:
let rc = api.fetchGenericData(mpt, root, path)
if rc.isOk:
logRecord(info, TrqDelete, rc.value)
elif rc.error == FetchPathNotFound:
logRecord(info, TrqDelete)
else:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path=($$path), error=rc.error
tr.jLogger(root, path, logRecord(info, TrqDelete, rc.error))
return err(rc.error)
# Delete from DB
let emptyTrie = api.deleteGenericData(mpt, root, path).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path=($$path), error
tr.jLogger(root, path, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(root, path, tiRec)
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path=($$path), emptyTrie
ok emptyTrie
tracerApi.deleteGenericTree =
proc(mpt: AristoDbRef;
root: VertexID;
): Result[void,AristoError] =
const info = AristoApiProfDeleteGenericTreeFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Delete from DB
api.deleteGenericTree(mpt, root).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, error
tr.jLogger(root, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(root, logRecord(info, TrqDelete))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root
ok()
tracerApi.deleteStorageData =
proc(mpt: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
): Result[bool,AristoError] =
const info = AristoApiProfDeleteStorageDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let tiRec = block:
let rc = api.fetchStorageData(mpt, accPath, stoPath)
if rc.isOk:
logRecord(info, TrqDelete, rc.value)
elif rc.error == FetchPathNotFound:
logRecord(info, TrqDelete)
else:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, error=rc.error
tr.jLogger(accPath, stoPath, logRecord(info, TrqDelete, rc.error))
return err(rc.error)
let emptyTrie = api.deleteStorageData(mpt, accPath, stoPath).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, error
tr.jLogger(accPath, stoPath, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(accPath, stoPath, tiRec)
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, emptyTrie
ok emptyTrie
tracerApi.deleteStorageTree =
proc(mpt: AristoDbRef;
accPath: Hash256;
): Result[void,AristoError] =
const info = AristoApiProfDeleteStorageTreeFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Delete from DB
api.deleteStorageTree(mpt, accPath).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, error
tr.jLogger(accPath, logRecord(info, TrqDelete, error))
return err(error)
# Log on journal
tr.jLogger(accPath, logRecord(info, TrqDelete))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath
ok()
tracerApi.mergeAccountRecord =
proc(mpt: AristoDbRef;
accPath: Hash256;
accRec: AristoAccount;
): Result[bool,AristoError] =
const info = AristoApiProfMergeAccountRecordFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let
hadPath = api.hasPathAccount(mpt, accPath).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, error
tr.jLogger(accPath, logRecord(info, TrqAdd, error))
return err(error)
mode = if hadPath: TrqModify else: TrqAdd
# Do the merge
let updated = api.mergeAccountRecord(mpt, accPath, accRec).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, hadPath, error
tr.jLogger(accPath, logRecord(info, mode, error))
return err(error)
# Log on journal
tr.jLogger(accPath, logRecord(info, mode, accRec))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, accRec, hadPath, updated
ok updated
tracerApi.mergeGenericData =
proc(mpt: AristoDbRef;
root: VertexID;
path: openArray[byte];
data: openArray[byte];
): Result[bool,AristoError] =
const info = AristoApiProfMergeGenericDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let
hadPath = api.hasPathGeneric(mpt, root, path).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path, error
tr.jLogger(root, path, logRecord(info, TrqAdd, error))
return err(error)
mode = if hadPath: TrqModify else: TrqAdd
# Do the merge
let updated = api.mergeGenericData(mpt, root, path, data).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path, error
tr.jLogger(root, path, logRecord(info, mode, error))
return err(error)
# Log on journal
tr.jLogger(root, path, logRecord(info, mode, data))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, root, path, data=($$data), hadPath, updated
ok updated
tracerApi.mergeStorageData =
proc(mpt: AristoDbRef;
accPath: Hash256;
stoPath: Hash256;
stoData: UInt256;
): Result[void,AristoError] =
const info = AristoApiProfMergeStorageDataFn
when CoreDbNoisyCaptJournal:
let level = tr.topLevel()
# Find entry on DB (for comprehensive log record)
let
hadPath = api.hasPathStorage(mpt, accPath, stoPath).valueOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, error
tr.jLogger(accPath, stoPath, logRecord(info, TrqAdd, error))
return err(error)
mode = if hadPath: TrqModify else: TrqAdd
# Do the merge
api.mergeStorageData(mpt, accPath, stoPath,stoData).isOkOr:
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, error
tr.jLogger(accPath, stoPath, logRecord(info, mode, error))
return err(error)
# Log on journal
tr.jLogger(accPath, stoPath, logRecord(info, mode, stoData))
when CoreDbNoisyCaptJournal:
debug logTxt $info, level, accPath, stoPath, stoData, hadPath
ok()
assert tr.ariSave != tr.db.ariApi
assert tr.ariSave.deleteAccountRecord != tr.db.ariApi.deleteAccountRecord
assert tr.ariSave.hasPathAccount == tr.db.ariApi.hasPathAccount
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc topInst*(tr: TraceRecorderRef): TraceLogInstRef =
## Get top level logger
tr.log[^1]
func truncated*(log: TraceLogInstRef): bool =
## True if journal was truncated due to collecting too many entries
log.truncated
func level*(log: TraceLogInstRef): int =
## Non-negative stack level of this log instance.
log.level
func journal*(log: TraceLogInstRef): KeyedQueue[Blob,TraceDataItemRef] =
## Get the journal
log.journal
func db*(log: TraceLogInstRef): CoreDbRef =
## Get database
log.base.db
iterator kvtLog*(log: TraceLogInstRef): (Blob,TraceDataItemRef) =
## Extract `Kvt` journal
for p in log.journal.nextPairs:
let pfx = TracePfx(p.key[0])
if pfx == TrpKvt:
yield (p.key[1..^1], p.data)
proc kvtLogBlobs*(log: TraceLogInstRef): seq[(Blob,Blob)] =
log.kvtLog.toSeq
.filterIt(it[1].kind==TdtBlob)
.mapIt((it[0],it[1].blob))
iterator ariLog*(log: TraceLogInstRef): (VertexID,Blob,TraceDataItemRef) =
## Extract `Aristo` journal
for p in log.journal.nextPairs:
let
pfx = TracePfx(p.key[0])
(root, key) = block:
case pfx:
of TrpAccounts,TrpStorage:
(VertexID(1), p.key[1..^1])
of TrpGeneric:
(VertexID(p.key[1]), p.key[2..^1])
else:
continue
yield (root, key, p.data)
proc pop*(log: TraceLogInstRef): bool =
## Reduce logger stack by the argument descriptor `log` which must be the
## top entry on the stack. The function returns `true` if the descriptor
## `log` was not the only one on stack and the stack was reduced by the
## top entry. Otherwise nothing is done and `false` returned.
##
let tr = log.base
doAssert log.level == tr.topLevel()
if 1 < tr.log.len: # Always leave one instance on stack
tr.log.setLen(tr.log.len - 1)
return true
proc push*(tr: TraceRecorderRef) =
## Push overlay logger instance
tr.log.add TraceLogInstRef(base: tr, level: tr.log.len)
# ------------------------------------------------------------------------------
# Public constructor/destructor
# ------------------------------------------------------------------------------
proc init*(
T: type TraceRecorderRef; # Recorder desc to instantiate
db: CoreDbRef; # Database
): T =
## Constructor, create initial/base tracer descriptor
result = T(db: db)
result.push()
result.kvtTraceRecorder()
result.ariTraceRecorder()
proc restore*(tr: TraceRecorderRef) =
## Restore production API.
tr.db.kvtApi = tr.kvtSave
tr.db.ariApi = tr.ariSave
tr[].reset
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -15,6 +15,7 @@ import
eth/common,
"../.."/[constants, errors],
".."/[kvt, aristo],
./backend/aristo_db,
./base/[api_tracking, base_config, base_desc, base_helpers]
export
@ -44,7 +45,7 @@ when CoreDbEnableProfiling:
CoreDbFnInx,
CoreDbProfListRef
when CoreDbEnableCaptJournal and false:
when CoreDbEnableCaptJournal:
import
./backend/aristo_trace
type
@ -69,33 +70,69 @@ proc ctx*(db: CoreDbRef): CoreDbCtxRef =
##
db.defCtx
proc swapCtx*(db: CoreDbRef; ctx: CoreDbCtxRef): CoreDbCtxRef =
## Activate argument context `ctx` as default and return the previously
## active context. This function goes typically together with `forget()`. A
## valid scenario might look like
## ::
## proc doSomething(db: CoreDbRef; ctx: CoreDbCtxRef) =
## let saved = db.swapCtx ctx
## defer: db.swapCtx(saved).forget()
proc newCtxByKey*(ctx: CoreDbCtxRef; root: Hash256): CoreDbRc[CoreDbCtxRef] =
## Create new context derived from a matching transaction of the currently
## active context. If successful, the resulting context has the following
## properties:
##
## * Transaction level is 1
## * The state of the accounts column is equal to the argument `root`
##
## If successful, the resulting descriptor **must** be manually released
## with `forget()` when it is not used, anymore.
##
## Note:
## The underlying `Aristo` backend uses lazy hashing so this function
## might fail simply because there is no computed state when nesting
## the next transaction. If the previous transaction needs to be found,
## then it must called like this:
## ::
## let db = .. # Instantiate CoreDb handle
## ...
## discard db.ctx.getAccounts.state() # Compute state hash
## db.ctx.newTransaction() # Enter new transaction
##
## However, remember that unused hash computations are contle relative
## to processing time.
##
ctx.setTrackNewApi CtxNewCtxByKeyFn
result = ctx.newCtxByKey(root, $api)
ctx.ifTrackNewApi: debug logTxt, api, elapsed, root=($$root), result
proc swapCtx*(ctx: CoreDbCtxRef; db: CoreDbRef): CoreDbCtxRef =
## Activate argument context `ctx` as default and return the previously
## active context. This function goes typically together with `forget()`.
## A valid scenario might look like
## ::
## let db = .. # Instantiate CoreDb handle
## ...
## let ctx = newCtxByKey(..).expect "ctx" # Create new context
## let saved = db.swapCtx ctx # Swap context dandles
## defer: db.swapCtx(saved).forget() # Restore
## ...
##
doAssert not ctx.isNil
db.setTrackNewApi BaseSwapCtxFn
assert db.defCtx != ctx # debugging only
db.setTrackNewApi CtxSwapCtxFn
# Swap default context with argument `ctx`
result = db.defCtx
db.defCtx = ctx
# Set read-write access and install
CoreDbAccRef(ctx).call(reCentre, db.ctx.mpt).isOkOr:
raiseAssert $api & " failed: " & $error
CoreDbKvtRef(ctx).call(reCentre, db.ctx.kvt).isOkOr:
raiseAssert $api & " failed: " & $error
db.defCtx = ctx
doAssert db.defCtx != result
db.ifTrackNewApi: debug logTxt, api, elapsed
proc forget*(ctx: CoreDbCtxRef) =
## Dispose `ctx` argument context and related columns created with this
## context. This function fails if `ctx` is the default context.
## context. This function throws an exception `ctx` is the default context.
##
ctx.setTrackNewApi CtxForgetFn
doAssert ctx != ctx.parent.defCtx
CoreDbAccRef(ctx).call(forget, ctx.mpt).isOkOr:
raiseAssert $api & ": " & $error
CoreDbKvtRef(ctx).call(forget, ctx.kvt).isOkOr:
@ -713,66 +750,54 @@ proc dispose*(tx: CoreDbTxRef) =
# Public tracer methods
# ------------------------------------------------------------------------------
when CoreDbEnableCaptJournal and false: # currently disabled
proc newCapture*(
db: CoreDbRef;
): CoreDbRc[CoreDbCaptRef] =
## Trace constructor providing an overlay on top of the argument database
## `db`. This overlay provides a replacement database handle that can be
## retrieved via `db.recorder()` (which can in turn be ovelayed.) While
## running the overlay stores data in a log-table which can be retrieved
## via `db.logDb()`.
when CoreDbEnableCaptJournal:
proc pushCapture*(db: CoreDbRef): CoreDbCaptRef =
## ..
##
## Caveat:
## The original database argument `db` should not be used while the tracer
## is active (i.e. exists as overlay). The behaviour for this situation
## is undefined and depends on the backend implementation of the tracer.
##
db.setTrackNewApi BaseNewCaptureFn
result = db.methods.newCaptureFn flags
db.setTrackNewApi BasePushCaptureFn
if db.tracerHook.isNil:
db.tracerHook = TraceRecorderRef.init(db)
else:
TraceRecorderRef(db.tracerHook).push()
result = TraceRecorderRef(db.tracerHook).topInst().CoreDbCaptRef
db.ifTrackNewApi: debug logTxt, api, elapsed, result
proc recorder*(cpt: CoreDbCaptRef): CoreDbRef =
## Getter, returns a tracer replacement handle to be used as new database.
## It records every action like fetch, store, hasKey, hasPath and delete.
## This descriptor can be superseded by a new overlay tracer (using
## `newCapture()`, again.)
proc level*(cpt: CoreDbCaptRef): int =
## Getter, returns the positive number of stacked instances.
##
## Caveat:
## Unless the desriptor `cpt` referes to the top level overlay tracer, the
## result is undefined and depends on the backend implementation of the
## tracer.
##
cpt.setTrackNewApi CptRecorderFn
result = cpt.methods.recorderFn()
cpt.ifTrackNewApi: debug logTxt, api, elapsed
let log = cpt.distinctBase
log.db.setTrackNewApi CptLevelFn
result = log.level()
log.db.ifTrackNewApi: debug logTxt, api, elapsed, result
proc logDb*(cp: CoreDbCaptRef): TableRef[Blob,Blob] =
## Getter, returns the logger table for the overlay tracer database.
proc kvtLog*(cpt: CoreDbCaptRef): seq[(Blob,Blob)] =
## Getter, returns the `Kvt` logger list for the argument instance.
##
## Caveat:
## Unless the desriptor `cpt` referes to the top level overlay tracer, the
## result is undefined and depends on the backend implementation of the
## tracer.
##
cp.setTrackNewApi CptLogDbFn
result = cp.methods.logDbFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed
let log = cpt.distinctBase
log.db.setTrackNewApi CptKvtLogFn
result = log.kvtLogBlobs()
log.db.ifTrackNewApi: debug logTxt, api, elapsed
proc flags*(cp: CoreDbCaptRef):set[CoreDbCaptFlags] =
## Getter
##
cp.setTrackNewApi CptFlagsFn
result = cp.methods.getFlagsFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed, result
proc forget*(cp: CoreDbCaptRef) =
proc pop*(cpt: CoreDbCaptRef) =
## Explicitely stop recording the current tracer instance and reset to
## previous level.
##
cp.setTrackNewApi CptForgetFn
cp.methods.forgetFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed
let db = cpt.distinctBase.db
db.setTrackNewApi CptPopFn
if not cpt.distinctBase.pop():
TraceRecorderRef(db.tracerHook).restore()
db.tracerHook = TraceRecorderRef(nil)
db.ifTrackNewApi: debug logTxt, api, elapsed, cpt
proc stopCapture*(db: CoreDbRef) =
## Discard capture instances. This function is equivalent to `pop()`-ing
## all instances.
##
db.setTrackNewApi CptStopCaptureFn
if not db.tracerHook.isNil:
TraceRecorderRef(db.tracerHook).restore()
db.tracerHook = TraceRecorderRef(nil)
db.ifTrackNewApi: debug logTxt, api, elapsed
# ------------------------------------------------------------------------------
# End

View File

@ -50,20 +50,21 @@ type
BaseFinishFn = "finish"
BaseLevelFn = "level"
BaseNewCaptureFn = "newCapture"
BaseNewCtxFromTxFn = "ctxFromTx"
BasePushCaptureFn = "pushCapture"
BaseNewTxFn = "newTransaction"
BasePersistentFn = "persistent"
BaseStateBlockNumberFn = "stateBlockNumber"
BaseSwapCtxFn = "swapCtx"
CptLogDbFn = "cpt/logDb"
CptRecorderFn = "cpt/recorder"
CptForgetFn = "cpt/forget"
CptKvtLogFn = "kvtLog"
CptLevelFn = "level"
CptPopFn = "pop"
CptStopCaptureFn = "stopCapture"
CtxForgetFn = "ctx/forget"
CtxGetAccountsFn = "getAccounts"
CtxGetGenericFn = "getGeneric"
CtxNewCtxByKeyFn = "newCtxByKey"
CtxSwapCtxFn = "swapCtx"
KvtDelFn = "del"
KvtGetFn = "get"

View File

@ -8,39 +8,93 @@
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
# TODO: CoreDb module needs to be updated
{.push raises: [].}
import
std/[strutils, json],
./common/common,
./db/[core_db, ledger],
./utils/utils,
./evm/tracer/legacy_tracer,
./constants,
./transaction,
./core/executor,
./evm/[state, types],
nimcrypto/utils as ncrutils,
web3/conversions, ./launcher,
results,
./beacon/web3_eth_conv
web3/conversions,
./beacon/web3_eth_conv,
./common/common,
./constants,
./core/executor,
./db/[core_db, ledger],
./evm/[code_bytes, state, types],
./evm/tracer/legacy_tracer,
./launcher,
./transaction,
./utils/utils
proc getParentHeader(self: CoreDbRef, header: BlockHeader): BlockHeader =
self.getBlockHeader(header.parentHash)
when not CoreDbEnableCaptJournal:
{.error: "Compiler flag missing for tracer, try -d:dbjapi_enabled".}
type
SaveCtxEnv = object
db: CoreDbRef
ctx: CoreDbCtxRef
CaptCtxRef = ref object
db: CoreDbRef # not `nil`
root: common.Hash256
ctx: CoreDbCtxRef # not `nil`
cpt: CoreDbCaptRef # not `nil`
restore: CoreDbCtxRef # `nil` unless `ctx` activated
proc newCtx(com: CommonRef; root: eth_types.Hash256): SaveCtxEnv =
let ctx = com.db.ctxFromTx(root).valueOr:
raiseAssert "setParentCtx: " & $$error
SaveCtxEnv(db: com.db, ctx: ctx)
const
senderName = "sender"
recipientName = "recipient"
minerName = "miner"
uncleName = "uncle"
internalTxName = "internalTx"
proc setCtx(saveCtx: SaveCtxEnv): SaveCtxEnv =
SaveCtxEnv(db: saveCtx.db, ctx: saveCtx.db.swapCtx saveCtx.ctx)
proc dumpMemoryDB*(node: JsonNode, cpt: CoreDbCaptRef) {.gcsafe.}
proc toJson*(receipts: seq[Receipt]): JsonNode {.gcsafe.}
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template safeTracer(info: string; code: untyped) =
try:
code
except CatchableError as e:
raiseAssert info & " name=" & $e.name & " msg=" & e.msg
# -------------------
proc init(
T: type CaptCtxRef;
com: CommonRef;
root: common.Hash256;
): T
{.raises: [CatchableError].} =
let ctx = block:
let rc = com.db.ctx.newCtxByKey(root)
if rc.isErr:
raiseAssert "newCptCtx: " & $$rc.error
rc.value
T(db: com.db, root: root, cpt: com.db.pushCapture(), ctx: ctx)
proc init(
T: type CaptCtxRef;
com: CommonRef;
topHeader: BlockHeader;
): T
{.raises: [CatchableError].} =
T.init(com, com.db.getBlockHeader(topHeader.parentHash).stateRoot)
proc activate(cc: CaptCtxRef): CaptCtxRef {.discardable.} =
## Install/activate new context `cc.ctx`, old one in `cc.restore`
doAssert not cc.isNil
doAssert cc.restore.isNil # otherwise activated, already
cc.restore = cc.ctx.swapCtx cc.db
cc
proc release(cc: CaptCtxRef) =
if not cc.restore.isNil: # switch to original context (if any)
let ctx = cc.restore.swapCtx(cc.db)
doAssert ctx == cc.ctx
cc.ctx.forget() # dispose
cc.cpt.pop() # discard top layer of actions tracer
# -------------------
proc `%`(x: openArray[byte]): JsonNode =
result = %toHex(x, false)
@ -57,17 +111,25 @@ proc toJson(receipt: Receipt): JsonNode =
else:
result["status"] = %receipt.status
proc dumpReceipts*(chainDB: CoreDbRef, header: BlockHeader): JsonNode =
proc dumpReceiptsImpl(
chainDB: CoreDbRef;
header: BlockHeader;
): JsonNode
{.raises: [CatchableError].} =
result = newJArray()
for receipt in chainDB.getReceipts(header.receiptsRoot):
result.add receipt.toJson
proc toJson*(receipts: seq[Receipt]): JsonNode =
result = newJArray()
for receipt in receipts:
result.add receipt.toJson
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc captureAccount(n: JsonNode, db: LedgerRef, address: EthAddress, name: string) =
proc captureAccount(
n: JsonNode;
db: LedgerRef;
address: EthAddress;
name: string;
) =
var jaccount = newJObject()
jaccount["name"] = %name
jaccount["address"] = %("0x" & $address)
@ -82,7 +144,7 @@ proc captureAccount(n: JsonNode, db: LedgerRef, address: EthAddress, name: strin
let code = db.getCode(address)
jaccount["codeHash"] = %("0x" & ($codeHash).toLowerAscii)
jaccount["code"] = %("0x" & toHex(code, true))
jaccount["code"] = %("0x" & code.bytes.toHex(true))
jaccount["storageRoot"] = %("0x" & ($storageRoot).toLowerAscii)
var storage = newJObject()
@ -92,48 +154,26 @@ proc captureAccount(n: JsonNode, db: LedgerRef, address: EthAddress, name: strin
n.add jaccount
proc dumpMemoryDB*(node: JsonNode, db: CoreDbRef) =
var n = newJObject()
for k, v in db.ctx.getKvt():
n[k.toHex(false)] = %v
node["state"] = n
proc dumpMemoryDB*(node: JsonNode, kvt: TableRef[common.Blob, common.Blob]) =
var n = newJObject()
for k, v in kvt:
n[k.toHex(false)] = %v
node["state"] = n
proc traceTransactionImpl(
com: CommonRef;
header: BlockHeader;
transactions: openArray[Transaction];
txIndex: uint64;
tracerFlags: set[TracerFlags] = {};
): JsonNode
{.raises: [CatchableError].}=
if header.txRoot == EMPTY_ROOT_HASH:
return newJNull()
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,
transactions: openArray[Transaction], txIndex: uint64,
tracerFlags: set[TracerFlags] = {}): JsonNode =
let
# we add a memory layer between backend/lower layer db
# and capture state db snapshot during transaction execution
capture = com.db.newCapture.value
tracerInst = newLegacyTracer(tracerFlags)
captureCom = com.clone(capture.recorder)
saveCtx = setCtx com.newCtx(com.db.getParentHeader(header).stateRoot)
vmState = BaseVMState.new(header, captureCom).valueOr:
return newJNull()
cc = activate CaptCtxRef.init(com, header)
vmState = BaseVMState.new(header, com).valueOr: return newJNull()
stateDb = vmState.stateDB
defer:
saveCtx.setCtx().ctx.forget()
capture.forget()
defer: cc.release()
if header.txRoot == EMPTY_ROOT_HASH: return newJNull()
doAssert(transactions.calcTxRoot == header.txRoot)
doAssert(transactions.len != 0)
@ -142,8 +182,7 @@ proc traceTransaction*(com: CommonRef, header: BlockHeader,
before = newJArray()
after = newJArray()
stateDiff = %{"before": before, "after": after}
beforeRoot: common.Hash256
beforeCtx: SaveCtxEnv
stateCtx = CaptCtxRef(nil)
let
miner = vmState.coinbase()
@ -159,13 +198,14 @@ proc traceTransaction*(com: CommonRef, header: BlockHeader,
before.captureAccount(stateDb, miner, minerName)
stateDb.persist()
stateDiff["beforeRoot"] = %($stateDb.rootHash)
beforeRoot = stateDb.rootHash
beforeCtx = com.newCtx beforeRoot
discard com.db.ctx.getAccounts.state(updateOk=true) # lazy hashing!
stateCtx = CaptCtxRef.init(com, stateDb.rootHash)
let rc = vmState.processTransaction(tx, sender, header)
gasUsed = if rc.isOk: rc.value else: 0
if idx.uint64 == txIndex:
discard com.db.ctx.getAccounts.state(updateOk=true) # lazy hashing!
after.captureAccount(stateDb, sender, senderName)
after.captureAccount(stateDb, recipient, recipientName)
after.captureAccount(stateDb, miner, minerName)
@ -176,13 +216,12 @@ proc traceTransaction*(com: CommonRef, header: BlockHeader,
# internal transactions:
let
saveCtxBefore = setCtx beforeCtx
stateBefore = LedgerRef.init(capture.recorder, beforeRoot)
defer:
saveCtxBefore.setCtx().ctx.forget()
cx = activate stateCtx
ldgBefore = LedgerRef.init(com.db, cx.root)
defer: cx.release()
for idx, acc in tracedAccountsPairs(tracerInst):
before.captureAccount(stateBefore, acc, internalTxName & $idx)
before.captureAccount(ldgBefore, acc, internalTxName & $idx)
for idx, acc in tracedAccountsPairs(tracerInst):
after.captureAccount(stateDb, acc, internalTxName & $idx)
@ -195,30 +234,34 @@ proc traceTransaction*(com: CommonRef, header: BlockHeader,
# now we dump captured state db
if TracerFlags.DisableState notin tracerFlags:
result.dumpMemoryDB(capture)
result.dumpMemoryDB(cx.cpt)
proc dumpBlockState*(com: CommonRef, blk: EthBlock, dumpState = false): JsonNode =
proc dumpBlockStateImpl(
com: CommonRef;
blk: EthBlock;
dumpState = false;
): JsonNode
{.raises: [CatchableError].} =
template header: BlockHeader = blk.header
let
parent = com.db.getParentHeader(header)
capture = com.db.newCapture.value
captureCom = com.clone(capture.recorder)
# we only need a stack dump when scanning for internal transaction address
cc = activate CaptCtxRef.init(com, header)
parent = com.db.getBlockHeader(header.parentHash)
# only need a stack dump when scanning for internal transaction address
captureFlags = {DisableMemory, DisableStorage, EnableAccount}
tracerInst = newLegacyTracer(captureFlags)
saveCtx = setCtx com.newCtx(parent.stateRoot)
vmState = BaseVMState.new(header, captureCom, tracerInst).valueOr:
return newJNull()
vmState = BaseVMState.new(header, com, tracerInst).valueOr:
return newJNull()
miner = vmState.coinbase()
defer:
saveCtx.setCtx().ctx.forget()
capture.forget()
defer: cc.release()
var
before = newJArray()
after = newJArray()
stateBefore = LedgerRef.init(capture.recorder, parent.stateRoot)
stateBefore = LedgerRef.init(com.db, parent.stateRoot)
for idx, tx in blk.transactions:
let sender = tx.getSender
@ -259,22 +302,24 @@ proc dumpBlockState*(com: CommonRef, blk: EthBlock, dumpState = false): JsonNode
result = %{"before": before, "after": after}
if dumpState:
result.dumpMemoryDB(capture)
result.dumpMemoryDB(cc.cpt)
proc traceBlock*(com: CommonRef, blk: EthBlock, tracerFlags: set[TracerFlags] = {}): JsonNode =
proc traceBlockImpl(
com: CommonRef;
blk: EthBlock;
tracerFlags: set[TracerFlags] = {};
): JsonNode
{.raises: [CatchableError].} =
template header: BlockHeader = blk.header
let
capture = com.db.newCapture.value
captureCom = com.clone(capture.recorder)
cc = activate CaptCtxRef.init(com, header)
tracerInst = newLegacyTracer(tracerFlags)
vmState = BaseVMState.new(header, com, tracerInst).valueOr:
return newJNull()
saveCtx = setCtx com.newCtx(com.db.getParentHeader(header).stateRoot)
vmState = BaseVMState.new(header, captureCom, tracerInst).valueOr:
return newJNull()
defer:
saveCtx.setCtx().ctx.forget()
capture.forget()
defer: cc.release()
if header.txRoot == EMPTY_ROOT_HASH: return newJNull()
doAssert(blk.transactions.calcTxRoot == header.txRoot)
@ -293,24 +338,33 @@ proc traceBlock*(com: CommonRef, blk: EthBlock, tracerFlags: set[TracerFlags] =
result["gas"] = %gasUsed
if TracerFlags.DisableState notin tracerFlags:
result.dumpMemoryDB(capture)
result.dumpMemoryDB(cc.cpt)
proc traceTransactions*(com: CommonRef, header: BlockHeader, transactions: openArray[Transaction]): JsonNode =
proc traceTransactionsImpl(
com: CommonRef;
header: BlockHeader;
transactions: openArray[Transaction];
): JsonNode
{.raises: [CatchableError].} =
result = newJArray()
for i in 0 ..< transactions.len:
result.add traceTransaction(com, header, transactions, i.uint64, {DisableState})
result.add traceTransactionImpl(
com, header, transactions, i.uint64, {DisableState})
proc dumpDebuggingMetaData*(vmState: BaseVMState, blk: EthBlock, launchDebugger = true) =
proc dumpDebuggingMetaDataImpl(
vmState: BaseVMState;
blk: EthBlock;
launchDebugger = true;
) {.raises: [CatchableError].} =
template header: BlockHeader = blk.header
let
com = vmState.com
cc = activate CaptCtxRef.init(vmState.com, header)
blockNumber = header.number
capture = com.db.newCapture.value
captureCom = com.clone(capture.recorder)
bloom = createBloom(vmState.receipts)
defer:
capture.forget()
defer: cc.release()
let blockSummary = %{
"receiptsRoot": %("0x" & toHex(calcReceiptsRoot(vmState.receipts).data)),
@ -320,17 +374,82 @@ proc dumpDebuggingMetaData*(vmState: BaseVMState, blk: EthBlock, launchDebugger
var metaData = %{
"blockNumber": %blockNumber.toHex,
"txTraces": traceTransactions(captureCom, header, blk.transactions),
"stateDump": dumpBlockState(captureCom, blk),
"blockTrace": traceBlock(captureCom, blk, {DisableState}),
"txTraces": traceTransactionsImpl(vmState.com, header, blk.transactions),
"stateDump": dumpBlockStateImpl(vmState.com, blk),
"blockTrace": traceBlockImpl(vmState.com, blk, {DisableState}),
"receipts": toJson(vmState.receipts),
"block": blockSummary
}
metaData.dumpMemoryDB(capture)
metaData.dumpMemoryDB(cc.cpt)
let jsonFileName = "debug" & $blockNumber & ".json"
if launchDebugger:
launchPremix(jsonFileName, metaData)
else:
writeFile(jsonFileName, metaData.pretty())
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc traceBlock*(
com: CommonRef;
blk: EthBlock;
tracerFlags: set[TracerFlags] = {};
): JsonNode =
"traceBlock".safeTracer:
result = com.traceBlockImpl(blk, tracerFlags)
proc toJson*(receipts: seq[Receipt]): JsonNode =
result = newJArray()
for receipt in receipts:
result.add receipt.toJson
proc dumpMemoryDB*(node: JsonNode, cpt: CoreDbCaptRef) =
var n = newJObject()
for (k,v) in cpt.kvtLog:
n[k.toHex(false)] = %v
node["state"] = n
proc dumpReceipts*(chainDB: CoreDbRef, header: BlockHeader): JsonNode =
"dumpReceipts".safeTracer:
result = chainDB.dumpReceiptsImpl header
proc traceTransaction*(
com: CommonRef;
header: BlockHeader;
txs: openArray[Transaction];
txIndex: uint64;
tracerFlags: set[TracerFlags] = {};
): JsonNode =
"traceTransaction".safeTracer:
result = com.traceTransactionImpl(header, txs, txIndex,tracerFlags)
proc dumpBlockState*(
com: CommonRef;
blk: EthBlock;
dumpState = false;
): JsonNode =
"dumpBlockState".safeTracer:
result = com.dumpBlockStateImpl(blk, dumpState)
proc traceTransactions*(
com: CommonRef;
header: BlockHeader;
transactions: openArray[Transaction];
): JsonNode =
"traceTransactions".safeTracer:
result = com.traceTransactionsImpl(header, transactions)
proc dumpDebuggingMetaData*(
vmState: BaseVMState;
blk: EthBlock;
launchDebugger = true;
) =
"dumpDebuggingMetaData".safeTracer:
vmState.dumpDebuggingMetaDataImpl(blk, launchDebugger)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -16,7 +16,7 @@ cliBuilder:
./test_genesis,
./test_precompiles,
./test_generalstate_json,
#./test_tracer_json, -- temporarily disabled
./test_tracer_json,
#./test_persistblock_json, -- fails
#./test_rpc, -- fails
./test_filters,

View File

@ -66,7 +66,7 @@ func pp*(h: BlockHeader; sep = " "): string =
&"receiptsRoot={h.receiptsRoot.pp}{sep}" &
&"stateRoot={h.stateRoot.pp}{sep}" &
&"baseFee={h.baseFeePerGas}{sep}" &
&"withdrawalsRoot={h.withdrawalsRoot.get(EMPTY_ROOT_HASH)}{sep}" &
&"withdrawalsRoot={h.withdrawalsRoot.get(EMPTY_ROOT_HASH).pp}{sep}" &
&"blobGasUsed={h.blobGasUsed.get(0'u64)}{sep}" &
&"excessBlobGas={h.excessBlobGas.get(0'u64)}"

View File

@ -9,16 +9,16 @@
# or distributed except according to those terms.
import
std/[json, os, sets, tables, strutils],
std/[json, os, tables, strutils],
stew/byteutils,
chronicles,
unittest2,
results,
./test_helpers,
../nimbus/sync/protocol/snap/snap_types,
../nimbus/db/aristo/aristo_merge,
../nimbus/db/kvt/kvt_utils,
../nimbus/db/aristo,
../nimbus/db/aristo/[aristo_desc, aristo_layers, aristo_nearby, aristo_part],
../nimbus/db/aristo/aristo_part/part_debug,
../nimbus/db/kvt/kvt_utils,
../nimbus/[tracer, evm/types],
../nimbus/common/common
@ -28,14 +28,17 @@ proc setErrorLevel {.used.} =
proc preLoadAristoDb(cdb: CoreDbRef; jKvp: JsonNode; num: BlockNumber) =
## Hack for `Aristo` pre-lading using the `snap` protocol proof-loader
const
info = "preLoadAristoDb"
var
proof: seq[SnapProof] # for pre-loading MPT
predRoot: Hash256 # from predecessor header
txRoot: Hash256 # header with block number `num`
rcptRoot: Hash256 # ditto
proof: seq[Blob] # for pre-loading MPT
predRoot: Hash256 # from predecessor header
txRoot: Hash256 # header with block number `num`
rcptRoot: Hash256 # ditto
let
adb = cdb.mpt
kdb = cdb.kvt
adb = cdb.ctx.mpt # `Aristo` db
kdb = cdb.ctx.kvt # `Kvt` db
ps = PartStateRef.init adb # Partial DB descriptor
# Fill KVT and collect `proof` data
for (k,v) in jKvp.pairs:
@ -45,7 +48,7 @@ proc preLoadAristoDb(cdb: CoreDbRef; jKvp: JsonNode; num: BlockNumber) =
if key.len == 32:
doAssert key == val.keccakHash.data
if val != @[0x80u8]: # Exclude empty item
proof.add SnapProof(val)
proof.add val
else:
if key[0] == 0:
try:
@ -60,19 +63,62 @@ proc preLoadAristoDb(cdb: CoreDbRef; jKvp: JsonNode; num: BlockNumber) =
discard
check kdb.put(key, val).isOk
# TODO: `getColumn(CtXyy)` does not exists anymore. There is only the generic
# `MPT` left that can be retrieved with `getGeneric()`, optionally with
# argument `clearData=true`
# Install sub-trie roots onto production db
if txRoot.isValid:
doAssert adb.mergeProof(txRoot, VertexID(CtTxs)).isOk
if rcptRoot.isValid:
doAssert adb.mergeProof(rcptRoot, VertexID(CtReceipts)).isOk
doAssert adb.mergeProof(predRoot, VertexID(CtAccounts)).isOk
# Set up production MPT
doAssert adb.mergeProof(proof).isOk
ps.partPut(proof, AutomaticPayload).isOkOr:
raiseAssert info & ": partPut => " & $error
# Handle transaction sub-tree
if txRoot.isValid:
var txs: seq[Transaction]
for (key,pyl) in adb.rightPairs LeafTie(root: ps.partGetSubTree txRoot):
let
inx = key.path.to(UInt256).truncate(uint)
tx = rlp.decode(pyl.rawBlob, Transaction)
#
# FIXME: Is this might be a bug in the test data?
#
# The single item test key is always `128`. For non-single test
# lists, the keys are `1`,`2`, ..,`N`, `128` (some single digit
# number `N`.)
#
# Unless the `128` item value is put at the start of the argument
# list `txs[]` for `persistTransactions()`, the `tracer` module
# will throw an exception at
# `doAssert(transactions.calcTxRoot == header.txRoot)` in the
# function `traceTransactionImpl()`.
#
if (inx and 0x80) != 0:
txs = @[tx] & txs
else:
txs.add tx
cdb.persistTransactions(num, txRoot, txs)
# Handle receipts sub-tree
if rcptRoot.isValid:
var rcpts: seq[Receipt]
for (key,pyl) in adb.rightPairs LeafTie(root: ps.partGetSubTree rcptRoot):
let
inx = key.path.to(UInt256).truncate(uint)
rcpt = rlp.decode(pyl.rawBlob, Receipt)
# FIXME: See comment at `txRoot` section.
if (inx and 0x80) != 0:
rcpts = @[rcpt] & rcpts
else:
rcpts.add rcpt
cdb.persistReceipts(rcptRoot, rcpts)
# Save keys to database
for (rvid,key) in ps.vkPairs:
adb.layersPutKey(rvid, key)
ps.check().isOkOr:
raiseAssert info & ": check => " & $error
#echo ">>> preLoadAristoDb (9)",
# "\n ps\n ", ps.pp(byKeyOk=false,byVidOk=false),
# ""
# -----------
#if true: quit()
# use tracerTestGen.nim to generate additional test data
proc testFixtureImpl(node: JsonNode, testStatusIMPL: var TestStatus, memoryDB: CoreDbRef) =
@ -98,15 +144,25 @@ proc testFixtureImpl(node: JsonNode, testStatusIMPL: var TestStatus, memoryDB: C
let stateDump = dumpBlockState(com, blk)
let blockTrace = traceBlock(com, blk, {DisableState})
# Fix hex representation
for inx in 0 ..< node["txTraces"].len:
for key in ["beforeRoot", "afterRoot"]:
# Here, `node["txTraces"]` stores a string while `txTraces` uses a
# `Hash256` which might expand to a didfferent upper/lower case.
var strHash = txTraces[inx]["stateDiff"][key].getStr.toUpperAscii
if strHash.len < 64:
strHash = '0'.repeat(64 - strHash.len) & strHash
txTraces[inx]["stateDiff"][key] = %(strHash)
check node["txTraces"] == txTraces
check node["stateDump"] == stateDump
check node["blockTrace"] == blockTrace
for i in 0 ..< receipts.len:
let receipt = receipts[i]
let stateDiff = txTraces[i]["stateDiff"]
check receipt["root"].getStr().toLowerAscii() == stateDiff["afterRoot"].getStr().toLowerAscii()
proc testFixtureAristo(node: JsonNode, testStatusIMPL: var TestStatus) =
node.testFixtureImpl(testStatusIMPL, newCoreDbRef AristoDbMemory)