Provide `Aristo` backend for `CoreDb` (#1895)

This commit is contained in:
Jordan Hrycaj 2023-11-16 20:53:44 +00:00 committed by GitHub
parent c47f021596
commit 4825ab1566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1297 additions and 3 deletions

View File

@ -0,0 +1,285 @@
# Nimbus
# Copyright (c) 2023 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,
results,
"../.."/[aristo, aristo/aristo_desc, aristo/aristo_init/memory_only],
"../.."/[kvt, kvt/kvt_desc, kvt/kvt_init/memory_only],
".."/[base, base/base_desc],
./aristo_db/[common_desc, handlers_aristo, handlers_kvt]
# Annotation helpers
{.pragma: noRaise, gcsafe, raises: [].}
{.pragma: rlpRaise, gcsafe, raises: [AristoApiRlpError].}
export
AristoApiRlpError,
AristoCoreDbKvtBE,
memory_only
type
AristoCoreDbRef* = ref object of CoreDbRef
## Main descriptor
kdbBase: KvtBaseRef ## Kvt subsystem
adbBase: AristoBaseRef ## Aristo subsystem
AristoCoreDbBE = ref object of CoreDbBackendRef
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template valueOrApiError[U,V](rc: Result[U,V]; info: static[string]): U =
rc.valueOr: raise (ref AristoApiRlpError)(msg: info)
func notImplemented[T](
_: typedesc[T];
db: AristoCoreDbRef;
info: string;
): CoreDbRc[T] {.gcsafe.} =
## Applies only to `Aristo` methods
err((VertexID(0),aristo.NotImplemented).toError(db, info))
# ------------------------------------------------------------------------------
# Private call back functions (too large for embedding to maintain)
# ------------------------------------------------------------------------------
iterator kvtPairs(
T: typedesc;
dsc: CoreDxKvtRef;
info: static[string];
): (Blob,Blob) =
let p = dsc.kvt.forkTop.valueOrApiError info
defer: discard p.forget()
dsc.methods.pairsIt = iterator(): (Blob, Blob) =
for (n,k,v) in T.walkPairs p:
yield (k,v)
iterator mptReplicate(
T: typedesc;
dsc: CoreDxMptRef;
info: static[string];
): (Blob,Blob)
{.rlpRaise.} =
let p = dsc.mpt.forkTop.valueOrApiError info
defer: discard p.forget()
let root = dsc.root
for (vid,key,vtx,node) in T.replicate p:
if key.len == 32:
yield (@key, node.encode)
elif vid == root:
yield (@(key.to(Hash256).data), node.encode)
# ------------------------------------------------------------------------------
# Private tx and base methods
# ------------------------------------------------------------------------------
proc txMethods(
db: AristoCoreDbRef;
aTx: AristoTxRef;
kTx: KvtTxRef;
): CoreDbTxFns =
## To be constructed by some `CoreDbBaseFns` function
CoreDbTxFns(
commitFn: proc(ignore: bool): CoreDbRc[void] =
const info = "commitFn()"
? aTx.commit.toVoidRc(db, info)
? kTx.commit.toVoidRc(db, info)
ok(),
rollbackFn: proc(): CoreDbRc[void] =
const info = "rollbackFn()"
? aTx.rollback.toVoidRc(db, info)
? kTx.rollback.toVoidRc(db, info)
ok(),
disposeFn: proc(): CoreDbRc[void] =
const info = "disposeFn()"
if aTx.isTop: ? aTx.rollback.toVoidRc(db, info)
if kTx.isTop: ? kTx.rollback.toVoidRc(db, info)
ok(),
safeDisposeFn: proc(): CoreDbRc[void] =
const info = "safeDisposeFn()"
if aTx.isTop: ? aTx.rollback.toVoidRc(db, info)
if kTx.isTop: ? kTx.rollback.toVoidRc(db, info)
ok())
proc baseMethods(
db: AristoCoreDbRef;
A: typedesc;
K: typedesc;
): CoreDbBaseFns =
let db = db
CoreDbBaseFns(
backendFn: proc(): CoreDbBackendRef =
db.bless(AristoCoreDbBE()),
destroyFn: proc(flush: bool) =
db.adbBase.destroy(flush)
db.kdbBase.destroy(flush),
vidHashFn: proc(vid: CoreDbVidRef; update: bool): CoreDbRc[Hash256] =
db.adbBase.getHash(vid, update, "vidHashFn()"),
errorPrintFn: proc(e: CoreDbErrorRef): string =
e.errorPrint(),
legacySetupFn: proc() =
discard,
getRootFn: proc(root: Hash256; createOk: bool): CoreDbRc[CoreDbVidRef] =
db.adbBase.getVid(root, createOk, "getRootFn()"),
newKvtFn: proc(saveMode: CoreDbSaveFlags): CoreDbRc[CoreDxKvtRef] =
db.kdbBase.gc()
let dsc = ? db.kdbBase.newKvtHandler(saveMode, "newKvtFn()")
when K is MemBackendRef:
dsc.methods.pairsIt = iterator(): (Blob, Blob) =
for (n,k,v) in K.kvtPairs dsc:
yield (k,v)
ok(dsc),
newMptFn: proc(
root: CoreDbVidRef;
prune: bool;
saveMode: CoreDbSaveFlags;
): CoreDbRc[CoreDxMptRef] =
db.kdbBase.gc()
let dsc = ? db.adbBase.newMptHandler(root, prune, saveMode, "newMptFn()")
when K is MemBackendRef:
dsc.methods.replicateIt = iterator: (Blob,Blob) {.rlpRaise.} =
for w in T.mptReplicate(dsc, "forkTop() for replicateIt()"):
yield w
ok(dsc),
newAccFn: proc(
root: CoreDbVidRef;
prune: bool;
saveMode: CoreDbSaveFlags;
): CoreDbRc[CoreDxAccRef] =
db.kdbBase.gc()
ok(? db.adbBase.newAccHandler(prune, saveMode, "newAccFn()")),
beginFn: proc(): CoreDbRc[CoreDxTxRef] =
const info = "beginFn()"
let
aTx = ? db.adbBase.txBegin(info)
kTx = ? db.kdbBase.txBegin(info)
ok(db.bless CoreDxTxRef(methods: db.txMethods(aTx, kTx))),
getIdFn: proc(): CoreDbRc[CoreDxTxID] =
CoreDxTxID.notImplemented(db, "getIdFn()"),
captureFn: proc(flags: set[CoreDbCaptFlags]): CoreDbRc[CoreDxCaptRef] =
CoreDxCaptRef.notImplemented(db, "capture()"))
# ------------------------------------------------------------------------------
# Private constructor helpers
# ------------------------------------------------------------------------------
proc create(
dbType: CoreDbType;
kdb: KvtDbRef;
K: typedesc;
adb: AristoDbRef;
A: typedesc;
): CoreDbRef =
## Constructor helper
# Local extensions
var db = AristoCoreDbRef()
db.adbBase = AristoBaseRef.init(db, adb)
db.kdbBase = KvtBaseRef.init(db, kdb)
# Base descriptor
db.dbType = dbType
db.methods = db.baseMethods(A,K)
db.bless
proc init(
dbType: CoreDbType;
K: typedesc;
A: typedesc;
qlr: QidLayoutRef;
): CoreDbRef =
dbType.create(KvtDbRef.init(K), K, AristoDbRef.init(A, qlr), A)
proc init(
dbType: CoreDbType;
K: typedesc;
A: typedesc;
): CoreDbRef =
dbType.create(KvtDbRef.init(K), K, AristoDbRef.init(A), A)
# ------------------------------------------------------------------------------
# Public constructor helpers
# ------------------------------------------------------------------------------
proc init*(
dbType: CoreDbType;
K: typedesc;
A: typedesc;
path: string;
qlr: QidLayoutRef;
): CoreDbRef =
dbType.create(
KvtDbRef.init(K, path).expect "Kvt/RocksDB init() failed", K,
AristoDbRef.init(A, path, qlr).expect "Aristo/RocksDB init() failed", A)
proc init*(
dbType: CoreDbType;
K: typedesc;
A: typedesc;
path: string;
): CoreDbRef =
dbType.create(
KvtDbRef.init(K, path).expect "Kvt/RocksDB init() failed", K,
AristoDbRef.init(A, path).expect "Aristo/RocksDB init() failed", A)
# ------------------------------------------------------------------------------
# Public constructor
# ------------------------------------------------------------------------------
proc newAristoMemoryCoreDbRef*(qlr: QidLayoutRef): CoreDbRef =
AristoDbMemory.init(kvt.MemBackendRef, aristo.MemBackendRef, qlr)
proc newAristoMemoryCoreDbRef*(): CoreDbRef =
AristoDbMemory.init(kvt.MemBackendRef, aristo.MemBackendRef)
proc newAristoVoidCoreDbRef*(): CoreDbRef =
AristoDbVoid.init(kvt.VoidBackendRef, aristo.VoidBackendRef)
# ------------------------------------------------------------------------------
# Public helpers for direct backend access
# ------------------------------------------------------------------------------
func toAristo*(be: CoreDbKvtBackendRef): KvtDbRef =
if be.parent.isAristo:
return be.AristoCoreDbKvtBE.kdb
func toAristo*(be: CoreDbMptBackendRef): AristoDbRef =
if be.parent.isAristo:
return be.AristoCoreDbMptBE.adb
func toAristo*(be: CoreDbAccBackendRef): AristoDbRef =
if be.parent.isAristo:
return be.AristoCoreDbAccBE.adb
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -0,0 +1,53 @@
# Nimbus
# Copyright (c) 2023 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
../../../../errors,
"../../.."/[aristo, kvt],
../../base/base_desc
type
AristoApiRlpError* = object of CoreDbApiError
## For re-routing exceptions in iterator closure
AristoCoreDbError* = ref object of CoreDbErrorRef
## Error return code
ctx*: string ## Context where the exception or error occured
case isAristo*: bool
of true:
aVid*: VertexID
aErr*: AristoError
else:
kErr*: KvtError
# ------------------------------------------------------------------------------
# Public helpers
# ------------------------------------------------------------------------------
func isAristo*(be: CoreDbRef): bool =
be.dbType in {AristoDbMemory, AristoDbRocks}
func errorPrint*(e: CoreDbErrorRef): string =
if not e.isNil:
let e = e.AristoCoreDbError
result = if e.isAristo: "Aristo: " else: "Kvt: "
result &= "ctx=\"" & $e.ctx & "\"" & ", "
if e.isAristo:
if e.aVid.isValid:
result &= "vid=\"" & $e.aVid & "\"" & ", "
result &= "error=\"" & $e.aErr & "\""
else:
result &= "error=\"" & $e.kErr & "\""
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -0,0 +1,586 @@
# Nimbus
# Copyright (c) 2023 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,
results,
../../../aristo,
../../../aristo/aristo_desc,
../../base,
../../base/base_desc,
./common_desc
type
AristoBaseRef* = ref object
parent: CoreDbRef ## Opaque top level descriptor
adb: AristoDbRef ## Aristo MPT database
gq: seq[AristoChildDbRef] ## Garbage queue, deferred disposal
AristoChildDbRef = ref AristoChildDbObj
AristoChildDbObj = object
## Sub-handle for triggering destructor when it goes out of scope
base: AristoBaseRef ## Local base descriptor
root: VertexID ## State root
prune: bool ## Currently unused
mpt: AristoDbRef ## Descriptor, may be copy of `base.adb`
saveMode: CoreDbSaveFlags ## When to store/discard
AristoCoreDxMptRef = ref object of CoreDxMptRef
## Some extendion to recover embedded state
ctx: AristoChildDbRef ## Embedded state, typical var name: `cMpt`
AristoCoreDbVid* = ref object of CoreDbVidRef
## Vertex ID wrapper, optinally with *MPT* context
ctx: AristoDbRef ## Optional *MPT* context, might be `nil`
aVid: VertexID ## Refers to root vertex
createOk: bool ## Create new root vertex when appropriate
expHash: Hash256 ## Deferred validation
AristoCoreDbMptBE* = ref object of CoreDbMptBackendRef
adb*: AristoDbRef
AristoCoreDbAccBE* = ref object of CoreDbAccBackendRef
adb*: AristoDbRef
logScope:
topics = "aristo-hdl"
proc gc*(base: AristoBaseRef) {.gcsafe.}
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template logTxt(info: static[string]): static[string] =
"CoreDb/adb " & info
func isValid(vid: CoreDbVidRef): bool =
not vid.isNil and vid.ready
func to*(vid: CoreDbVidRef; T: type VertexID): T =
if vid.isValid:
return vid.AristoCoreDbVid.aVid
func createOk(vid: CoreDbVidRef): bool =
if vid.isValid:
return vid.AristoCoreDbVid.createOk
# --------------
func toCoreDbAccount(
cMpt: AristoChildDbRef;
acc: AristoAccount;
): CoreDbAccount =
let db = cMpt.base.parent
CoreDbAccount(
nonce: acc.nonce,
balance: acc.balance,
codeHash: acc.codeHash,
storageVid: db.bless AristoCoreDbVid(ctx: cMpt.mpt, aVid: acc.storageID))
func toPayloadRef(acc: CoreDbAccount): PayloadRef =
PayloadRef(
pType: AccountData,
account: AristoAccount(
nonce: acc.nonce,
balance: acc.balance,
storageID: acc.storageVid.to(VertexID),
codeHash: acc.codeHash))
# --------------
func toErrorImpl(
e: AristoError;
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbErrorRef =
db.bless(error, AristoCoreDbError(
ctx: info,
isAristo: true,
aErr: e))
func toErrorImpl(
e: (VertexID,AristoError);
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbErrorRef =
db.bless(error, AristoCoreDbError(
ctx: info,
isAristo: true,
aVid: e[0],
aErr: e[1]))
func toRcImpl[T](
rc: Result[T,(VertexID,AristoError)];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[T] =
if rc.isOk:
when T is void:
return ok()
else:
return ok(rc.value)
err rc.error.toErrorImpl(db, info, error)
func toRcImpl[T](
rc: Result[T,AristoError];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[T] =
if rc.isOk:
when T is void:
return ok()
else:
return ok(rc.value)
err((VertexID(0),rc.error).toErrorImpl(db, info, error))
func toVoidRcImpl[T](
rc: Result[T,(VertexID,AristoError)];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[void] =
if rc.isOk:
return ok()
err rc.error.toErrorImpl(db, info, error)
func toVoidRcImpl[T](
rc: Result[T,AristoError];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[void] =
if rc.isOk:
return ok()
err((VertexID(0),rc.error).toErrorImpl(db, info, error))
# ------------------------------------------------------------------------------
# Private call back functions (too large for keeping inline)
# ------------------------------------------------------------------------------
proc finish(
cMpt: AristoChildDbRef;
info: static[string];
): CoreDbRc[void] =
## Hexary trie destructor to be called automagically when the argument
## wrapper gets out of scope.
let
base = cMpt.base
db = base.parent
result = ok()
if cMpt.mpt != base.adb:
let rc = cMpt.mpt.forget()
if rc.isErr:
result = err(rc.error.toErrorImpl(db, info))
cMpt.mpt = AristoDbRef(nil) # disables `=destroy`
if cMpt.saveMode == AutoSave:
if base.adb.level == 0:
let rc = base.adb.stow(persistent = true)
if rc.isErr:
result = err(rc.error.toErrorImpl(db, info))
proc `=destroy`(cMpt: var AristoChildDbObj) =
## Auto destructor
if not cMpt.mpt.isNil:
# Add to destructor batch queue unless direct reference
if cMpt.mpt != cMpt.base.adb or
cMpt.saveMode == AutoSave:
cMpt.base.gq.add AristoChildDbRef(
base: cMpt.base,
mpt: cMpt.mpt,
saveMode: cMpt.saveMode)
# -------------------------------
proc mptFetch(
cMpt: AristoChildDbRef;
k: openArray[byte];
info: static[string];
): CoreDbRc[Blob] =
let
db = cMpt.base.parent
rc = cMpt.mpt.fetchPayload(cMpt.root, k)
if rc.isOk:
return cMpt.mpt.serialise(rc.value).toRcImpl(db, info)
if rc.error[1] != FetchPathNotFound:
return err(rc.error.toErrorImpl(db, info))
err rc.error.toErrorImpl(db, info, MptNotFound)
proc mptMerge(
cMpt: AristoChildDbRef;
k: openArray[byte];
v: openArray[byte];
info: static[string];
): CoreDbRc[void] =
let rc = cMpt.mpt.merge(cMpt.root, k, v)
if rc.isErr:
return err(rc.error.toErrorImpl(cMpt.base.parent, info))
ok()
proc mptDelete(
cMpt: AristoChildDbRef;
k: openArray[byte];
info: static[string];
): CoreDbRc[void] =
let rc = cMpt.mpt.delete(cMpt.root, k)
if rc.isErr:
return err(rc.error.toErrorImpl(cMpt.base.parent, info))
ok()
# -------------------------------
proc accFetch(
cMpt: AristoChildDbRef;
address: EthAddress;
info: static[string];
): CoreDbRc[CoreDbAccount] =
let
db = cMpt.base.parent
pyl = block:
let rc = cMpt.mpt.fetchPayload(cMpt.root, address.keccakHash.data)
if rc.isOk:
rc.value
elif rc.error[1] != FetchPathNotFound:
return err(rc.error.toErrorImpl(db, info))
else:
return err(rc.error.toErrorImpl(db, info, AccNotFound))
if pyl.pType != AccountData:
let vePair = (pyl.account.storageID, PayloadTypeUnsupported)
return err(vePair.toErrorImpl(db, info & "/" & $pyl.pType))
ok cMpt.toCoreDbAccount pyl.account
proc accMerge(
cMpt: AristoChildDbRef;
address: EthAddress;
acc: CoreDbAccount;
info: static[string];
): CoreDbRc[void] =
let
key = address.keccakHash.data
val = acc.toPayloadRef()
rc = cMpt.mpt.merge(cMpt.root, key, val)
if rc.isErr:
return rc.toVoidRcImpl(cMpt.base.parent, info)
ok()
proc accDelete(
cMpt: AristoChildDbRef;
address: EthAddress;
info: static[string];
): CoreDbRc[void] =
let
key = address.keccakHash.data
rc = cMpt.mpt.delete(cMpt.root, key)
if rc.isErr:
return rc.toVoidRcImpl(cMpt.base.parent, info)
ok()
# ------------------------------------------------------------------------------
# Private database methods function tables
# ------------------------------------------------------------------------------
proc mptMethods(cMpt: AristoChildDbRef): CoreDbMptFns =
## Hexary trie database handlers
let db = cMpt.base.parent
CoreDbMptFns(
backendFn: proc(): CoreDbMptBackendRef =
AristoCoreDbMptBE(adb: cMpt.mpt),
fetchFn: proc(k: openArray[byte]): CoreDbRc[Blob] =
cMpt.mptFetch(k, "fetchFn()"),
deleteFn: proc(k: openArray[byte]): CoreDbRc[void] =
cMpt.mptDelete(k, "deleteFn()"),
mergeFn: proc(k: openArray[byte]; v: openArray[byte]): CoreDbRc[void] =
cMpt.mptMerge(k, v, "mergeFn()"),
hasPathFn: proc(k: openArray[byte]): CoreDbRc[bool] =
cMpt.mpt.hasPath(cMpt.root, k).toRcImpl(db, "hasPathFn()"),
rootVidFn: proc(): CoreDbVidRef =
var w = AristoCoreDbVid(ctx: cMpt.mpt, aVid: cMpt.root)
db.bless(w),
isPruningFn: proc(): bool =
cMpt.prune,
destroyFn: proc(saveMode: CoreDbSaveFlags): CoreDbRc[void] =
cMpt.base.gc()
result = cMpt.finish "destroyFn()"
cMpt.mpt = AristoDbRef(nil), # Disables `=destroy()` action
pairsIt: iterator: (Blob,Blob) =
for (k,v) in cMpt.mpt.right LeafTie(root: cMpt.root):
yield (k.path.pathAsBlob, cMpt.mpt.serialise(v).valueOr(EmptyBlob)),
replicateIt: iterator: (Blob,Blob) {.gcsafe, raises: [AristoApiRlpError].} =
discard)
proc accMethods(cMpt: AristoChildDbRef): CoreDbAccFns =
## Hexary trie database handlers
let db = cMpt.base.parent
CoreDbAccFns(
backendFn: proc(): CoreDbAccBackendRef =
db.bless(AristoCoreDbAccBE(adb: cMpt.mpt)),
fetchFn: proc(address: EthAddress): CoreDbRc[CoreDbAccount] =
cMpt.accFetch(address, "fetchFn()"),
deleteFn: proc(address: EthAddress): CoreDbRc[void] =
cMpt.mptDelete(address, "deleteFn()"),
mergeFn: proc(address: EthAddress; acc: CoreDbAccount): CoreDbRc[void] =
cMpt.accMerge(address, acc, "mergeFn()"),
hasPathFn: proc(address: EthAddress): CoreDbRc[bool] =
let key = address.keccakHash.data
cMpt.mpt.hasPath(cMpt.root, key).toRcImpl(db, "hasPathFn()"),
rootVidFn: proc(): CoreDbVidRef =
db.bless(AristoCoreDbVid(ctx: cMpt.mpt, aVid: cMpt.root)),
isPruningFn: proc(): bool =
cMpt.prune,
destroyFn: proc(saveMode: CoreDbSaveFlags): CoreDbRc[void] =
cMpt.base.gc()
result = cMpt.finish "destroyFn()"
cMpt.mpt = AristoDbRef(nil)) # Disables `=destroy()` action
# ------------------------------------------------------------------------------
# Public handlers and helpers
# ------------------------------------------------------------------------------
func toError*(
e: (VertexID,AristoError);
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbErrorRef =
e.toErrorImpl(db, info, error)
func toVoidRc*[T](
rc: Result[T,AristoError];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[void] =
rc.toVoidRcImpl(db, info, error)
proc gc*(base: AristoBaseRef) =
## Run deferred destructors when it is safe. It is needed to run the
## destructors at the same scheduler level as the API call back functions.
## Any of the API functions can be intercepted by the `=destroy()` hook at
## inconvenient times so that atomicity would be violated if the actual
## destruction took place in `=destroy()`.
##
## Note: In practice the `db.gq` queue should not have much more than one
## entry and mostly be empty.
const info = "gc()"
while 0 < base.gq.len:
var q: typeof base.gq
base.gq.swap q # now `=destroy()` may refill while destructing, below
for cMpt in q:
cMpt.finish(info).isOkOr:
debug logTxt info, `error`=error.errorPrint
continue # terminates `isOkOr()`
func mpt*(dsc: CoreDxMptRef): AristoDbRef =
dsc.AristoCoreDxMptRef.ctx.mpt
func rootID*(dsc: CoreDxMptRef): VertexID =
dsc.AristoCoreDxMptRef.ctx.root
# ---------------------
func txTop*(
base: AristoBaseRef;
info: static[string];
): CoreDbRc[AristoTxRef] =
base.adb.txTop.toRcImpl(base.parent, info)
func txBegin*(
base: AristoBaseRef;
info: static[string];
): CoreDbRc[AristoTxRef] =
base.adb.txBegin.toRcImpl(base.parent, info)
proc getHash*(
base: AristoBaseRef;
vid: CoreDbVidRef;
update: bool;
info: static[string];
): CoreDbRc[Hash256] =
let
db = base.parent
aVid = vid.to(VertexID)
if not aVid.isValid:
return ok(EMPTY_ROOT_HASH)
let mpt = vid.AristoCoreDbVid.ctx
if update:
? mpt.hashify.toVoidRcImpl(db, info, HashNotAvailable)
let key = block:
let rc = mpt.getKeyRc aVid
if rc.isErr:
doAssert rc.error in {GetKeyNotFound,GetKeyTempLocked}
return err(rc.error.toErrorImpl(db, info, HashNotAvailable))
rc.value
ok key.to(Hash256)
proc getVid*(
base: AristoBaseRef;
root: Hash256;
createOk: bool;
info: static[string];
): CoreDbRc[CoreDbVidRef] =
let
db = base.parent
adb = base.adb
if root == VOID_CODE_HASH:
return ok(db.bless AristoCoreDbVid())
block:
base.gc() # update pending changes
let rc = adb.hashify()
? adb.hashify.toVoidRcImpl(db, info, HashNotAvailable)
# Check whether hash is available as state root on main trie
block:
let rc = adb.getKeyRc VertexID(1)
if rc.isErr:
doAssert rc.error == GetKeyNotFound
elif rc.value == root.to(HashKey):
return ok(db.bless AristoCoreDbVid(aVid: VertexID(1), ctx: adb))
else:
discard
# Check whether the `root` is avalilable in backlog
block:
# ..
discard
# Check whether the root vertex should be created
if createOk:
return ok(db.bless AristoCoreDbVid(createOk: true, expHash: root))
err(aristo.GenericError.toErrorImpl(db, info, RootNotFound))
# ------------------------------------------------------------------------------
# Public constructors and related
# ------------------------------------------------------------------------------
proc newMptHandler*(
base: AristoBaseRef;
root: CoreDbVidRef;
prune: bool;
saveMode: CoreDbSaveFlags;
info: static[string];
): CoreDbRc[CoreDxMptRef] =
base.gc()
var rootID = root.to(VertexID)
if not rootID.isValid:
let rc = base.adb.getKeyRc VertexID(1)
if rc.isErr and rc.error == GetKeyNotFound:
rootID = VertexID(1)
let
db = base.parent
(mode, mpt) = block:
if saveMode == Companion:
(saveMode, ? base.adb.forkTop.toRcImpl(db, info))
elif base.adb.backend.isNil:
(Cached, base.adb)
else:
(saveMode, base.adb)
cMpt = AristoChildDbRef(
base: base,
root: rootID,
prune: prune,
mpt: mpt,
saveMode: mode)
dsc = AristoCoreDxMptRef(
ctx: cMpt,
methods: cMpt.mptMethods)
ok(db.bless dsc)
proc newAccHandler*(
base: AristoBaseRef;
prune: bool;
saveMode: CoreDbSaveFlags;
info: static[string];
): CoreDbRc[CoreDxAccRef] =
base.gc()
let
db = base.parent
(mode, mpt) = block:
if saveMode == Companion:
(saveMode, ? base.adb.forkTop.toRcImpl(db, info))
elif base.adb.backend.isNil:
(Cached, base.adb)
else:
(saveMode, base.adb)
cMpt = AristoChildDbRef(
base: base,
root: VertexID(1),
prune: prune,
mpt: mpt,
saveMode: mode)
ok(db.bless CoreDxAccRef(methods: cMpt.accMethods))
proc destroy*(base: AristoBaseRef; flush: bool) =
base.gc()
base.adb.finish(flush)
func init*(T: type AristoBaseRef; db: CoreDbRef; adb: AristoDbRef): T =
T(parent: db, adb: adb)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -0,0 +1,285 @@
# Nimbus
# Copyright (c) 2023 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,
results,
../../../kvt,
../../../kvt/kvt_desc,
../../base,
../../base/base_desc,
./common_desc
type
KvtBaseRef* = ref object
parent: CoreDbRef ## Opaque top level descriptor
kdb: KvtDbRef ## Key-value table
gq: seq[KvtChildDbRef] ## Garbage queue, deferred disposal
KvtChildDbRef = ref KvtChildDbObj
KvtChildDbObj = object
## Sub-handle for triggering destructor when it goes out of scope
base: KvtBaseRef ## Local base descriptor
kvt: KvtDbRef ## Descriptor
saveMode: CoreDbSaveFlags ## When to store/discard
KvtCoreDxKvtRef = ref object of CoreDxKvtRef
## Some extendion to recover embedded state
ctx: KvtChildDbRef ## Embedded state, typical var name: `cKvt`
AristoCoreDbKvtBE* = ref object of CoreDbKvtBackendRef
kdb*: KvtDbRef
logScope:
topics = "kvt-hdl"
proc gc*(base: KvtBaseRef) {.gcsafe.}
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template logTxt(info: static[string]): static[string] =
"CoreDb/kdb " & info
func toErrorImpl(
e: KvtError;
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbErrorRef =
db.bless(error, AristoCoreDbError(
ctx: info,
isAristo: false,
kErr: e))
func toRcImpl[T](
rc: Result[T,KvtError];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[T] =
if rc.isOk:
when T is void:
return ok()
else:
return ok(rc.value)
err rc.error.toErrorImpl(db, info, error)
# ------------------------------------------------------------------------------
# Private call back functions (too large for keeping inline)
# ------------------------------------------------------------------------------
proc finish(
cKvt: KvtChildDbRef;
info: static[string];
): CoreDbRc[void] =
## key-value table destructor to be called automagically when the argument
## wrapper gets out of scope
let
base = cKvt.base
db = base.parent
result = ok()
if cKvt.kvt != base.kdb:
let rc = cKvt.kvt.forget()
if rc.isErr:
result = err(rc.error.toErrorImpl(db, info))
cKvt.kvt = KvtDbRef(nil) # Disables `=destroy()` action
if cKvt.saveMode == AutoSave:
if base.kdb.level == 0:
let rc = base.kdb.stow()
if rc.isErr:
result = err(rc.error.toErrorImpl(db, info))
proc `=destroy`(cKvt: var KvtChildDbObj) =
## Auto destructor
if not cKvt.kvt.isNil:
# Add to destructor batch queue unless direct reference
if cKvt.kvt != cKvt.base.kdb or
cKvt.saveMode == AutoSave:
cKvt.base.gq.add KvtChildDbRef(
base: cKvt.base,
kvt: cKvt.kvt,
saveMode: cKvt.saveMode)
# -------------------------------
proc kvtGet(
cKvt: KvtChildDbRef;
k: openArray[byte];
info: static[string];
): CoreDbRc[Blob] =
## Member of `CoreDbKvtFns`
let rc = cKvt.kvt.get(k)
if rc.isErr:
let db = cKvt.base.parent
if rc.error == GetNotFound:
return err(rc.error.toErrorImpl(db, info, KvtNotFound))
else:
return rc.toRcImpl(db, info)
ok(rc.value)
proc kvtPut(
cKvt: KvtChildDbRef;
k: openArray[byte];
v: openArray[byte];
info: static[string];
): CoreDbRc[void] =
let rc = cKvt.kvt.put(k,v)
if rc.isErr:
return err(rc.error.toErrorImpl(cKvt.base.parent, info))
ok()
proc kvtDel(
cKvt: KvtChildDbRef;
k: openArray[byte];
info: static[string];
): CoreDbRc[void] =
let rc = cKvt.kvt.del k
if rc.isErr:
return err(rc.error.toErrorImpl(cKvt.base.parent, info))
ok()
proc kvtHasKey(
cKvt: KvtChildDbRef;
k: openArray[byte];
info: static[string];
): CoreDbRc[bool] =
cKvt.kvt.hasKey(k).toRcImpl(cKvt.base.parent, info)
# ------------------------------------------------------------------------------
# Private database methods function table
# ------------------------------------------------------------------------------
proc kvtMethods(cKvt: KvtChildDbRef): CoreDbKvtFns =
## Key-value database table handlers
let db = cKvt.base.parent
CoreDbKvtFns(
backendFn: proc(): CoreDbKvtBackendRef =
db.bless(AristoCoreDbKvtBE(kdb: cKvt.kvt)),
getFn: proc(k: openArray[byte]): CoreDbRc[Blob] =
cKvt.kvtGet(k, "getFn()"),
delFn: proc(k: openArray[byte]): CoreDbRc[void] =
cKvt.kvtDel(k, "delFn()"),
putFn: proc(k: openArray[byte]; v: openArray[byte]): CoreDbRc[void] =
cKvt.kvtPut(k, v, "putFn()"),
hasKeyFn: proc(k: openArray[byte]): CoreDbRc[bool] =
cKvt.kvtHasKey(k, "hasKeyFn()"),
destroyFn: proc(saveMode: CoreDbSaveFlags): CoreDbRc[void] =
cKvt.base.gc()
cKvt.finish("destroyFn()"),
pairsIt: iterator(): (Blob, Blob) =
discard)
# ------------------------------------------------------------------------------
# Public handlers and helpers
# ------------------------------------------------------------------------------
func toVoidRc*[T](
rc: Result[T,KvtError];
db: CoreDbRef;
info: string;
error = Unspecified;
): CoreDbRc[void] =
if rc.isOk:
return ok()
err rc.error.toErrorImpl(db, info, error)
proc gc*(base: KvtBaseRef) =
## Run deferred destructors when it is safe. It is needed to run the
## destructors at the same scheduler level as the API call back functions.
## Any of the API functions can be intercepted by the `=destroy()` hook at
## inconvenient times so that atomicity would be violated if the actual
## destruction took place in `=destroy()`.
##
## Note: In practice the garbage queue should not have much more than one
## entry and mostly be empty.
const info = "gc()"
while 0 < base.gq.len:
var q: typeof base.gq
base.gq.swap q # now `=destroy()` may refill while destructing, below
for cKvt in q:
cKvt.finish(info).isOkOr:
debug logTxt info, `error`=error.errorPrint
continue # terminates `isOkOr()`
func kvt*(dsc: CoreDxKvtRef): KvtDbRef =
dsc.KvtCoreDxKvtRef.ctx.kvt
# ---------------------
func txTop*(
base: KvtBaseRef;
info: static[string];
): CoreDbRc[KvtTxRef] =
base.kdb.txTop.toRcImpl(base.parent, info)
func txBegin*(
base: KvtBaseRef;
info: static[string];
): CoreDbRc[KvtTxRef] =
base.kdb.txBegin.toRcImpl(base.parent, info)
# ------------------------------------------------------------------------------
# Public constructors and related
# ------------------------------------------------------------------------------
proc newKvtHandler*(
base: KvtBaseRef;
saveMode: CoreDbSaveFlags;
info: static[string];
): CoreDbRc[CoreDxKvtRef] =
base.gc()
let
db = base.parent
(mode, kvt) = block:
if saveMode == Companion:
(saveMode, ? base.kdb.forkTop.toRcImpl(db, info))
elif base.kdb.backend.isNil:
(Cached, base.kdb)
else:
(saveMode, base.kdb)
cKvt = KvtChildDbRef(
base: base,
kvt: kvt,
saveMode: saveMode)
dsc = KvtCoreDxKvtRef(
ctx: cKvt,
methods: cKvt.kvtMethods)
ok(db.bless dsc)
proc destroy*(base: KvtBaseRef; flush: bool) =
base.gc()
base.kdb.finish(flush)
func init*(T: type KvtBaseRef; db: CoreDbRef; kdb: KvtDbRef): T =
T(parent: db, kdb: kdb)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -0,0 +1,38 @@
# Nimbus
# Copyright (c) 2023 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,
results,
"../.."/[aristo, aristo/aristo_persistent, kvt, kvt/kvt_persistent],
../base,
./aristo_db
# ------------------------------------------------------------------------------
# Public constructor
# ------------------------------------------------------------------------------
proc newAristoRocksDbCoreDbRef*(path: string; qlr: QidLayoutRef): CoreDbRef =
AristoDbRocks.init(
kvt_persistent.RdbBackendRef,
aristo_persistent.RdbBackendRef,
path, qlr)
proc newAristoRocksDbCoreDbRef*(path: string): CoreDbRef =
AristoDbRocks.init(
kvt_persistent.RdbBackendRef,
aristo_persistent.RdbBackendRef,
path)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -24,9 +24,12 @@ type
Ooops
LegacyDbMemory
LegacyDbPersistent
AristoDbMemory ## Memory backend emulator
AristoDbRocks ## RocksDB backend
AristoDbVoid ## No backend
const
CoreDbPersistentTypes* = {LegacyDbPersistent}
CoreDbPersistentTypes* = {LegacyDbPersistent, AristoDbRocks}
type
CoreDbRc*[T] = Result[T,CoreDbErrorRef]

View File

@ -14,7 +14,7 @@ import
std/options,
eth/[common, trie/db],
../aristo,
./backend/legacy_db,
./backend/[aristo_db, legacy_db],
./base,
#./core_apps_legacy as core_apps
./core_apps_newapi as core_apps
@ -135,9 +135,34 @@ proc newCoreDbRef*(
when dbType == LegacyDbMemory:
newLegacyMemoryCoreDbRef()
elif dbType == AristoDbMemory:
newAristoMemoryCoreDbRef()
elif dbType == AristoDbVoid:
newAristoVoidCoreDbRef()
else:
{.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()".}
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
qidLayout: QidLayoutRef; # `Aristo` only
): CoreDbRef =
## Constructor for volatile/memory type DB
##
## Note: Using legacy notation `newCoreDbRef()` rather than
## `CoreDbRef.init()` because of compiler coughing.
##
when dbType == AristoDbMemory:
newAristoMemoryCoreDbRef(DefaultQidLayoutRef)
elif dbType == AristoDbVoid:
newAristoVoidCoreDbRef()
else:
{.error: "Unsupported constructor " & $dbType & ".newCoreDbRef()" &
" with qidLayout argument".}
# ------------------------------------------------------------------------------
# Public template wrappers
# ------------------------------------------------------------------------------

View File

@ -17,7 +17,7 @@
import
../aristo,
./backend/[legacy_rocksdb],
./backend/[aristo_rocksdb, legacy_rocksdb],
./memory_only
export
@ -35,7 +35,26 @@ proc newCoreDbRef*(
when dbType == LegacyDbPersistent:
newLegacyPersistentCoreDbRef path
elif dbType == AristoDbRocks:
newAristoRocksDbCoreDbRef path
else:
{.error: "Unsupported dbType for persistent newCoreDbRef()".}
proc newCoreDbRef*(
dbType: static[CoreDbType]; # Database type symbol
path: string; # Storage path for database
qidLayout: QidLayoutRef; # Optional for `Aristo`, ignored by others
): CoreDbRef =
## Constructor for persistent type DB
##
## Note: Using legacy notation `newCoreDbRef()` rather than
## `CoreDbRef.init()` because of compiler coughing.
when dbType == AristoDbRocks:
newAristoRocksDbCoreDbRef(path, qlr)
else:
{.error: "Unsupported dbType for persistent newCoreDbRef()" &
" with qidLayout argument".}
# End