nimbus-eth1/nimbus/db/aristo/aristo_part/part_helpers.nim

303 lines
9.1 KiB
Nim

# nimbus-eth1
# Copyright (c) 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
std/[sets, sequtils],
eth/common,
results,
".."/[aristo_desc, aristo_get, aristo_vid],
./part_desc
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
proc read(rlp: var Rlp; T: type PrfNode): T {.gcsafe, raises: [RlpError].} =
## Mixin for RLP reader. The decoder with error return code in a `Leaf`
## node if needed.
##
func readError(error: AristoError): PrfNode =
## Prettify return code expression
PrfNode(vType: Leaf, prfType: isError, error: error)
if not rlp.isList:
# Otherwise `rlp.items` would raise a `Defect`
return readError(PartRlp2Or17ListEntries)
var
blobs = newSeq[Blob](2) # temporary, cache
links: array[16,HashKey] # reconstruct branch node
top = 0 # count entries and positions
# Collect lists of either 2 or 17 blob entries.
for w in rlp.items:
case top
of 0, 1:
if not w.isBlob:
return readError(PartRlpBlobExpected)
blobs[top] = rlp.read(Blob)
of 2 .. 15:
let blob = rlp.read(Blob)
links[top] = HashKey.fromBytes(blob).valueOr:
return readError(PartRlpBranchHashKeyExpected)
of 16:
if not w.isBlob or 0 < rlp.read(Blob).len:
return readError(PartRlpEmptyBlobExpected)
else:
return readError(PartRlp2Or17ListEntries)
top.inc
# Verify extension data
case top
of 2:
if blobs[0].len == 0:
return readError(PartRlpNonEmptyBlobExpected)
let (isLeaf, pathSegment) = NibblesBuf.fromHexPrefix blobs[0]
if isLeaf:
return PrfNode(
vType: Leaf,
prfType: ignore,
lPfx: pathSegment,
lData: LeafPayload(
pType: RawData,
rawBlob: blobs[1]))
else:
var node = PrfNode(
vType: Branch,
prfType: isExtension,
ePfx: pathSegment)
node.key[0] = HashKey.fromBytes(blobs[1]).valueOr:
return readError(PartRlpExtHashKeyExpected)
return node
of 17:
for n in [0,1]:
links[n] = HashKey.fromBytes(blobs[n]).valueOr:
return readError(PartRlpBranchHashKeyExpected)
return PrfNode(
vType: Branch,
prfType: ignore,
key: links)
else:
discard
readError(PartRlp2Or17ListEntries)
proc read(rlp: var Rlp; T: type PrfPayload): T {.gcsafe, raises: [RlpError].} =
## Mixin for RLP reader decoding `Account` or storage slot payload.
##
case rlp.listLen:
of 1:
result.prfType = isStoValue
result.num = rlp.read UInt256
of 4:
result.prfType = isAccount
result.acc = rlp.read Account
else:
result.prfType = isError
result.error = PartRlp1r4ListEntries
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
func toNodesTab*(
proof: openArray[Blob]; # List of RLP encoded nodes
mode: PartStateMode; # How to decode `Leaf` nodes
): Result[TableRef[HashKey,PrfNode],AristoError] =
## Convert RLP encoded argument list `proof` to a nodes table indexed by
## the `HashKey` values of the `proof` list entries.
var
exts: Table[HashKey,PrfNode] # need to be processed separately
nodes = newTable[HashKey,PrfNode]()
# populate tables
for w in proof:
# Decode blob `w`
let nd = block:
try: rlp.decode(w, PrfNode)
except RlpError as e:
return err(PartRlpNodeException)
case nd.prfType:
of isExtension:
# For a moment, hold extensions on a separate cache
exts[w.digestTo HashKey] = nd
of ignore:
# Store `Branch` and `Leaf` nodes in final lookup table
nodes[w.digestTo HashKey] = nd
# Special decoding for account `Leaf` nodes
if nd.vType == Leaf and mode != ForceGenericPayload:
# Decode payload to deficated format for storage or accounts
var pyl: PrfPayload
try:
pyl = rlp.decode(nd.lData.rawBlob, PrfPayload)
except RlpError as e:
pyl = PrfPayload(prfType: isError, error: PartRlpPayloadException)
case pyl.prfType:
of isStoValue:
# Single value encoding might not be unique so it cannot be
# automatically detected
if mode != AutomaticPayload:
nd.lData = LeafPayload(pType: StoData, stoData: pyl.num)
of isAccount:
nd.key[0] = pyl.acc.storageRoot.to(HashKey)
nd.lData = LeafPayload(
pType: AccountData,
account: AristoAccount(
nonce: pyl.acc.nonce,
balance: pyl.acc.balance,
codeHash: pyl.acc.codeHash))
elif mode == AutomaticPayload:
discard
else:
return err(pyl.error)
else:
return err(nd.error)
# Postprocess extension nodes
for (xKey,xNode) in exts.pairs:
# Combine `xNode + nodes[w.ePfx]`
let nd = nodes.getOrDefault xNode.key[0]
if nd.isNil:
# Need to store raw extension
nodes[xKey] = xNode
continue
if nd.ePfx.len != 0:
return err(PartGarbledExtsInProofs)
# Move extended `nd` branch node
nd.prfType = ignore
nd.ePfx = xNode.ePfx
nodes.del xNode.key[0]
nodes[xKey] = nd
ok nodes
proc backLinks*(nTab: TableRef[HashKey,PrfNode]): PrfBackLinks =
## tuple[chains: seq[seq[HashKey]], links: Table[HashKey,HashKey]] =
## Classify argument table
##
## * chains: key list of back chains
## * links: `(child,parent)` lookup table
##
new result
# Collect predecessor list
for (key,nd) in nTab.pairs:
if nd.vType == Leaf:
if nd.lData.pType == AccountData and nd.key[0].isValid:
result.links[nd.key[0]] = key
elif nd.prfType == isExtension:
result.links[nd.key[0]] = key
else:
for w in nd.key:
if w.isValid:
result.links[w] = key
# Compute leafs list, i.e. keys without children in `nTab[]`
var leafs = nTab.keys.toSeq.toHashSet
for (child,parent) in result.links.pairs:
if child in nTab:
leafs.excl parent # `parent` has `child` => not a leaf
# Compute chains starting at leafs
for leaf in leafs:
var q = @[leaf]
while true:
let up = result.links.getOrVoid q[^1]
if up.isValid:
q.add up
else:
break
result.chains.add q
proc getTreeRootVid*(
ps: PartStateRef;
key: HashKey;
): Result[VertexID,AristoError] =
## Find root ID in `ps[]` or create a new ID
##
# Use root from `ps` descriptor
let rvid = ps[key]
if rvid.isValid:
return ok(rvid.vid)
# Try next free VID
for n in 2 .. LEAST_FREE_VID:
let rvid = (VertexID(n),VertexID(n))
if not ps.db.getVtx(rvid).isValid:
ps[key] = rvid
return ok(VertexID n)
err(PartNoMoreRootVidsLeft)
proc getRvid*(
ps: PartStateRef;
root: VertexID;
key: HashKey;
): Result[tuple[rvid: RootedVertexID, fromStateDb: bool],AristoError] =
## Find key in `ps[]` or create a new key. the value `onStateDb` is
## return `false` if a new entry was created.
##
var (rvid, fromStateDb) = (ps[key], true)
if not rvid.isValid:
# Create new one
(rvid, fromStateDb) = ((root, ps.db.vidFetch()), false)
ps[key] = rvid
elif root != rvid.root:
# Oops
return err(PartRootVidsDontMatch)
ok((rvid, fromStateDb))
proc updateAccountsTree*(
ps: PartStateRef; # Partial database descriptor
nodes: TableRef[HashKey,PrfNode]; # Node lookup table
bl: PrfBackLinks; # Uplink lists
mode: PartStateMode; # Try accounts, otherwise generic
): Result[void,AristoError] =
## Check wether the chain has an accounts leaf node and update the
## argument descriptor `ps` accordingly.
##
if mode == ForceGenericPayload or VertexID(1) in ps.core:
return ok()
var accRootKey = VOID_HASH_KEY
for chain in bl.chains:
for key in chain:
nodes[].withValue(key,node):
if node.vType == Leaf and node.lData.pType == AccountData:
# Ok, got an accounts leaf node
if not accRootKey.isValid:
# Register accounts root
accRootKey = chain[^1]
ps[accRootKey] = (VertexID(1),VertexID(1))
elif accRootKey != chain[^1]:
# Two account chains with different root keys
return err(PartRootKeysDontMatch)
# Register storage root (if any)
if node.key[0].isValid:
let vid = ps.db.vidFetch()
ps[node.key[0]] = (vid,vid)
ok()
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------