307 lines
10 KiB
Nim
307 lines
10 KiB
Nim
|
# nimbus-eth1
|
||
|
# 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
|
||
|
std/[algorithm, sequtils, sets, tables],
|
||
|
eth/common,
|
||
|
results,
|
||
|
./aristo_desc
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Private functions
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
func dup(sTab: Table[VertexID,VertexRef]): Table[VertexID,VertexRef] =
|
||
|
## Explicit dup for `VertexRef` values
|
||
|
for (k,v) in sTab.pairs:
|
||
|
result[k] = v.dup
|
||
|
|
||
|
func dup(delta: LayerDelta): LayerDelta =
|
||
|
result = LayerDelta(
|
||
|
sTab: delta.sTab.dup, # explicit dup for ref values
|
||
|
kMap: delta.kMap,
|
||
|
pAmk: delta.pAmk)
|
||
|
|
||
|
func stackGetLebalOrVoid(db: AristoDbRef; lbl: HashLabel): HashSet[VertexID] =
|
||
|
# Helper: get next set of vertex IDs from stack.
|
||
|
for w in db.stack.reversed:
|
||
|
w.delta.pAmk.withValue(lbl,value):
|
||
|
return value[]
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public getters: lazy value lookup for read only versions
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
func lTab*(db: AristoDbRef): Table[LeafTie,VertexID] =
|
||
|
db.top.final.lTab
|
||
|
|
||
|
func pPrf*(db: AristoDbRef): HashSet[VertexID] =
|
||
|
db.top.final.pPrf
|
||
|
|
||
|
func vGen*(db: AristoDbRef): seq[VertexID] =
|
||
|
db.top.final.vGen
|
||
|
|
||
|
func dirty*(db: AristoDbRef): bool =
|
||
|
db.top.final.dirty
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public getters/helpers
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
func nLayersVtx*(db: AristoDbRef): int =
|
||
|
## Number of vertex entries on the cache layers
|
||
|
db.stack.mapIt(it.delta.sTab.len).foldl(a + b, db.top.delta.sTab.len)
|
||
|
|
||
|
func nLayersLabel*(db: AristoDbRef): int =
|
||
|
## Number of key/label entries on the cache layers
|
||
|
db.stack.mapIt(it.delta.kMap.len).foldl(a + b, db.top.delta.kMap.len)
|
||
|
|
||
|
func nLayersLebal*(db: AristoDbRef): int =
|
||
|
## Number of key/label reverse lookup entries on the cache layers
|
||
|
db.stack.mapIt(it.delta.pAmk.len).foldl(a + b, db.top.delta.pAmk.len)
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public functions: get variants
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
proc layersGetVtx*(db: AristoDbRef; vid: VertexID): Result[VertexRef,void] =
|
||
|
## Find a vertex on the cache layers. An `ok()` result might contain a
|
||
|
## `nil` vertex if it is stored on the cache that way.
|
||
|
##
|
||
|
if db.top.delta.sTab.hasKey vid:
|
||
|
return ok(db.top.delta.sTab.getOrVoid vid)
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
if w.delta.sTab.hasKey vid:
|
||
|
return ok(w.delta.sTab.getOrVoid vid)
|
||
|
|
||
|
err()
|
||
|
|
||
|
proc layersGetVtxOrVoid*(db: AristoDbRef; vid: VertexID): VertexRef =
|
||
|
## Simplified version of `layersGetVtx()`
|
||
|
db.layersGetVtx(vid).valueOr: VertexRef(nil)
|
||
|
|
||
|
|
||
|
proc layersGetLabel*(db: AristoDbRef; vid: VertexID): Result[HashLabel,void] =
|
||
|
## Find a hash label (containh the `HashKey`) on the cache layers. An
|
||
|
## `ok()` result might contain a void hash label if it is stored on the
|
||
|
## cache that way.
|
||
|
##
|
||
|
if db.top.delta.kMap.hasKey vid:
|
||
|
return ok(db.top.delta.kMap.getOrVoid vid)
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
if w.delta.kMap.hasKey vid:
|
||
|
return ok(w.delta.kMap.getOrVoid vid)
|
||
|
|
||
|
err()
|
||
|
|
||
|
proc layersGetlabelOrVoid*(db: AristoDbRef; vid: VertexID): HashLabel =
|
||
|
## Simplified version of `layersGetLabel()`
|
||
|
db.layersGetLabel(vid).valueOr: VOID_HASH_LABEL
|
||
|
|
||
|
|
||
|
proc layersGetKey*(db: AristoDbRef; vid: VertexID): Result[HashKey,void] =
|
||
|
## Variant of `layersGetLabel()` for returning the `HashKey` part of the
|
||
|
## label only.
|
||
|
let lbl = db.layersGetLabel(vid).valueOr:
|
||
|
return err()
|
||
|
# Note that `lbl.isValid == lbl.key.isValid`
|
||
|
ok(lbl.key)
|
||
|
|
||
|
proc layersGetKeyOrVoid*(db: AristoDbRef; vid: VertexID): HashKey =
|
||
|
## Simplified version of `layersGetKey()`
|
||
|
db.layersGetKey(vid).valueOr: VOID_HASH_KEY
|
||
|
|
||
|
|
||
|
proc layersGetLebal*(
|
||
|
db: AristoDbRef;
|
||
|
lbl: HashLabel;
|
||
|
): Result[HashSet[VertexID],void] =
|
||
|
## Inverse of `layersGetKey()`. For a given argumnt `lbl`, find all vertex
|
||
|
## IDs that have `layersGetLbl()` return this very `lbl` value for the these
|
||
|
## vertex IDs.
|
||
|
if db.top.delta.pAmk.hasKey lbl:
|
||
|
return ok(db.top.delta.pAmk.getOrVoid lbl)
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
if w.delta.pAmk.hasKey lbl:
|
||
|
return ok(w.delta.pAmk.getOrVoid lbl)
|
||
|
|
||
|
err()
|
||
|
|
||
|
proc layersGetLebalOrVoid*(db: AristoDbRef; lbl: HashLabel): HashSet[VertexID] =
|
||
|
## Simplified version of `layersGetVidsOrVoid()`
|
||
|
db.layersGetLebal(lbl).valueOr: EmptyVidSet
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public functions: put variants
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
proc layersPutVtx*(db: AristoDbRef; vid: VertexID; vtx: VertexRef) =
|
||
|
## Store a (potentally empty) vertex on the top layer
|
||
|
db.top.delta.sTab[vid] = vtx
|
||
|
db.top.final.dirty = true # Modified top cache layers
|
||
|
|
||
|
proc layersResVtx*(db: AristoDbRef; vid: VertexID) =
|
||
|
## Shortcut for `db.layersPutVtx(vid, VertexRef(nil))`. It is sort of the
|
||
|
## equivalent of a delete function.
|
||
|
db.layersPutVtx(vid, VertexRef(nil))
|
||
|
|
||
|
|
||
|
proc layersPutLabel*(db: AristoDbRef; vid: VertexID; lbl: HashLabel) =
|
||
|
## Store a (potentally void) hash label on the top layer
|
||
|
|
||
|
# Get previous label
|
||
|
let blb = db.top.delta.kMap.getOrVoid vid
|
||
|
|
||
|
# Update label on `label->vid` mappiing table
|
||
|
db.top.delta.kMap[vid] = lbl
|
||
|
db.top.final.dirty = true # Modified top cache layers
|
||
|
|
||
|
# Clear previous value on reverse table if it has changed
|
||
|
if blb.isValid and blb != lbl:
|
||
|
db.top.delta.pAmk.withValue(blb, value):
|
||
|
value[].excl vid
|
||
|
do: # provide empty lookup
|
||
|
db.top.delta.pAmk[blb] = db.stackGetLebalOrVoid(blb) - @[vid].toHashSet
|
||
|
|
||
|
# Add updated value on reverse table if non-zero
|
||
|
if lbl.isValid:
|
||
|
db.top.delta.pAmk.withValue(lbl, value):
|
||
|
value[].incl vid
|
||
|
do: # else if not found: need to merge with value set from lower layer
|
||
|
db.top.delta.pAmk[lbl] = db.stackGetLebalOrVoid(lbl) + @[vid].toHashSet
|
||
|
|
||
|
|
||
|
proc layersResLabel*(db: AristoDbRef; vid: VertexID) =
|
||
|
## Shortcut for `db.layersPutLabel(vid, VOID_HASH_LABEL)`. It is sort of the
|
||
|
## equivalent of a delete function.
|
||
|
db.layersPutLabel(vid, VOID_HASH_LABEL)
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public functions
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
proc layersMergeOnto*(src: LayerRef; trg: LayerRef): LayerRef {.discardable.} =
|
||
|
## Merges the argument `src` into the argument `trg` and returns `trg`. For
|
||
|
## the result layer, the `txUid` value set to `0`.
|
||
|
trg.final = src.final
|
||
|
trg.txUid = 0
|
||
|
|
||
|
for (vid,vtx) in src.delta.sTab.pairs:
|
||
|
trg.delta.sTab[vid] = vtx
|
||
|
|
||
|
for (vid,lbl) in src.delta.kMap.pairs:
|
||
|
trg.delta.kMap[vid] = lbl
|
||
|
|
||
|
for (lbl,vids) in src.delta.pAmk.pairs:
|
||
|
trg.delta.pAmk.withValue(lbl, value):
|
||
|
value[] = value[] + vids
|
||
|
do:
|
||
|
trg.delta.pAmk[lbl] = vids
|
||
|
|
||
|
trg
|
||
|
|
||
|
|
||
|
proc layersCc*(db: AristoDbRef; level = high(int)): LayerRef =
|
||
|
## Provide a collapsed copy of layers up to a particular transaction level.
|
||
|
## If the `level` argument is too large, the maximum transaction level is
|
||
|
## returned. For the result layer, the `txUid` value set to `0`.
|
||
|
let level = min(level, db.stack.len)
|
||
|
|
||
|
result = LayerRef(final: db.top.final) # Pre-merged/final values
|
||
|
|
||
|
# Merge stack into its bottom layer
|
||
|
if level <= 0 and db.stack.len == 0:
|
||
|
result.delta = db.top.delta.dup # Explicit dup for ref values
|
||
|
else:
|
||
|
# now: 0 < level <= db.stack.len
|
||
|
result.delta = db.stack[0].delta.dup # Explicit dup for ref values
|
||
|
|
||
|
# Merge stack: structural vertex table and hash key mapping
|
||
|
for w in db.stack.reversed:
|
||
|
w.layersMergeOnto result
|
||
|
|
||
|
# Merge top layer
|
||
|
db.top.layersMergeOnto result
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# Public iterators
|
||
|
# ------------------------------------------------------------------------------
|
||
|
|
||
|
iterator layersWalkVtx*(
|
||
|
db: AristoDbRef;
|
||
|
seen: var HashSet[VertexID];
|
||
|
): tuple[vid: VertexID, vtx: VertexRef] =
|
||
|
## Walk over all `(VertexID,VertexRef)` pairs on the cache layers. Note that
|
||
|
## entries are unsorted.
|
||
|
##
|
||
|
## The argument `seen` collects a set of all visited vertex IDs including
|
||
|
## the one with a zero vertex which are othewise skipped by the iterator.
|
||
|
## The `seen` argument must not be modified while the iterator is active.
|
||
|
##
|
||
|
for (vid,vtx) in db.top.delta.sTab.pairs:
|
||
|
yield (vid,vtx)
|
||
|
seen.incl vid
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
for (vid,vtx) in w.delta.sTab.pairs:
|
||
|
if vid notin seen:
|
||
|
yield (vid,vtx)
|
||
|
seen.incl vid
|
||
|
|
||
|
iterator layersWalkVtx*(
|
||
|
db: AristoDbRef;
|
||
|
): tuple[vid: VertexID, vtx: VertexRef] =
|
||
|
## Variant of `layersWalkVtx()`.
|
||
|
var seen: HashSet[VertexID]
|
||
|
for (vid,vtx) in db.layersWalkVtx seen:
|
||
|
yield (vid,vtx)
|
||
|
|
||
|
|
||
|
iterator layersWalkLabel*(
|
||
|
db: AristoDbRef;
|
||
|
): tuple[vid: VertexID, lbl: HashLabel] =
|
||
|
## Walk over all `(VertexID,HashLabel)` pairs on the cache layers. Note that
|
||
|
## entries are unsorted.
|
||
|
var seen: HashSet[VertexID]
|
||
|
for (vid,lbl) in db.top.delta.kMap.pairs:
|
||
|
yield (vid,lbl)
|
||
|
seen.incl vid
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
for (vid,lbl) in w.delta.kMap.pairs:
|
||
|
if vid notin seen:
|
||
|
yield (vid,lbl)
|
||
|
seen.incl vid
|
||
|
|
||
|
|
||
|
iterator layersWalkLebal*(
|
||
|
db: AristoDbRef;
|
||
|
): tuple[lbl: HashLabel, vids: HashSet[VertexID]] =
|
||
|
## Walk over `(HashLabel,HashSet[VertexID])` pairs.
|
||
|
var seen: HashSet[HashLabel]
|
||
|
for (lbl,vids) in db.top.delta.pAmk.pairs:
|
||
|
yield (lbl,vids)
|
||
|
seen.incl lbl
|
||
|
|
||
|
for w in db.stack.reversed:
|
||
|
for (lbl,vids) in w.delta.pAmk.pairs:
|
||
|
if lbl notin seen:
|
||
|
yield (lbl,vids)
|
||
|
seen.incl lbl
|
||
|
|
||
|
# ------------------------------------------------------------------------------
|
||
|
# End
|
||
|
# ------------------------------------------------------------------------------
|