From f17eba634de91b2d78b96dcd78d257d6e1f249d0 Mon Sep 17 00:00:00 2001 From: kdeme <7857583+kdeme@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:35:04 +0200 Subject: [PATCH] Witness: Implement optimisation where touched code can be skipped Implementation of optimisation where touched code can be skipped from the witness if that same code was deployed in an earlier transaction in the same block. In this scenario the witness can be smaller as that code is technically not required for the stateless execution, as it is already available via the transaction in the block, and thus in stateless execution will already be there after executing the earlier transaction. Not fully sure however if this added complexity is worth the optimisation. As in, I have no view on how often a case like that occurs, and maybe more edge cases exist, but it is however a scenario tested in EEST zkevm tests. We could of course decide not to implement this, as the bigger witness will also work as input in practice. --- .../core/chain/forked_chain/chain_private.nim | 1 + execution_chain/core/chain/persist_blocks.nim | 1 + execution_chain/db/ledger.nim | 40 +++++++++++++++---- tests/eest/eest_stateless_execution_test.nim | 1 - 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/execution_chain/core/chain/forked_chain/chain_private.nim b/execution_chain/core/chain/forked_chain/chain_private.nim index 32e493d7c2..490ccc4b3a 100644 --- a/execution_chain/core/chain/forked_chain/chain_private.nim +++ b/execution_chain/core/chain/forked_chain/chain_private.nim @@ -118,6 +118,7 @@ proc processBlock*( # witness keys and block hashes when processing the block as these will be used # when building the witness. vmState.ledger.clearWitnessKeys() + vmState.ledger.clearDeployedCodeHashes() vmState.ledger.clearBlockHashesCache() processBlock() diff --git a/execution_chain/core/chain/persist_blocks.nim b/execution_chain/core/chain/persist_blocks.nim index 71fa3cb642..c48f740c1a 100644 --- a/execution_chain/core/chain/persist_blocks.nim +++ b/execution_chain/core/chain/persist_blocks.nim @@ -188,6 +188,7 @@ proc persistBlock*(p: var Persister, blk: Block): Result[void, string] = # witness keys and block hashes when processing the block as these will be used # when building the witness. vmState.ledger.clearWitnessKeys() + vmState.ledger.clearDeployedCodeHashes() vmState.ledger.clearBlockHashesCache() processBlock() diff --git a/execution_chain/db/ledger.nim b/execution_chain/db/ledger.nim index a29680c615..ba397615dc 100644 --- a/execution_chain/db/ledger.nim +++ b/execution_chain/db/ledger.nim @@ -110,6 +110,8 @@ type dirty: Table[Address, AccountRef] selfDestruct: HashSet[Address] accessList: ac_access_list.AccessList + deployedCodeHashes: HashSet[Hash32] + ## Caches codeHashes deployed via CREATE in this savepoint. const resetFlags = { @@ -376,6 +378,7 @@ proc commit*(ledger: LedgerRef, savePoint: LedgerSpRef) = ledger.savePoint.dirty.mergeAndReset(savePoint.dirty) ledger.savePoint.accessList.mergeAndReset(savePoint.accessList) ledger.savePoint.selfDestruct.mergeAndReset(savePoint.selfDestruct) + ledger.savePoint.deployedCodeHashes.mergeAndReset(savePoint.deployedCodeHashes) savePoint.parentSavePoint = nil # Release memory @@ -408,16 +411,30 @@ proc getNonce*(ledger: LedgerRef, address: Address): AccountNonce = if acc.isNil: EMPTY_ACCOUNT.nonce else: acc.statement.nonce +func isDeployedCode(ledger: LedgerRef, codeHash: Hash32): bool = + ## Check if codeHash was deployed in any currently active save point. + var sp = ledger.savePoint + while sp != nil: + if codeHash in sp.deployedCodeHashes: + return true + sp = sp.parentSavePoint + false + proc getCode*(ledger: LedgerRef, address: Address, returnHash: static[bool] = false): auto = + let acc = ledger.getAccount(address, false) + if ledger.collectWitness: let lookupKey = (address, Opt.none(UInt256)) - # We overwrite any existing record here so that codeTouched is always set to - # true even if an account was previously accessed without touching the code - ledger.witnessKeys[lookupKey] = true + # Only mark codeTouched if the code isn't deployed in this block. + # Deployed code can be recovered from tx data directly. + # We overwrite any existing false entry so codeTouched is set to true + # even if the account was previously accessed without touching the code. + if acc.isNil or acc.statement.codeHash == EMPTY_CODE_HASH or + not ledger.isDeployedCode(acc.statement.codeHash): + ledger.witnessKeys[lookupKey] = true - let acc = ledger.getAccount(address, false) if acc.isNil: when returnHash: return (EMPTY_CODE_HASH, CodeBytesRef()) @@ -445,13 +462,14 @@ proc getCode*(ledger: LedgerRef, acc.code proc getCodeSize*(ledger: LedgerRef, address: Address): int = + let acc = ledger.getAccount(address, false) + if ledger.collectWitness: let lookupKey = (address, Opt.none(UInt256)) - # We overwrite any existing record here so that codeTouched is always set to - # true even if an account was previously accessed without touching the code - ledger.witnessKeys[lookupKey] = true + if acc.isNil or acc.statement.codeHash == EMPTY_CODE_HASH or + not ledger.isDeployedCode(acc.statement.codeHash): + ledger.witnessKeys[lookupKey] = true - let acc = ledger.getAccount(address, false) if acc.isNil: return 0 @@ -575,6 +593,8 @@ proc setCode*(ledger: LedgerRef, address: Address, code: seq[byte]) = # a given that it will be executed within LRU range acc.code = ledger.code.get(codeHash).valueOr(CodeBytesRef.init(code)) acc.flags.incl CodeChanged + if ledger.collectWitness and code.len > 0: + ledger.savePoint.deployedCodeHashes.incl(codeHash) proc setStorage*(ledger: LedgerRef, address: Address, slot, value: UInt256) = let acc = ledger.getAccount(address) @@ -663,6 +683,9 @@ template getWitnessKeys*(ledger: LedgerRef): WitnessTable = template clearWitnessKeys*(ledger: LedgerRef) = ledger.witnessKeys.clear() +template clearDeployedCodeHashes*(ledger: LedgerRef) = + ledger.savePoint.deployedCodeHashes.clear() + proc getBlockHash*(ledger: LedgerRef, blockNumber: BlockNumber): Hash32 = ledger.blockHashes.get(blockNumber).valueOr: let blockHash = ledger.txFrame.getBlockHash(blockNumber).valueOr: @@ -742,6 +765,7 @@ proc persist*(ledger: LedgerRef, if clearWitness: ledger.clearWitnessKeys() + ledger.clearDeployedCodeHashes() ledger.clearBlockHashesCache() iterator addresses*(ledger: LedgerRef): Address = diff --git a/tests/eest/eest_stateless_execution_test.nim b/tests/eest/eest_stateless_execution_test.nim index 8b5cb13ba4..a86b874a19 100644 --- a/tests/eest/eest_stateless_execution_test.nim +++ b/tests/eest/eest_stateless_execution_test.nim @@ -61,7 +61,6 @@ const skipFiles = [ "varying_calldata_costs.json", "bal_7002_partial_sweep.json", "witness_codes_delegated_eoa_insufficient_balance.json", - "witness_codes_create_same_hash_then_read.json", "witness_headers_blockhash_at_offset.json", "witness_headers_blockhash_boundary.json", "witness_headers_blockhash_in_reverted_tx.json",