nimbus-eth1/nimbus/db/core_db/base.nim

840 lines
29 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
chronicles,
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 | CoreDbMptRef): 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 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.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(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(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(
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 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(colType, EMPTY_ROOT_HASH, address).value
result = ctx.methods.getMptFn(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().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 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.ifTrackNewApi: debug newApiTxt, api, elapsed, result
proc getColumn*(mpt: CoreDbMptRef): CoreDbColRef =
## Variant of `getColumn()`
##
mpt.setTrackNewApi MptGetColFn
result = mpt.methods.getColFn()
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 key
mpt.ifTrackNewApi:
let col = mpt.methods.getColFn()
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 key
if result.isErr and result.error.error == MptNotFound:
result = CoreDbRc[Blob].ok(EmptyBlob)
mpt.ifTrackNewApi:
let col = mpt.methods.getColFn()
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 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(key, val)
mpt.ifTrackNewApi:
let col = mpt.methods.getColFn()
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 key
mpt.ifTrackNewApi:
let col = mpt.methods.getColFn()
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 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 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 address
acc.ifTrackNewApi: debug newApiTxt, api, elapsed, address, result
proc merge*(
acc: CoreDbAccRef;
account: CoreDbAccount;
): CoreDbRc[void] =
acc.setTrackNewApi AccMergeFn
result = acc.methods.mergeFn 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 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
# ------------------------------------------------------------------------------