Skip to content

Commit 2ea90ab

Browse files
authored
Stateless: Add multiproof function and use when building witnesses (#3556)
* Implement multiproof function. * Improve performance of witness building when using multiproof using a single loop. * Use var parameters to return proofs to reduce seq copies and create a multiStorageProof function. * Use separate account and storage preimages tables. * Fix possible underflow when processing block hashes.
1 parent 0a48102 commit 2ea90ab

File tree

4 files changed

+119
-55
lines changed

4 files changed

+119
-55
lines changed

execution_chain/db/aristo/aristo_proof.nim

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,11 @@
1515
{.push raises: [].}
1616

1717
import
18+
std/[tables, sets, sequtils],
1819
eth/common/hashes,
1920
results,
2021
./[aristo_desc, aristo_fetch, aristo_get, aristo_serialise, aristo_utils]
2122

22-
# ------------------------------------------------------------------------------
23-
# Public functions
24-
# ------------------------------------------------------------------------------
25-
26-
2723
const
2824
ChainRlpNodesNoEntry* = {
2925
PartChnLeafPathMismatch, PartChnExtPfxMismatch, PartChnBranchVoidEdge}
@@ -133,38 +129,37 @@ proc trackRlpNodes(
133129
return err(PartTrkLinkExpected)
134130
chain.toOpenArray(1,chain.len-1).trackRlpNodes(nextKey, path.slice nChewOff)
135131

136-
# ------------------------------------------------------------------------------
137-
# Public functions
138-
# ------------------------------------------------------------------------------
139-
140132
proc makeProof(
141133
db: AristoTxRef;
142134
root: VertexID;
143135
path: NibblesBuf;
144136
nodesCache: var NodesCache;
145-
): Result[(seq[seq[byte]], bool), AristoError] =
137+
chain: var seq[seq[byte]];
138+
): Result[bool, AristoError] =
146139
## This function returns a chain of rlp-encoded nodes along the argument
147140
## path `(root,path)` followed by a `true` value if the `path` argument
148141
## exists in the database. If the argument `path` is not on the database,
149142
## a partial path will be returned follwed by a `false` value.
150143
##
151144
## Errors will only be returned for invalid paths.
152145
##
153-
var chain: seq[seq[byte]]
154146
let rc = db.chainRlpNodes((root,root), path, chain, nodesCache)
155147
if rc.isOk:
156-
ok((chain, true))
148+
ok(true)
157149
elif rc.error in ChainRlpNodesNoEntry:
158-
ok((chain, false))
150+
ok(false)
159151
else:
160152
err(rc.error)
161153

162154
proc makeAccountProof*(
163155
db: AristoTxRef;
164156
accPath: Hash32;
165157
): Result[(seq[seq[byte]], bool), AristoError] =
166-
var nodesCache: NodesCache
167-
db.makeProof(STATE_ROOT_VID, NibblesBuf.fromBytes accPath.data, nodesCache)
158+
var
159+
nodesCache: NodesCache
160+
proof: seq[seq[byte]]
161+
let exists = ?db.makeProof(STATE_ROOT_VID, NibblesBuf.fromBytes accPath.data, nodesCache, proof)
162+
ok((proof, exists))
168163

169164
proc makeStorageProof*(
170165
db: AristoTxRef;
@@ -177,8 +172,11 @@ proc makeStorageProof*(
177172
if error == FetchPathStoRootMissing:
178173
return ok((@[],false))
179174
return err(error)
180-
var nodesCache: NodesCache
181-
db.makeProof(vid, NibblesBuf.fromBytes stoPath.data, nodesCache)
175+
var
176+
nodesCache: NodesCache
177+
proof: seq[seq[byte]]
178+
let exists = ?db.makeProof(vid, NibblesBuf.fromBytes stoPath.data, nodesCache, proof)
179+
ok((proof, exists))
182180

183181
proc makeStorageProofs*(
184182
db: AristoTxRef;
@@ -196,14 +194,56 @@ proc makeStorageProofs*(
196194
var
197195
nodesCache: NodesCache
198196
proofs = newSeqOfCap[seq[seq[byte]]](stoPaths.len())
199-
200197
for stoPath in stoPaths:
201-
let (proof, _) = ?db.makeProof(vid, NibblesBuf.fromBytes stoPath.data, nodesCache)
198+
var proof: seq[seq[byte]]
199+
discard ?db.makeProof(vid, NibblesBuf.fromBytes stoPath.data, nodesCache, proof)
202200
proofs.add(proof)
203201

204202
ok(proofs)
205203

206-
# ----------
204+
proc makeStorageMultiProof(
205+
db: AristoTxRef;
206+
accPath: Hash32;
207+
stoPaths: openArray[Hash32];
208+
nodesCache: var NodesCache;
209+
multiProof: var HashSet[seq[byte]]
210+
): Result[void, AristoError] =
211+
## Note that the function returns an error unless
212+
## the argument `accPath` is valid.
213+
let vid = db.fetchStorageID(accPath).valueOr:
214+
if error == FetchPathStoRootMissing:
215+
return ok()
216+
return err(error)
217+
218+
for stoPath in stoPaths:
219+
var proof: seq[seq[byte]]
220+
discard ?db.makeProof(vid, NibblesBuf.fromBytes stoPath.data, nodesCache, proof)
221+
for node in proof:
222+
multiProof.incl(node)
223+
224+
ok()
225+
226+
proc makeMultiProof*(
227+
db: AristoTxRef;
228+
paths: Table[Hash32, seq[Hash32]], # maps each account path to a list of storage paths
229+
multiProof: var seq[seq[byte]]
230+
): Result[void, AristoError] =
231+
var
232+
nodesCache: NodesCache
233+
proofNodes: HashSet[seq[byte]]
234+
235+
for accPath, stoPaths in paths:
236+
var accProof: seq[seq[byte]]
237+
let exists = ?db.makeProof(STATE_ROOT_VID, NibblesBuf.fromBytes accPath.data, nodesCache, accProof)
238+
for node in accProof:
239+
proofNodes.incl(node)
240+
241+
if exists:
242+
?db.makeStorageMultiProof(accPath, stoPaths, nodesCache, proofNodes)
243+
244+
multiProof = proofNodes.toSeq()
245+
246+
ok()
207247

208248
proc verifyProof*(
209249
chain: openArray[seq[byte]];
@@ -222,7 +262,3 @@ proc verifyProof*(
222262
return err(rc.error)
223263
except RlpError:
224264
return err(PartTrkRlpError)
225-
226-
# ------------------------------------------------------------------------------
227-
# End
228-
# ------------------------------------------------------------------------------

execution_chain/db/core_db/base.nim

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,20 @@ proc getStateRoot*(acc: CoreDbTxRef): CoreDbRc[Hash32] =
307307

308308
ok(rc)
309309

310+
proc multiProof*(
311+
acc: CoreDbTxRef;
312+
paths: Table[Hash32, seq[Hash32]];
313+
multiProof: var seq[seq[byte]]
314+
): CoreDbRc[void] =
315+
## Returns a multiproof for every account and storage path specified
316+
## in the paths table. All rlp-encoded trie nodes from all account
317+
## and storage proofs are returned in a single list.
318+
319+
acc.aTx.makeMultiProof(paths, multiProof).isOkOr:
320+
return err(error.toError("", ProofCreate))
321+
322+
ok()
323+
310324
# ------------ storage ---------------
311325

312326
proc slotProofs*(

execution_chain/stateless/witness_generation.nim

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,42 +25,55 @@ proc build*(
2525
T: type Witness,
2626
witnessKeys: WitnessTable,
2727
preStateLedger: LedgerRef): T =
28+
2829
var
30+
proofPaths: Table[Hash32, seq[Hash32]]
31+
addedCodeHashes: HashSet[Hash32]
32+
accPreimages: Table[Hash32, array[20, byte]]
33+
stoPreimages: Table[Hash32, array[32, byte]]
2934
witness = Witness.init()
30-
addedState = initHashSet[seq[byte]]()
31-
addedCodeHashes = initHashSet[Hash32]()
3235

3336
for key, codeTouched in witnessKeys:
34-
if key.slot.isNone(): # Is an account key
35-
witness.addKey(key.address.data())
37+
let
38+
addressBytes = key.address.data()
39+
accPath = keccak256(addressBytes)
40+
accPreimages[accPath] = addressBytes
3641

37-
let proof = preStateLedger.getAccountProof(key.address)
38-
for trieNode in proof:
39-
addedState.incl(trieNode)
42+
if key.slot.isNone(): # Is an account key
43+
proofPaths.withValue(accPath, v):
44+
discard
45+
do:
46+
proofPaths[accPath] = @[]
4047

48+
# codeTouched is only set for account keys
4149
if codeTouched:
4250
let codeHash = preStateLedger.getCodeHash(key.address)
4351
if codeHash != EMPTY_CODE_HASH and codeHash notin addedCodeHashes:
4452
witness.addCodeHash(codeHash)
4553
addedCodeHashes.incl(codeHash)
4654

47-
# Add the storage slots for this account
48-
var slots: seq[UInt256]
49-
for key2, codeTouched2 in witnessKeys:
50-
if key2.address == key.address and key2.slot.isSome():
51-
let slot = key2.slot.get()
52-
slots.add(slot)
53-
witness.addKey(slot.toBytesBE())
54-
55-
if slots.len() > 0:
56-
let proofs = preStateLedger.getStorageProof(key.address, slots)
57-
doAssert(proofs.len() == slots.len())
58-
for proof in proofs:
59-
for trieNode in proof:
60-
addedState.incl(trieNode)
61-
62-
for s in addedState.items():
63-
witness.addState(s)
55+
else: # Is a slot key
56+
let
57+
slotBytes = key.slot.get().toBytesBE()
58+
slotPath = keccak256(slotBytes)
59+
stoPreimages[slotPath] = slotBytes
60+
61+
proofPaths.withValue(accPath, v):
62+
v[].add(slotPath)
63+
do:
64+
var paths: seq[Hash32]
65+
paths.add(slotPath)
66+
proofPaths[accPath] = paths
67+
68+
var multiProof: seq[seq[byte]]
69+
preStateLedger.txFrame.multiProof(proofPaths, multiProof).isOkOr:
70+
raiseAssert "Failed to get multiproof: " & $$error
71+
witness.state = move(multiProof)
72+
73+
for accPath, stoPaths in proofPaths:
74+
witness.addKey(accPreimages.getOrDefault(accPath))
75+
for stoPath in stoPaths:
76+
witness.addKey(stoPreimages.getOrDefault(stoPath))
6477

6578
witness
6679

@@ -92,10 +105,11 @@ proc build*(
92105
let
93106
blockHashes = ledger.getBlockHashesCache()
94107
earliestBlockNumber = getEarliestCachedBlockNumber(blockHashes)
108+
95109
if earliestBlockNumber.isSome():
96-
var n = parent.number - 1
110+
var n = parent.number
97111
while n >= earliestBlockNumber.get():
112+
dec n
98113
let blockHash = ledger.getBlockHash(BlockNumber(n))
99114
doAssert(blockHash != default(Hash32))
100115
witness.addHeaderHash(blockHash)
101-
dec n

tests/test_stateless_witness_generation.nim

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ suite "Stateless: Witness Generation":
144144

145145
check:
146146
witness.keys.len() == 5
147-
witness.keys[0] == addr1.data()
148-
witness.keys[1] == slot1.toBytesBE()
149-
witness.keys[2] == slot2.toBytesBE()
150-
witness.keys[3] == slot3.toBytesBE()
151-
witness.keys[4] == addr2.data()
147+
witness.keys[0] == addr2.data()
148+
witness.keys[1] == addr1.data()
149+
witness.keys[2] == slot1.toBytesBE()
150+
witness.keys[3] == slot2.toBytesBE()
151+
witness.keys[4] == slot3.toBytesBE()

0 commit comments

Comments
 (0)