Skip to content

Commit d93ffb3

Browse files
authored
Stateless: Witness generation, clearing of witness keys and db persist and get helpers (#3458)
* Implement function to build execution witness from witness keys and proofs from database. * Clear witness keys in ledger after processing each block. * Add functions to persist and get witness by block hash. * Rename/restructure witness files.
1 parent b5e5b5e commit d93ffb3

File tree

10 files changed

+341
-18
lines changed

10 files changed

+341
-18
lines changed

execution_chain/core/executor/process_block.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ proc procBlkEpilogue(
198198
db.persist(
199199
clearEmptyAccount = vmState.com.isSpuriousOrLater(header.number),
200200
clearCache = true,
201+
clearWitness = vmState.com.statelessProviderEnabled
201202
)
202203

203204
var

execution_chain/db/core_db/core_apps.nim

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import
2020
stew/byteutils,
2121
results,
2222
"../.."/[constants],
23+
"../.."/stateless/witness_types,
2324
".."/[aristo, storage_types],
2425
"."/base
2526

@@ -622,6 +623,17 @@ proc persistUncles*(db: CoreDbTxRef, uncles: openArray[Header]): Hash32 =
622623
warn "persistUncles()", unclesHash=result, error=($$error)
623624
return EMPTY_ROOT_HASH
624625
626+
proc persistWitness*(db: CoreDbTxRef, blockHash: Hash32, witness: Witness): Result[void, string] =
627+
db.put(blockHashToWitnessKey(blockHash).toOpenArray, witness.encode()).isOkOr:
628+
return err("persistWitness: " & $$error)
629+
ok()
630+
631+
proc getWitness*(db: CoreDbTxRef, blockHash: Hash32): Result[Witness, string] =
632+
let witnessBytes = db.get(blockHashToWitnessKey(blockHash).toOpenArray).valueOr:
633+
return err("getWitness: " & $$error)
634+
635+
Witness.decode(witnessBytes)
636+
625637
# ------------------------------------------------------------------------------
626638
# End
627639
# ------------------------------------------------------------------------------

execution_chain/db/ledger.nim

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -703,9 +703,16 @@ proc clearEmptyAccounts(ac: LedgerRef) =
703703
ac.deleteEmptyAccount(RIPEMD_ADDR)
704704
ac.ripemdSpecial = false
705705
706+
template getWitnessKeys*(ac: LedgerRef): WitnessTable =
707+
ac.witnessKeys
708+
709+
template clearWitnessKeys*(ac: LedgerRef) =
710+
ac.witnessKeys.clear()
711+
706712
proc persist*(ac: LedgerRef,
707713
clearEmptyAccount: bool = false,
708-
clearCache = false) =
714+
clearCache = false,
715+
clearWitness = false) =
709716
const info = "persist(): "
710717
711718
# make sure all savepoint already committed
@@ -757,6 +764,9 @@ proc persist*(ac: LedgerRef,
757764
758765
ac.isDirty = false
759766
767+
if clearWitness:
768+
ac.clearWitnessKeys()
769+
760770
iterator addresses*(ac: LedgerRef): Address =
761771
# make sure all savepoint already committed
762772
doAssert(ac.savePoint.parentSavepoint.isNil)
@@ -878,12 +888,6 @@ proc getStorageProof*(ac: LedgerRef, address: Address, slots: openArray[UInt256]
878888

879889
storageProof
880890

881-
func getWitnessKeys*(ac: LedgerRef): WitnessTable =
882-
ac.witnessKeys
883-
884-
proc clearWitnessKeys*(ac: LedgerRef) =
885-
ac.witnessKeys.clear()
886-
887891
# ------------------------------------------------------------------------------
888892
# Public virtual read-only methods
889893
# ------------------------------------------------------------------------------

execution_chain/db/storage_types.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type
2828
fcState = 9
2929
beaconHeader = 10
3030
wdKey = 11
31+
witness = 12
3132

3233
DbKey* = object
3334
# The first byte stores the key type. The rest are key-specific values
@@ -104,6 +105,11 @@ func withdrawalsKey*(h: Hash32): DbKey {.inline.} =
104105
result.data[1 .. 32] = h.data
105106
result.dataEndPos = uint8 32
106107

108+
func blockHashToWitnessKey*(h: Hash32): DbKey {.inline.} =
109+
result.data[0] = byte ord(witness)
110+
result.data[1 .. 32] = h.data
111+
result.dataEndPos = uint8 32
112+
107113
template toOpenArray*(k: DbKey): openArray[byte] =
108114
k.data.toOpenArray(0, int(k.dataEndPos))
109115

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Nimbus
2+
# Copyright (c) 2025 Status Research & Development GmbH
3+
# Licensed under either of
4+
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
5+
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
6+
# at your option.
7+
# This file may not be copied, modified, or distributed except according to
8+
# those terms.
9+
10+
{.push raises: [].}
11+
12+
import
13+
std/[tables, sets],
14+
eth/common,
15+
../db/ledger,
16+
./witness_types
17+
18+
export
19+
common,
20+
ledger,
21+
witness_types
22+
23+
proc build*(
24+
T: type Witness,
25+
ledger: LedgerRef,
26+
codes: var seq[seq[byte]]): T =
27+
var
28+
witness = Witness.init()
29+
addedStateHashes = initHashSet[Hash32]()
30+
addedCodeHashes = initHashSet[Hash32]()
31+
32+
for key, codeTouched in ledger.getWitnessKeys():
33+
let (adr, maybeSlot) = key
34+
if maybeSlot.isSome():
35+
let slot = maybeSlot.get()
36+
witness.addKey(slot.toBytesBE())
37+
38+
let proofs = ledger.getStorageProof(key.address, @[slot])
39+
doAssert(proofs.len() == 1)
40+
for trieNode in proofs[0]:
41+
let nodeHash = keccak256(trieNode)
42+
if nodeHash notin addedStateHashes:
43+
witness.addState(trieNode)
44+
addedStateHashes.incl(nodeHash)
45+
else:
46+
witness.addKey(key.address.data())
47+
48+
let proof = ledger.getAccountProof(key.address)
49+
for trieNode in proof:
50+
let nodeHash = keccak256(trieNode)
51+
if nodeHash notin addedStateHashes:
52+
witness.addState(trieNode)
53+
addedStateHashes.incl(nodeHash)
54+
55+
if codeTouched:
56+
let (codeHash, code) = ledger.getCode(key.address, returnHash = true)
57+
if codeHash != EMPTY_CODE_HASH and codeHash notin addedCodeHashes:
58+
codes.add(code.bytes)
59+
witness.addCodeHash(codeHash)
60+
addedCodeHashes.incl(codeHash)
61+
62+
witness

execution_chain/stateless/witness.nim renamed to execution_chain/stateless/witness_types.nim

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
# This file may not be copied, modified, or distributed except according to
88
# those terms.
99

10+
{.push raises: [].}
11+
1012
import
1113
eth/common,
1214
eth/rlp,
@@ -16,24 +18,57 @@ export
1618
common,
1719
results
1820

19-
{.push raises: [].}
20-
2121
type
22+
Witness* = object
23+
state*: seq[seq[byte]] # MPT trie nodes accessed while executing the block.
24+
keys*: seq[seq[byte]] # Ordered list of access keys (address bytes or storage slots bytes).
25+
codeHashes*: seq[Hash32] # Code hashes of the bytecode required by the witness.
26+
headerHashes*: seq[Hash32] # Hashes of block headers which are required by the witness.
27+
2228
ExecutionWitness* = object
2329
state*: seq[seq[byte]] # MPT trie nodes accessed while executing the block.
24-
codes*: seq[seq[byte]] # Contract bytecodes read while executing the block.
2530
keys*: seq[seq[byte]] # Ordered list of access keys (address bytes or storage slots bytes).
31+
codes*: seq[seq[byte]] # Contract bytecodes read while executing the block.
2632
headers*: seq[Header] # Block headers required for proving correctness of stateless execution.
2733
# Stores the parent block headers needed to verify that the state reads are correct with respect
2834
# to the pre-state root.
2935

36+
func init*(
37+
T: type Witness,
38+
state = newSeq[seq[byte]](),
39+
keys = newSeq[seq[byte]](),
40+
codeHashes = newSeq[Hash32](),
41+
headerHashes = newSeq[Hash32]()): T =
42+
Witness(state: state, keys: keys, headerHashes: headerHashes)
43+
44+
template addState*(witness: var Witness, trieNode: seq[byte]) =
45+
witness.state.add(trieNode)
46+
47+
template addKey*(witness: var Witness, key: openArray[byte]) =
48+
witness.keys.add(@key)
49+
50+
template addCodeHash*(witness: var Witness, codeHash: Hash32) =
51+
witness.codeHashes.add(codeHash)
52+
53+
template addHeaderHash*(witness: var Witness, headerHash: Hash32) =
54+
witness.headerHashes.add(headerHash)
55+
56+
func encode*(witness: Witness): seq[byte] =
57+
rlp.encode(witness)
58+
59+
func decode*(T: type Witness, witnessBytes: openArray[byte]): Result[T, string] =
60+
try:
61+
ok(rlp.decode(witnessBytes, T))
62+
except RlpError as e:
63+
err(e.msg)
64+
3065
func init*(
3166
T: type ExecutionWitness,
3267
state = newSeq[seq[byte]](),
33-
codes = newSeq[seq[byte]](),
3468
keys = newSeq[seq[byte]](),
69+
codes = newSeq[seq[byte]](),
3570
headers = newSeq[Header]()): T =
36-
ExecutionWitness(state: state, codes: codes, keys: keys, headers: headers)
71+
ExecutionWitness(state: state, keys: keys, codes: codes, headers: headers)
3772
3873
template addState*(witness: var ExecutionWitness, trieNode: seq[byte]) =
3974
witness.state.add(trieNode)

tests/all_tests.nim

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ import
3636
test_txpool,
3737
test_networking,
3838
test_pooled_tx,
39-
test_stateless_witness,
39+
test_stateless_witness_types,
40+
test_stateless_witness_generation,
4041
# These two suites are much slower than all the rest, so run them last
4142
test_blockchain_json,
4243
test_generalstate_json,

tests/test_ledger.nim

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,29 @@ proc runLedgerBasicOperationsTests() =
754754
keysList[4][0].slot == Opt.none(UInt256)
755755
keysList[4][1] == false
756756

757+
test "Witness keys - Clear cache":
758+
var
759+
ac = LedgerRef.init(memDB.baseTxFrame(), false, collectWitness = true)
760+
addr1 = initAddr(1)
761+
addr2 = initAddr(2)
762+
addr3 = initAddr(3)
763+
slot1 = 1.u256
764+
765+
discard ac.getAccount(addr1)
766+
discard ac.getCode(addr2)
767+
discard ac.getCode(addr1)
768+
discard ac.getStorage(addr2, slot1)
769+
discard ac.getStorage(addr1, slot1)
770+
discard ac.getStorage(addr2, slot1)
771+
discard ac.getAccount(addr3)
772+
773+
check ac.getWitnessKeys().len() == 5
774+
775+
ac.persist() # persist should not clear the witness keys by default
776+
check ac.getWitnessKeys().len() == 5
777+
778+
ac.persist(clearWitness = true)
779+
check ac.getWitnessKeys().len() == 0
757780

758781
# ------------------------------------------------------------------------------
759782
# Main function(s)

0 commit comments

Comments
 (0)