mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-10 04:15:54 +00:00
9521582005
An instance of `CoreDbMptRef` is created for and stored in every account - when we are processing blocks and have many accounts in memory, this closure environment takes up hundreds of mb of memory (around block 5M, it is the 4:th largest memory consumer!) - incidentally, this also removes a circular reference in the setup that causes the `AristoCodeDbMptRef` to linger in memory much longer than it has to which is the core reason why it takes so much. The real solution here is to remove the methods indirection entirely, but this PR provides relief until that has been done. Similar treatment is given to some of the other core api functions to avoid circulars there too.
846 lines
30 KiB
Nim
846 lines
30 KiB
Nim
# 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.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
eth/common,
|
|
"../.."/[constants, errors],
|
|
./base/[api_tracking, base_desc]
|
|
|
|
from ../aristo
|
|
import EmptyBlob, PayloadRef, isValid
|
|
|
|
const
|
|
EnableApiTracking = false
|
|
## When enabled, functions using this tracking facility need to import
|
|
## `chronicles`, as well. Tracking is enabled by setting `true` the flags
|
|
## `trackLegaApi` and/or `trackNewApi` in the `CoreDbTxRef` descriptor.
|
|
|
|
EnableApiProfiling = true
|
|
## Enables functions profiling if `EnableApiTracking` is also set `true`.
|
|
|
|
AutoValidateDescriptors = defined(release).not
|
|
## No validatinon needed for production suite.
|
|
|
|
|
|
export
|
|
CoreDbAccount,
|
|
CoreDbApiError,
|
|
CoreDbCaptFlags,
|
|
CoreDbColType,
|
|
CoreDbColRef,
|
|
CoreDbCtxRef,
|
|
CoreDbErrorCode,
|
|
CoreDbErrorRef,
|
|
CoreDbFnInx,
|
|
CoreDbKvtBackendRef,
|
|
CoreDbMptBackendRef,
|
|
CoreDbPayloadRef,
|
|
CoreDbPersistentTypes,
|
|
CoreDbProfListRef,
|
|
CoreDbRef,
|
|
CoreDbType,
|
|
CoreDbAccRef,
|
|
CoreDbCaptRef,
|
|
CoreDbKvtRef,
|
|
CoreDbMptRef,
|
|
CoreDbTxRef,
|
|
PayloadRef
|
|
|
|
const
|
|
CoreDbEnableApiTracking* = EnableApiTracking
|
|
CoreDbEnableApiProfiling* = EnableApiTracking and EnableApiProfiling
|
|
|
|
when AutoValidateDescriptors:
|
|
import ./base/validate
|
|
|
|
# More settings
|
|
const
|
|
logTxt = "CoreDb "
|
|
newApiTxt = logTxt & "API"
|
|
|
|
# Annotation helpers
|
|
{.pragma: apiRaise, gcsafe, raises: [CoreDbApiError].}
|
|
{.pragma: catchRaise, gcsafe, raises: [CatchableError].}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
when EnableApiTracking:
|
|
when EnableApiProfiling:
|
|
{.warning: "*** Provided API profiling for CoreDB (disabled by default)".}
|
|
else:
|
|
{.warning: "*** Provided API logging for CoreDB (disabled by default)".}
|
|
|
|
import
|
|
std/times
|
|
|
|
proc `$`[T](rc: CoreDbRc[T]): string = rc.toStr
|
|
proc `$`(q: set[CoreDbCaptFlags]): string = q.toStr
|
|
proc `$`(t: Duration): string = t.toStr
|
|
proc `$`(e: EthAddress): string = e.toStr
|
|
proc `$`(v: CoreDbColRef): string = v.toStr
|
|
proc `$`(h: Hash256): string = h.toStr
|
|
|
|
template setTrackNewApi(
|
|
w: CoreDbApiTrackRef;
|
|
s: static[CoreDbFnInx];
|
|
code: untyped;
|
|
) =
|
|
## Template with code section that will be discarded if logging is
|
|
## disabled at compile time when `EnableApiTracking` is `false`.
|
|
when EnableApiTracking:
|
|
w.beginNewApi(s)
|
|
code
|
|
const api {.inject,used.} = s
|
|
|
|
template setTrackNewApi*(
|
|
w: CoreDbApiTrackRef;
|
|
s: static[CoreDbFnInx];
|
|
) =
|
|
w.setTrackNewApi(s):
|
|
discard
|
|
|
|
template ifTrackNewApi*(w: CoreDbApiTrackRef; code: untyped) =
|
|
when EnableApiTracking:
|
|
w.endNewApiIf:
|
|
code
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public constructor helper
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc bless*(db: CoreDbRef): CoreDbRef =
|
|
## Verify descriptor
|
|
when AutoValidateDescriptors:
|
|
db.validate
|
|
when CoreDbEnableApiProfiling:
|
|
db.profTab = CoreDbProfListRef.init()
|
|
db
|
|
|
|
proc bless*(db: CoreDbRef; col: CoreDbColRef): CoreDbColRef =
|
|
## Complete sub-module descriptor, fill in `parent` and actvate it.
|
|
col.parent = db
|
|
col.ready = true
|
|
when AutoValidateDescriptors:
|
|
col.validate
|
|
col
|
|
|
|
proc bless*(db: CoreDbRef; kvt: CoreDbKvtRef): CoreDbKvtRef =
|
|
## Complete sub-module descriptor, fill in `parent`.
|
|
kvt.parent = db
|
|
when AutoValidateDescriptors:
|
|
kvt.validate
|
|
kvt
|
|
|
|
proc bless*[T: CoreDbKvtRef |
|
|
CoreDbCtxRef | CoreDbMptRef | CoreDbAccRef |
|
|
CoreDbTxRef | CoreDbCaptRef |
|
|
CoreDbKvtBackendRef | CoreDbMptBackendRef](
|
|
db: CoreDbRef;
|
|
dsc: T;
|
|
): auto =
|
|
## Complete sub-module descriptor, fill in `parent`.
|
|
dsc.parent = db
|
|
when AutoValidateDescriptors:
|
|
dsc.validate
|
|
dsc
|
|
|
|
proc bless*(
|
|
db: CoreDbRef;
|
|
error: CoreDbErrorCode;
|
|
dsc: CoreDbErrorRef;
|
|
): CoreDbErrorRef =
|
|
dsc.parent = db
|
|
dsc.error = error
|
|
when AutoValidateDescriptors:
|
|
dsc.validate
|
|
dsc
|
|
|
|
|
|
proc prettyText*(e: CoreDbErrorRef): string =
|
|
## Pretty print argument object (for tracking use `$$()`)
|
|
if e.isNil: "$ø" else: e.toStr()
|
|
|
|
proc prettyText*(col: CoreDbColRef): string =
|
|
## Pretty print argument object (for tracking use `$$()`)
|
|
if col.isNil or not col.ready: "$ø" else: col.toStr()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public main descriptor methods
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc dbProfData*(db: CoreDbRef): CoreDbProfListRef =
|
|
## Return profiling data table (only available in profiling mode). If
|
|
## available (i.e. non-nil), result data can be organised by the functions
|
|
## available with `aristo_profile`.
|
|
when CoreDbEnableApiProfiling:
|
|
db.profTab
|
|
|
|
proc dbType*(db: CoreDbRef): CoreDbType =
|
|
## Getter, print DB type identifier
|
|
##
|
|
db.setTrackNewApi BaseDbTypeFn
|
|
result = db.dbType
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc parent*[T: CoreDbKvtRef |
|
|
CoreDbColRef |
|
|
CoreDbCtxRef | CoreDbMptRef | CoreDbAccRef |
|
|
CoreDbTxRef |
|
|
CoreDbCaptRef |
|
|
CoreDbErrorRef](
|
|
child: T): CoreDbRef =
|
|
## Getter, common method for all sub-modules
|
|
##
|
|
result = child.parent
|
|
|
|
proc backend*(dsc: CoreDbKvtRef): auto =
|
|
## Getter, retrieves the *raw* backend object for special/localised support.
|
|
##
|
|
dsc.setTrackNewApi AnyBackendFn
|
|
result = dsc.methods.backendFn()
|
|
dsc.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
proc backend*(mpt: CoreDbMptRef): auto =
|
|
## Getter, retrieves the *raw* backend object for special/localised support.
|
|
##
|
|
mpt.setTrackNewApi AnyBackendFn
|
|
result = mpt.methods.backendFn(mpt)
|
|
mpt.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
proc finish*(db: CoreDbRef; eradicate = false) =
|
|
## Database destructor. If the argument `eradicate` is set `false`, the
|
|
## database is left as-is and only the in-memory handlers are cleaned up.
|
|
##
|
|
## Otherwise the destructor is allowed to remove the database. This feature
|
|
## depends on the backend database. Currently, only the `AristoDbRocks` type
|
|
## backend removes the database on `true`.
|
|
##
|
|
db.setTrackNewApi BaseFinishFn
|
|
db.methods.destroyFn eradicate
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
proc `$$`*(e: CoreDbErrorRef): string =
|
|
## Pretty print error symbol, note that this directive may have side effects
|
|
## as it calls a backend function.
|
|
##
|
|
e.setTrackNewApi ErrorPrintFn
|
|
result = e.prettyText()
|
|
e.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public key-value table methods
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc newKvt*(db: CoreDbRef): CoreDbKvtRef =
|
|
## Constructor, will defect on failure.
|
|
##
|
|
## This function subscribes to the common base object shared with other
|
|
## KVT descriptors. Any changes are immediately visible to subscribers.
|
|
## On destruction (when the constructed object gets out of scope), changes
|
|
## are not saved to the backend database but are still cached and available.
|
|
##
|
|
db.setTrackNewApi BaseNewKvtFn
|
|
result = db.methods.newKvtFn().valueOr:
|
|
raiseAssert error.prettyText()
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
proc get*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[Blob] =
|
|
## This function always returns a non-empty `Blob` or an error code.
|
|
kvt.setTrackNewApi KvtGetFn
|
|
result = kvt.methods.getFn key
|
|
kvt.ifTrackNewApi: debug newApiTxt, api, elapsed, key=key.toStr, result
|
|
|
|
proc len*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[int] =
|
|
## This function always returns a non-empty `Blob` or an error code.
|
|
kvt.setTrackNewApi KvtLenFn
|
|
result = kvt.methods.lenFn key
|
|
kvt.ifTrackNewApi: debug newApiTxt, api, elapsed, key=key.toStr, result
|
|
|
|
proc getOrEmpty*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[Blob] =
|
|
## This function sort of mimics the behaviour of the legacy database
|
|
## returning an empty `Blob` if the argument `key` is not found on the
|
|
## database.
|
|
##
|
|
kvt.setTrackNewApi KvtGetOrEmptyFn
|
|
result = kvt.methods.getFn key
|
|
if result.isErr and result.error.error == KvtNotFound:
|
|
result = CoreDbRc[Blob].ok(EmptyBlob)
|
|
kvt.ifTrackNewApi: debug newApiTxt, api, elapsed, key=key.toStr, result
|
|
|
|
proc del*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[void] =
|
|
kvt.setTrackNewApi KvtDelFn
|
|
result = kvt.methods.delFn key
|
|
kvt.ifTrackNewApi: debug newApiTxt, api, elapsed, key=key.toStr, result
|
|
|
|
proc put*(
|
|
kvt: CoreDbKvtRef;
|
|
key: openArray[byte];
|
|
val: openArray[byte];
|
|
): CoreDbRc[void] =
|
|
kvt.setTrackNewApi KvtPutFn
|
|
result = kvt.methods.putFn(key, val)
|
|
kvt.ifTrackNewApi:
|
|
debug newApiTxt, api, elapsed, key=key.toStr, val=val.toLenStr, result
|
|
|
|
proc hasKey*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[bool] =
|
|
## Would be named `contains` if it returned `bool` rather than `Result[]`.
|
|
##
|
|
kvt.setTrackNewApi KvtHasKeyFn
|
|
result = kvt.methods.hasKeyFn key
|
|
kvt.ifTrackNewApi: debug newApiTxt, api, elapsed, key=key.toStr, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public Merkle Patricia Tree context constructors and administration
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc ctx*(db: CoreDbRef): CoreDbCtxRef =
|
|
## Get currently active column context.
|
|
##
|
|
db.setTrackNewApi BaseNewCtxFn
|
|
result = db.methods.newCtxFn()
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
proc ctxFromTx*(
|
|
db: CoreDbRef;
|
|
colState: Hash256;
|
|
colType = CtAccounts;
|
|
): CoreDbRc[CoreDbCtxRef] =
|
|
## Create new context derived from matching transaction of the currently
|
|
## active column context. For the legacy backend, this function always
|
|
## returns the currently active context (i.e. the same as `db.ctx()`.)
|
|
##
|
|
db.setTrackNewApi BaseNewCtxFromTxFn
|
|
result = db.methods.newCtxFromTxFn(colState, colType)
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc swapCtx*(db: CoreDbRef; ctx: CoreDbCtxRef): CoreDbCtxRef =
|
|
## Activate argument context `ctx` and return the previously active column
|
|
## 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()
|
|
## ...
|
|
##
|
|
db.setTrackNewApi BaseSwapCtxFn
|
|
result = db.methods.swapCtxFn ctx
|
|
db.ifTrackNewApi: debug newApiTxt, 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.
|
|
##
|
|
ctx.setTrackNewApi CtxForgetFn
|
|
ctx.methods.forgetFn(ctx)
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public Merkle Patricia Tree sub-trie abstaction management
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc newColumn*(
|
|
ctx: CoreDbCtxRef;
|
|
colType: CoreDbColType;
|
|
colState: Hash256;
|
|
address = Opt.none(EthAddress);
|
|
): CoreDbRc[CoreDbColRef] =
|
|
## Retrieve a new column descriptor.
|
|
##
|
|
## The database is can be viewed as a matrix of rows and columns, potenially
|
|
## with values at their intersection. A row is identified by a lookup key
|
|
## and a column is identified by a state hash.
|
|
##
|
|
## Additionally, any column has a column type attribute given as `colType`
|
|
## argument. Only storage columns also have an address attribute which must
|
|
## be passed as argument `address` when the `colType` argument is `CtStorage`.
|
|
##
|
|
## If the state hash argument `colState` is passed as `EMPTY_ROOT_HASH`, this
|
|
## function always succeeds. The result is the equivalent of a potential
|
|
## column be incarnated later. If the column type is different from
|
|
## `CtStorage` and `CtAccounts`, then the returned column descriptor will be
|
|
## flagged to reset all column data when incarnated as MPT (see `newMpt()`.).
|
|
##
|
|
## Otherwise, the function will fail unless a column with the corresponding
|
|
## argument `colState` identifier exists and can be found on the database.
|
|
## Note that on a single state database like `Aristo`, the requested column
|
|
## might exist but is buried in some history journal (which needs an extra
|
|
## effort to unwrap.)
|
|
##
|
|
## This function is intended to open a column on the database as in:
|
|
## ::
|
|
## proc openAccountLedger(db: CoreDbRef, colState: Hash256): CoreDbMptRef =
|
|
## let col = db.ctx.newColumn(CtAccounts, colState).valueOr:
|
|
## # some error handling
|
|
## return
|
|
## db.getAcc col
|
|
##
|
|
ctx.setTrackNewApi CtxNewColFn
|
|
result = ctx.methods.newColFn(ctx, colType, colState, address)
|
|
ctx.ifTrackNewApi:
|
|
debug newApiTxt, api, elapsed, colType, colState, address, result
|
|
|
|
proc newColumn*(
|
|
ctx: CoreDbCtxRef;
|
|
colState: Hash256;
|
|
address: EthAddress;
|
|
): CoreDbRc[CoreDbColRef] =
|
|
## Shortcut for `ctx.newColumn(CtStorage,colState,some(address))`.
|
|
##
|
|
ctx.setTrackNewApi CtxNewColFn
|
|
result = ctx.methods.newColFn(ctx, CtStorage, colState, Opt.some(address))
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, elapsed, colState, address, result
|
|
|
|
proc newColumn*(
|
|
ctx: CoreDbCtxRef;
|
|
address: EthAddress;
|
|
): CoreDbColRef =
|
|
## Shortcut for `ctx.newColumn(EMPTY_ROOT_HASH,address).value`. The function
|
|
## will throw an exception on error. So the result will always be a valid
|
|
## descriptor.
|
|
##
|
|
ctx.setTrackNewApi CtxNewColFn
|
|
result = ctx.methods.newColFn(
|
|
ctx, CtStorage, EMPTY_ROOT_HASH, Opt.some(address)).valueOr:
|
|
raiseAssert error.prettyText()
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, elapsed, address, result
|
|
|
|
|
|
proc `$$`*(col: CoreDbColRef): string =
|
|
## Pretty print the column descriptor. Note that this directive may have side
|
|
## effects as it calls a backend function.
|
|
##
|
|
#col.setTrackNewApi ColPrintFn
|
|
result = col.prettyText()
|
|
#col.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc stateEmpty*(col: CoreDbColRef): CoreDbRc[bool] =
|
|
## Getter (well, sort of). It retrieves the column state hash for the
|
|
## argument `col` descriptor. The function might fail unless the current
|
|
## state is available (e.g. on `Aristo`.)
|
|
##
|
|
## The value `EMPTY_ROOT_HASH` is returned on the void `col` descriptor
|
|
## argument `CoreDbColRef(nil)`.
|
|
##
|
|
col.setTrackNewApi BaseColStateEmptyFn
|
|
result = block:
|
|
if not col.isNil and col.ready:
|
|
col.parent.methods.colStateEmptyFn col
|
|
else:
|
|
ok true
|
|
# Note: tracker will be silent if `vid` is NIL
|
|
col.ifTrackNewApi: debug newApiTxt, api, elapsed, col, result
|
|
|
|
proc state*(col: CoreDbColRef): CoreDbRc[Hash256] =
|
|
## Getter (well, sort of). It retrieves the column state hash for the
|
|
## argument `col` descriptor. The function might fail unless the current
|
|
## state is available (e.g. on `Aristo`.)
|
|
##
|
|
## The value `EMPTY_ROOT_HASH` is returned on the void `col` descriptor
|
|
## argument `CoreDbColRef(nil)`.
|
|
##
|
|
col.setTrackNewApi BaseColStateFn
|
|
result = block:
|
|
if not col.isNil and col.ready:
|
|
col.parent.methods.colStateFn col
|
|
else:
|
|
ok EMPTY_ROOT_HASH
|
|
# Note: tracker will be silent if `vid` is NIL
|
|
col.ifTrackNewApi: debug newApiTxt, api, elapsed, col, result
|
|
|
|
proc stateEmptyOrVoid*(col: CoreDbColRef): bool =
|
|
## Convenience wrapper, returns `true` where `stateEmpty()` would fail.
|
|
col.stateEmpty.valueOr: true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public Merkle Patricia Tree, hexary trie constructors
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc getMpt*(
|
|
ctx: CoreDbCtxRef;
|
|
col: CoreDbColRef;
|
|
): CoreDbRc[CoreDbMptRef] =
|
|
## Get an MPT sub-trie view.
|
|
##
|
|
## If the `col` argument descriptor was created for an `EMPTY_ROOT_HASH`
|
|
## column state of type different form `CtStorage` or `CtAccounts`, all
|
|
## column will be flushed. There is no need to hold the `col` argument for
|
|
## later use. It can always be rerieved for this particular MPT using the
|
|
## function `getColumn()`.
|
|
##
|
|
ctx.setTrackNewApi CtxGetMptFn
|
|
result = ctx.methods.getMptFn(ctx, col)
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, elapsed, col, result
|
|
|
|
proc getMpt*(
|
|
ctx: CoreDbCtxRef;
|
|
colType: CoreDbColType;
|
|
address = Opt.none(EthAddress);
|
|
): CoreDbMptRef =
|
|
## Shortcut for `getMpt(col)` where the `col` argument is
|
|
## `db.getColumn(colType,EMPTY_ROOT_HASH).value`. This function will always
|
|
## return a non-nil descriptor or throw an exception.
|
|
##
|
|
ctx.setTrackNewApi CtxGetMptFn
|
|
let col = ctx.methods.newColFn(ctx, colType, EMPTY_ROOT_HASH, address).value
|
|
result = ctx.methods.getMptFn(ctx, col).valueOr:
|
|
raiseAssert error.prettyText()
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, colType, elapsed
|
|
|
|
|
|
proc getMpt*(acc: CoreDbAccRef): CoreDbMptRef =
|
|
## Variant of `getMpt()`, will defect on failure.
|
|
##
|
|
## The needed sub-trie information is taken/implied from the current `acc`
|
|
## argument.
|
|
##
|
|
acc.setTrackNewApi AccToMptFn
|
|
result = acc.methods.getMptFn(acc).valueOr:
|
|
raiseAssert error.prettyText()
|
|
acc.ifTrackNewApi:
|
|
let colState = result.methods.getColFn()
|
|
debug newApiTxt, api, elapsed, colState
|
|
|
|
|
|
proc getAcc*(
|
|
ctx: CoreDbCtxRef;
|
|
col: CoreDbColRef;
|
|
): CoreDbRc[CoreDbAccRef] =
|
|
## Accounts trie constructor, will defect on failure.
|
|
##
|
|
## Example:
|
|
## ::
|
|
## let col = db.getColumn(CtAccounts,<some-hash>).valueOr:
|
|
## ... # No node available with <some-hash>
|
|
## return
|
|
##
|
|
## let acc = db.getAccMpt(col)
|
|
## ... # Was not the state root for the accounts column
|
|
## return
|
|
##
|
|
## This function works similar to `getMpt()` for handling accounts.
|
|
##
|
|
ctx.setTrackNewApi CtxGetAccFn
|
|
result = ctx.methods.getAccFn(ctx, col)
|
|
ctx.ifTrackNewApi: debug newApiTxt, api, elapsed, col, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public common methods for all hexary trie databases (`mpt`, or `acc`)
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc getColumn*(acc: CoreDbAccRef): CoreDbColRef =
|
|
## Getter, result is not `nil`
|
|
##
|
|
acc.setTrackNewApi AccGetColFn
|
|
result = acc.methods.getColFn(acc)
|
|
acc.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc getColumn*(mpt: CoreDbMptRef): CoreDbColRef =
|
|
## Variant of `getColumn()`
|
|
##
|
|
mpt.setTrackNewApi MptGetColFn
|
|
result = mpt.methods.getColFn(mpt)
|
|
mpt.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public generic hexary trie database methods
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc fetch*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[Blob] =
|
|
## Fetch data from the argument `mpt`. The function always returns a
|
|
## non-empty `Blob` or an error code.
|
|
##
|
|
mpt.setTrackNewApi MptFetchFn
|
|
result = mpt.methods.fetchFn(mpt, key)
|
|
mpt.ifTrackNewApi:
|
|
let col = mpt.methods.getColFn(mpt)
|
|
debug newApiTxt, api, elapsed, col, key=key.toStr, result
|
|
|
|
proc fetchOrEmpty*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[Blob] =
|
|
## This function returns an empty `Blob` if the argument `key` is not found
|
|
## on the database.
|
|
##
|
|
mpt.setTrackNewApi MptFetchOrEmptyFn
|
|
result = mpt.methods.fetchFn(mpt, key)
|
|
if result.isErr and result.error.error == MptNotFound:
|
|
result = CoreDbRc[Blob].ok(EmptyBlob)
|
|
mpt.ifTrackNewApi:
|
|
let col = mpt.methods.getColFn(mpt)
|
|
debug newApiTxt, api, elapsed, col, key=key.toStr, result
|
|
|
|
proc delete*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[void] =
|
|
mpt.setTrackNewApi MptDeleteFn
|
|
result = mpt.methods.deleteFn(mpt, key)
|
|
mpt.ifTrackNewApi:
|
|
let col = mpt.methods.getColFn()
|
|
debug newApiTxt, api, elapsed, col, key=key.toStr, result
|
|
|
|
proc merge*(
|
|
mpt: CoreDbMptRef;
|
|
key: openArray[byte];
|
|
val: openArray[byte];
|
|
): CoreDbRc[void] =
|
|
mpt.setTrackNewApi MptMergeFn
|
|
result = mpt.methods.mergeFn(mpt, key, val)
|
|
mpt.ifTrackNewApi:
|
|
let col = mpt.methods.getColFn(mpt)
|
|
debug newApiTxt, api, elapsed, col, key=key.toStr, val=val.toLenStr, result
|
|
|
|
proc hasPath*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[bool] =
|
|
## This function would be named `contains()` if it returned `bool` rather
|
|
## than a `Result[]`.
|
|
##
|
|
mpt.setTrackNewApi MptHasPathFn
|
|
result = mpt.methods.hasPathFn(mpt, key)
|
|
mpt.ifTrackNewApi:
|
|
let col = mpt.methods.getColFn(mpt)
|
|
debug newApiTxt, api, elapsed, col, key=key.toStr, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public trie database methods for accounts
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc fetch*(acc: CoreDbAccRef; address: EthAddress): CoreDbRc[CoreDbAccount] =
|
|
## Fetch data from the argument `acc`.
|
|
##
|
|
acc.setTrackNewApi AccFetchFn
|
|
result = acc.methods.fetchFn(acc, address)
|
|
acc.ifTrackNewApi:
|
|
let storage = if result.isErr: "n/a" else: result.value.storage.prettyText()
|
|
debug newApiTxt, api, elapsed, address, storage, result
|
|
|
|
|
|
proc delete*(acc: CoreDbAccRef; address: EthAddress): CoreDbRc[void] =
|
|
acc.setTrackNewApi AccDeleteFn
|
|
result = acc.methods.deleteFn(acc, address)
|
|
acc.ifTrackNewApi: debug newApiTxt, api, elapsed, address, result
|
|
|
|
proc stoDelete*(acc: CoreDbAccRef; address: EthAddress): CoreDbRc[void] =
|
|
## Recursively delete all data elements from the storage trie associated to
|
|
## the account identified by the argument `address`. After successful run,
|
|
## the storage trie will be empty.
|
|
##
|
|
## Caveat:
|
|
## This function has no effect on the legacy backend so it must not be
|
|
## relied upon in general. On the legacy backend, storage tries might be
|
|
## shared by several accounts whereas they are unique on the `Aristo`
|
|
## backend.
|
|
##
|
|
acc.setTrackNewApi AccStoDeleteFn
|
|
result = acc.methods.stoDeleteFn(acc, address)
|
|
acc.ifTrackNewApi: debug newApiTxt, api, elapsed, address, result
|
|
|
|
|
|
proc merge*(
|
|
acc: CoreDbAccRef;
|
|
account: CoreDbAccount;
|
|
): CoreDbRc[void] =
|
|
acc.setTrackNewApi AccMergeFn
|
|
result = acc.methods.mergeFn(acc, account)
|
|
acc.ifTrackNewApi:
|
|
let address = account.address
|
|
debug newApiTxt, api, elapsed, address, result
|
|
|
|
|
|
proc hasPath*(acc: CoreDbAccRef; address: EthAddress): CoreDbRc[bool] =
|
|
## Would be named `contains` if it returned `bool` rather than `Result[]`.
|
|
##
|
|
acc.setTrackNewApi AccHasPathFn
|
|
result = acc.methods.hasPathFn(acc, address)
|
|
acc.ifTrackNewApi: debug newApiTxt, api, elapsed, address, result
|
|
|
|
|
|
proc recast*(statement: CoreDbAccount): CoreDbRc[Account] =
|
|
## Convert the argument `statement` to the portable Ethereum representation
|
|
## of an account statement. This conversion may fail if the storage colState
|
|
## hash (see `hash()` above) is currently unavailable.
|
|
##
|
|
## Note:
|
|
## With the legacy backend, this function always succeeds.
|
|
##
|
|
let storage = statement.storage
|
|
storage.setTrackNewApi EthAccRecastFn
|
|
let rc =
|
|
if storage.isNil or not storage.ready: CoreDbRc[Hash256].ok(EMPTY_ROOT_HASH)
|
|
else: storage.parent.methods.colStateFn storage
|
|
result =
|
|
if rc.isOk:
|
|
ok Account(
|
|
nonce: statement.nonce,
|
|
balance: statement.balance,
|
|
codeHash: statement.codeHash,
|
|
storageRoot: rc.value)
|
|
else:
|
|
err(rc.error)
|
|
storage.ifTrackNewApi: debug newApiTxt, api, elapsed, storage, result
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public transaction related methods
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc level*(db: CoreDbRef): int =
|
|
## Retrieve transaction level (zero if there is no pending transaction).
|
|
##
|
|
db.setTrackNewApi BaseLevelFn
|
|
result = db.methods.levelFn()
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc persistent*(
|
|
db: CoreDbRef;
|
|
): CoreDbRc[void] =
|
|
## For the legacy database, this function has no effect and succeeds always.
|
|
## It will nevertheless return a discardable error if there is a pending
|
|
## transaction (i.e. `db.level() == 0`.)
|
|
##
|
|
## Otherwise, cached data from the `Kvt`, `Mpt`, and `Acc` descriptors are
|
|
## stored on the persistent database (if any). This requires that that there
|
|
## is no transaction pending.
|
|
##
|
|
db.setTrackNewApi BasePersistentFn
|
|
result = db.methods.persistentFn Opt.none(BlockNumber)
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc persistent*(
|
|
db: CoreDbRef;
|
|
blockNumber: BlockNumber;
|
|
): CoreDbRc[void] {.discardable.} =
|
|
## Variant of `persistent()` which stores a block number within the recovery
|
|
## journal record. This recoed will be addressable by the `blockNumber` (e.g.
|
|
## for recovery.) The argument block number `blockNumber` must be greater
|
|
## than all previously stored block numbers.
|
|
##
|
|
## The function is intended to be used in a way so hat the argument block
|
|
## number `blockNumber` is associated with the state root to be recovered
|
|
## from a particular journal entry. This means that the correct block number
|
|
## will be the one of the state *before* a state change takes place. Using
|
|
## it that way, `pesistent()` must only be run after some blocks were fully
|
|
## executed.
|
|
##
|
|
## Example:
|
|
## ::
|
|
## # Save block number for the current state
|
|
## let stateBlockNumber = db.getCanonicalHead().blockNumber
|
|
## ..
|
|
## # Process blocks
|
|
## ..
|
|
## db.persistent(stateBlockNumber)
|
|
##
|
|
db.setTrackNewApi BasePersistentFn
|
|
result = db.methods.persistentFn Opt.some(blockNumber)
|
|
db.ifTrackNewApi: debug newApiTxt, api, elapsed, blockNumber, result
|
|
|
|
proc newTransaction*(db: CoreDbRef): CoreDbTxRef =
|
|
## Constructor
|
|
##
|
|
db.setTrackNewApi BaseNewTxFn
|
|
result = db.methods.beginFn()
|
|
db.ifTrackNewApi:
|
|
debug newApiTxt, api, elapsed, newLevel=db.methods.levelFn()
|
|
|
|
|
|
proc level*(tx: CoreDbTxRef): int =
|
|
## Print positive transaction level for argument `tx`
|
|
##
|
|
tx.setTrackNewApi TxLevelFn
|
|
result = tx.methods.levelFn()
|
|
tx.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc commit*(tx: CoreDbTxRef) =
|
|
tx.setTrackNewApi TxCommitFn:
|
|
let prvLevel {.used.} = tx.methods.levelFn()
|
|
tx.methods.commitFn()
|
|
tx.ifTrackNewApi: debug newApiTxt, api, elapsed, prvLevel
|
|
|
|
proc rollback*(tx: CoreDbTxRef) =
|
|
tx.setTrackNewApi TxRollbackFn:
|
|
let prvLevel {.used.} = tx.methods.levelFn()
|
|
tx.methods.rollbackFn()
|
|
tx.ifTrackNewApi: debug newApiTxt, api, elapsed, prvLevel
|
|
|
|
proc dispose*(tx: CoreDbTxRef) =
|
|
tx.setTrackNewApi TxDisposeFn:
|
|
let prvLevel {.used.} = tx.methods.levelFn()
|
|
tx.methods.disposeFn()
|
|
tx.ifTrackNewApi: debug newApiTxt, api, elapsed, prvLevel
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public tracer methods
|
|
# ------------------------------------------------------------------------------
|
|
|
|
when false: # currently disabled
|
|
proc newCapture*(
|
|
db: CoreDbRef;
|
|
flags: set[CoreDbCaptFlags] = {};
|
|
): 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()`.
|
|
##
|
|
## 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.ifTrackNewApi: debug newApiTxt, 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.)
|
|
##
|
|
## 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 newApiTxt, api, elapsed
|
|
|
|
proc logDb*(cp: CoreDbCaptRef): TableRef[Blob,Blob] =
|
|
## Getter, returns the logger table for the overlay tracer database.
|
|
##
|
|
## 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 newApiTxt, api, elapsed
|
|
|
|
proc flags*(cp: CoreDbCaptRef):set[CoreDbCaptFlags] =
|
|
## Getter
|
|
##
|
|
cp.setTrackNewApi CptFlagsFn
|
|
result = cp.methods.getFlagsFn()
|
|
cp.ifTrackNewApi: debug newApiTxt, api, elapsed, result
|
|
|
|
proc forget*(cp: CoreDbCaptRef) =
|
|
## Explicitely stop recording the current tracer instance and reset to
|
|
## previous level.
|
|
##
|
|
cp.setTrackNewApi CptForgetFn
|
|
cp.methods.forgetFn()
|
|
cp.ifTrackNewApi: debug newApiTxt, api, elapsed
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|