Skip to content

Commit 253e2a9

Browse files
authored
Stateless: Add support for loading subtries into the database (#3641)
1 parent 57a5d78 commit 253e2a9

File tree

6 files changed

+499
-10
lines changed

6 files changed

+499
-10
lines changed

execution_chain/db/aristo/aristo_desc/desc_identifiers.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func `<`*(a, b: HashKey): bool =
165165
# Public helpers: Reversible conversions between`HashKey`, etc.
166166
# ------------------------------------------------------------------------------
167167
func to*(lid: HashKey; T: type Hash32): T =
168-
## Returns the `Hash236` key if available, otherwise the Keccak hash of
168+
## Returns the `Hash32` key if available, otherwise the Keccak hash of
169169
## the `seq[byte]` version.
170170
if lid.len == 32:
171171
Hash32(lid.buf)

execution_chain/db/aristo/aristo_proof.nim

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
import
1818
std/[tables, sets, sequtils],
19-
eth/common/hashes,
19+
eth/common/[hashes, accounts_rlp],
2020
results,
21-
./[aristo_desc, aristo_fetch, aristo_get, aristo_serialise, aristo_utils]
21+
./[aristo_desc, aristo_fetch, aristo_get, aristo_serialise, aristo_utils, aristo_vid, aristo_layers]
2222

2323
const
2424
ChainRlpNodesNoEntry* = {
@@ -316,11 +316,184 @@ proc verifyProof*(
316316
proc verifyProof*(
317317
nodes: Table[Hash32, seq[byte]];
318318
root: Hash32;
319-
path: Hash32): Result[Opt[seq[byte]], AristoError] =
319+
path: Hash32;
320+
visitedNodes: var HashSet[Hash32]
321+
): Result[Opt[seq[byte]], AristoError] =
320322
if nodes.len() == 0:
321323
return err(PartTrkEmptyProof)
322324

323325
handleTrackRlpNodesResult():
324-
var visitedNodes: HashSet[Hash32]
325326
let nibbles = NibblesBuf.fromBytes(path.data)
326327
trackRlpNodes(nodes, visitedNodes, root.to(HashKey), nibbles, start = true)
328+
329+
proc verifyProof*(
330+
nodes: Table[Hash32, seq[byte]];
331+
root: Hash32;
332+
path: Hash32;
333+
): Result[Opt[seq[byte]], AristoError] =
334+
var visitedNodes: HashSet[Hash32]
335+
verifyProof(nodes, root, path, visitedNodes)
336+
337+
proc convertLeaf(
338+
leafNode: openArray[byte],
339+
segm: NibblesBuf,
340+
isStorage: bool): Result[NodeRef, AristoError] {.gcsafe, raises: [RlpError]} =
341+
342+
let node =
343+
if isStorage:
344+
let slotValue = rlp.decode(leafNode, UInt256)
345+
NodeRef(vtx: StoLeafRef.init(segm, slotValue))
346+
else: # Account leaf
347+
let
348+
acc = rlp.decode(leafNode, Account)
349+
aristoAcc = AristoAccount(
350+
nonce: acc.nonce,
351+
balance: acc.balance,
352+
codeHash: acc.codeHash)
353+
stoID = (acc.storageRoot != EMPTY_ROOT_HASH, default(VertexID))
354+
n = NodeRef(vtx: AccLeafRef.init(segm, aristoAcc, stoID))
355+
356+
n.key[0] = HashKey.fromBytes(acc.storageRoot.data).valueOr:
357+
return err(PartTrkLinkExpected)
358+
n
359+
360+
ok(node)
361+
362+
proc convertSubtrie(
363+
key: Hash32,
364+
src: Table[Hash32, seq[byte]],
365+
dst: var Table[HashKey, NodeRef],
366+
isStorage: static bool): Result[void, AristoError] {.gcsafe, raises: [RlpError]} =
367+
# Precondition: trieNodes have already been validated using verifyProof
368+
# Does not allocate any vertex ids when creating the VertexRef types.
369+
if key notin src:
370+
# Since we are processing a subtrie some nodes are expected to be missing
371+
return ok()
372+
373+
var rlpNode = rlpFromBytes(src.getOrDefault(key))
374+
375+
let node =
376+
case rlpNode.listLen()
377+
of 2:
378+
let
379+
(isLeaf, segm) = NibblesBuf.fromHexPrefix(rlpNode.listElem(0).toBytes())
380+
link = rlpNode.listElem(1).rlpNodeToBytes() # link or payload
381+
if isLeaf:
382+
let n = ?convertLeaf(link, segm, isStorage)
383+
if not isStorage and AccLeafRef(n.vtx).stoID.isValid:
384+
# Convert the storage subtrie
385+
?convertSubtrie(n.key[0].to(Hash32), src, dst, isStorage = true)
386+
n
387+
else: # Extension node
388+
let k = HashKey.fromBytes(link).valueOr:
389+
return err(PartTrkLinkExpected)
390+
391+
# Convert the child branch node which will be merged with this extension node
392+
?convertSubtrie(k.to(Hash32), src, dst, isStorage)
393+
doAssert(dst.contains(k))
394+
395+
let
396+
childNode = dst.getOrDefault(k)
397+
childBranch = BranchRef(childNode.vtx)
398+
399+
# Remove the childNode because it's branch was copied into this node
400+
dst.del(k)
401+
402+
NodeRef(
403+
key: childNode.key,
404+
vtx: ExtBranchRef.init(segm, childBranch.startVid, childBranch.used))
405+
406+
of 17: # Branch node
407+
var key: array[16, HashKey]
408+
let branch = BranchRef.init(default(VertexID), 0)
409+
for i in 0 ..< 16:
410+
let
411+
link = rlpNode.listElem(i).rlpNodeToBytes()
412+
k = HashKey.fromBytes(link).valueOr:
413+
return err(PartTrkLinkExpected)
414+
if k.len() > 0:
415+
discard branch.setUsed(i.uint8, true)
416+
?convertSubtrie(k.to(Hash32), src, dst, isStorage)
417+
key[i] = k
418+
NodeRef(key: key, vtx: branch)
419+
420+
else:
421+
return err(PartTrkGarbledNode)
422+
423+
let hashKey = HashKey.fromBytes(key.data).valueOr:
424+
return err(PartTrkLinkExpected)
425+
dst[hashKey] = node
426+
427+
ok()
428+
429+
proc putSubtrie(
430+
db: AristoTxRef,
431+
key: HashKey,
432+
nodes: Table[HashKey, NodeRef],
433+
rvid: RootedVertexID = (STATE_ROOT_VID, STATE_ROOT_VID)): Result[void, AristoError] =
434+
if key notin nodes:
435+
return err(PartTrkFollowUpKeyMismatch)
436+
437+
let node = nodes.getOrDefault(key)
438+
case node.vtx.vType:
439+
of AccLeaf:
440+
let accVtx = AccLeafRef(node.vtx)
441+
if accVtx.stoID.isValid:
442+
let stoVid = db.vidFetch()
443+
accVtx.stoID = (true, stoVid)
444+
445+
let
446+
k = node.key[0]
447+
r = (stoVid, stoVid)
448+
if nodes.contains(k):
449+
# Write the storage subtrie
450+
?db.putSubtrie(k, nodes, r)
451+
else:
452+
# Write the known hash key setting the vtx to nil
453+
db.layersPutKey(r, BranchRef(nil), k)
454+
455+
of StoLeaf:
456+
discard
457+
458+
of Branch, ExtBranch:
459+
let bvtx = BranchRef(node.vtx)
460+
bvtx.startVid = db.vidFetch(16)
461+
462+
for n, subvid in node.vtx.pairs():
463+
let
464+
r = (rvid.root, subvid)
465+
k = if node.key[n].len() < 32:
466+
# Embedded nodes are stored in the nodes map indexed by
467+
# a HashKey containing a hash of the node data rather than
468+
# a HashKey containing the embedded node itself
469+
node.key[n].to(Hash32).to(HashKey)
470+
else:
471+
node.key[n]
472+
if nodes.contains(k):
473+
?db.putSubtrie(k, nodes, r)
474+
else:
475+
# Write the known hash key setting the vtx to nil
476+
db.layersPutKey(r, BranchRef(nil), k)
477+
478+
db.layersPutVtx(rvid, node.vtx)
479+
480+
ok()
481+
482+
proc putSubtrie*(
483+
db: AristoTxRef,
484+
stateRoot: Hash32,
485+
nodes: Table[Hash32, seq[byte]]): Result[void, AristoError] =
486+
if nodes.len() == 0:
487+
return err(PartTrkEmptyProof)
488+
489+
let key = HashKey.fromBytes(stateRoot.data).valueOr:
490+
return err(PartTrkLinkExpected)
491+
492+
try:
493+
var convertedNodes: Table[HashKey, NodeRef]
494+
?convertSubtrie(stateRoot, nodes, convertedNodes, isStorage = false)
495+
?db.putSubtrie(key, convertedNodes)
496+
except RlpError:
497+
return err(PartTrkRlpError)
498+
499+
ok()

execution_chain/db/core_db/base.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,21 @@ proc recast*(
446446
codeHash: accRec.codeHash,
447447
storageRoot: rc)
448448

449+
proc putSubtrie*(
450+
acc: CoreDbTxRef;
451+
stateRoot: Hash32,
452+
nodes: Table[Hash32, seq[byte]]): CoreDbRc[void] =
453+
## Loads a subtrie of trie nodes into the database (both account and linked
454+
## storage subtries). It does this by walking down the account trie starting
455+
## from the state root and then eventually walking down each storage trie.
456+
## The implementation doesn't handle merging with any existing trie/s in the
457+
## database so this should only be used on an empty database.
458+
459+
acc.aTx.putSubtrie(stateRoot, nodes).isOkOr:
460+
return err(error.toError(""))
461+
462+
ok()
463+
449464
# ------------------------------------------------------------------------------
450465
# Public transaction related methods
451466
# ------------------------------------------------------------------------------

tests/all_tests.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import
2323
test_genesis,
2424
test_getproof_json,
2525
test_aristo_proof,
26+
test_aristo_subtries_json,
2627
test_jwt_auth,
2728
test_kvt,
2829
test_ledger,

tests/test_aristo_proof.nim

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{.push raises: [].}
1111

1212
import
13-
std/[tables, sequtils],
13+
std/[tables, sets, sequtils],
1414
unittest2,
1515
stint,
1616
results,
@@ -64,6 +64,17 @@ suite "Aristo proof verification":
6464
leafValue.isSome()
6565
leafValue.get() == value
6666

67+
block:
68+
var visitedNodes: HashSet[Hash32]
69+
let leafValue = verifyProof(toNodesTable(proof), root, key, visitedNodes).expect("valid proof")
70+
check:
71+
leafValue.isSome()
72+
leafValue.get() == value
73+
visitedNodes.len() == proof.len()
74+
for n in proof:
75+
check visitedNodes.contains(keccak256(n))
76+
77+
6778
test "Validate proof for existing value using nodes table":
6879
for i in 1..numValues:
6980
let
@@ -92,11 +103,20 @@ suite "Aristo proof verification":
92103
indexBytes = getBytes(i)
93104
key = keccak256(indexBytes)
94105
value = indexBytes
106+
107+
block:
108+
let leafValue = verifyProof(nodes, root, key).expect("valid proof")
109+
check:
110+
leafValue.isSome()
111+
leafValue.get() == value
95112

96-
let leafValue = verifyProof(nodes, root, key).expect("valid proof")
97-
check:
98-
leafValue.isSome()
99-
leafValue.get() == value
113+
block:
114+
var visitedNodes: HashSet[Hash32]
115+
let leafValue = verifyProof(nodes, root, key, visitedNodes).expect("valid proof")
116+
check:
117+
leafValue.isSome()
118+
leafValue.get() == value
119+
visitedNodes.len() > 0
100120

101121
test "Validate proof for non-existing value":
102122
for i in 1..numValues:

0 commit comments

Comments
 (0)