303 lines
9.1 KiB
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
|
|
# ------------------------------------------------------------------------------
|