-
Notifications
You must be signed in to change notification settings - Fork 10
Feature/virtual zkevm state proof v2 #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
b2c3cdc
initial scaffolding
garyschulte 511a257
bump besuVersion, fixes macos tahoe local test issue
garyschulte 9ac4f71
add besu eth_simulateV1 client
garyschulte b1343f8
initial impl for wrapped eth_simulateV1
garyschulte 2413ef7
add unit-integration test
garyschulte 41838b9
spotless
garyschulte 3d99660
use ephemeral TraceManager for virtual blocks, build on parernt block…
garyschulte b2ed780
propagate errors from besu eth_simulateV1 through to caller of rollup…
garyschulte 9220190
interim commit for testing
garyschulte 5954f80
another interim commit
garyschulte af1f93e
use a layered worldstate storage rather than trying to snapshot what …
garyschulte cbc4104
remove debugging statements
garyschulte 44c6e4f
use main vertx instance for web clients to prevent hang-on-exit races
garyschulte 6a04fea
spotless and unit test fix
garyschulte a155c1c
add end root hash to virtual proof response
garyschulte a5e7698
clean up
garyschulte f854431
account for deleted leaf keys in layered worldstate
garyschulte 906e8b8
add tests for LayeredWorldStateStorage
garyschulte c3ba2ef
address feedback about interrupted exception. also refactor sync to …
garyschulte 8ff6eac
fix breakage in LayeredWorldStateStorage, implement WorldStateUpdater
garyschulte ceedb33
refacor layered worldstate
matkt a4fd674
Merge branch 'main' into feature/virtual-zkevm-state-proof-v2
matkt 173c1a0
merge main
matkt b9983e2
fix build
matkt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
core/src/main/java/net/consensys/shomei/storage/worldstate/LayeredWorldStateStorage.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| /* | ||
| * Copyright ConsenSys Software Inc., 2026 | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
| * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations under the License. | ||
| */ | ||
|
|
||
| package net.consensys.shomei.storage.worldstate; | ||
|
|
||
| import net.consensys.shomei.trie.model.FlattenedLeaf; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Optional; | ||
|
|
||
| import org.apache.tuweni.bytes.Bytes; | ||
| import org.hyperledger.besu.datatypes.Hash; | ||
|
|
||
| /** | ||
| * LayeredWorldStateStorage composes an in-memory overlay and a base (parent) storage. | ||
| * All reads check the overlay first, then fall back to the parent. | ||
| * All writes go only to the overlay, leaving the parent unmodified. | ||
| * Deletes are tracked explicitly to prevent fallback to parent for deleted keys. | ||
| * | ||
| * This is useful for virtual/simulated blocks where we want to apply changes temporarily | ||
| * without modifying the cached parent state or persist the state permanently. | ||
| */ | ||
| public class LayeredWorldStateStorage extends InMemoryWorldStateStorage { | ||
|
|
||
| private final WorldStateStorage parent; | ||
|
|
||
| public LayeredWorldStateStorage(final WorldStateStorage parent) { | ||
| super(); | ||
| this.parent = parent; | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<FlattenedLeaf> getFlatLeaf(final Bytes key) { | ||
| // null = key not in overlay at all → ask parent | ||
| // Optional.empty() = key explicitly deleted in overlay → return empty | ||
| // Optional.of(val) = key exists in overlay → return value | ||
| final Optional<FlattenedLeaf> overlayValue = getFlatLeafStorage().get(key); | ||
| if (overlayValue == null) { | ||
| return parent.getFlatLeaf(key); | ||
| } | ||
| return overlayValue; | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Bytes> getTrieNode(final Bytes location, final Bytes nodeHash) { | ||
| final Optional<Bytes> overlayValue = getTrieNodeStorage().get(location); | ||
| if (overlayValue == null) { | ||
| return parent.getTrieNode(location, nodeHash); | ||
| } | ||
| return overlayValue; | ||
| } | ||
|
|
||
| @Override | ||
| public Range getNearestKeys(final Bytes hkey) { | ||
|
|
||
| final Range parentRange = parent.getNearestKeys(hkey); | ||
|
|
||
| // --- Center: check overlay directly, then parent --- | ||
| Optional<Map.Entry<Bytes, FlattenedLeaf>> centerNode; | ||
| final Optional<FlattenedLeaf> overlayCenter = getFlatLeafStorage().get(hkey); | ||
| if (overlayCenter != null && overlayCenter.isPresent()) { | ||
| centerNode = Optional.of(Map.entry(hkey, overlayCenter.get())); | ||
| } else if (overlayCenter != null && overlayCenter.isEmpty()) { | ||
| centerNode = Optional.empty(); | ||
| } else { | ||
| centerNode = parentRange.getCenterNode(); | ||
| } | ||
|
|
||
| // --- Left: closest key < hkey --- | ||
| final Map.Entry<Bytes, FlattenedLeaf> leftNode = | ||
| resolveLeftNode(hkey, parentRange); | ||
|
|
||
| // --- Right: closest key > hkey --- | ||
| final Map.Entry<Bytes, FlattenedLeaf> rightNode = | ||
| resolveRightNode(hkey, parentRange); | ||
|
|
||
| return new Range(leftNode, centerNode, rightNode); | ||
| } | ||
|
|
||
| private Map.Entry<Bytes, FlattenedLeaf> resolveLeftNode( | ||
| final Bytes hkey, final Range parentRange) { | ||
|
|
||
| final Map.Entry<Bytes, FlattenedLeaf> parentLeft = | ||
| getValidParentNode( | ||
| parentRange.getLeftNodeKey(), parentRange.getLeftNodeValue(), true); | ||
|
|
||
| Map.Entry<Bytes, Optional<FlattenedLeaf>> overlayLeft = | ||
| getFlatLeafStorage().lowerEntry(hkey); | ||
| // skip deleted entries | ||
| while (overlayLeft != null && overlayLeft.getValue().isEmpty()) { | ||
| overlayLeft = getFlatLeafStorage().lowerEntry(overlayLeft.getKey()); | ||
| } | ||
|
|
||
| if (overlayLeft != null && overlayLeft.getValue().isPresent() | ||
| && overlayLeft.getKey().compareTo(parentLeft.getKey()) > 0) { | ||
| return Map.entry(overlayLeft.getKey(), overlayLeft.getValue().get()); | ||
| } | ||
|
|
||
| return parentLeft; | ||
| } | ||
|
|
||
| private Map.Entry<Bytes, FlattenedLeaf> resolveRightNode( | ||
| final Bytes hkey, final Range parentRange) { | ||
|
|
||
| final Map.Entry<Bytes, FlattenedLeaf> parentRight = | ||
| getValidParentNode( | ||
| parentRange.getRightNodeKey(), parentRange.getRightNodeValue(), false); | ||
|
|
||
| Map.Entry<Bytes, Optional<FlattenedLeaf>> overlayRight = | ||
| getFlatLeafStorage().higherEntry(hkey); | ||
| // skip deleted entries | ||
| while (overlayRight != null && overlayRight.getValue().isEmpty()) { | ||
| overlayRight = getFlatLeafStorage().higherEntry(overlayRight.getKey()); | ||
| } | ||
|
|
||
| if (overlayRight != null && overlayRight.getKey().compareTo(parentRight.getKey()) < 0) { | ||
| return Map.entry(overlayRight.getKey(), overlayRight.getValue().get()); | ||
| } | ||
|
|
||
| return parentRight; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a key has been explicitly deleted in the overlay. | ||
| */ | ||
| private boolean isDeletedInOverlay(final Bytes key) { | ||
| final Optional<FlattenedLeaf> value = getFlatLeafStorage().get(key); | ||
| return value != null && value.isEmpty(); | ||
| } | ||
|
|
||
| /** | ||
| * Walk through parent storage to find a node that hasn't been deleted in the overlay. | ||
| */ | ||
| private Map.Entry<Bytes, FlattenedLeaf> getValidParentNode( | ||
| final Bytes initialKey, | ||
| final FlattenedLeaf initialValue, | ||
| final boolean searchLeft) { | ||
|
|
||
| Bytes currentKey = initialKey; | ||
| FlattenedLeaf currentValue = initialValue; | ||
|
|
||
| while (isDeletedInOverlay(currentKey) && !currentKey.equals(Bytes.EMPTY)) { | ||
| final Range nextRange = parent.getNearestKeys(currentKey); | ||
|
|
||
| if (searchLeft) { | ||
| final Bytes nextKey = nextRange.getLeftNodeKey(); | ||
| if (nextKey.equals(currentKey) || nextKey.equals(Bytes.EMPTY)) { | ||
| break; | ||
| } | ||
| currentKey = nextKey; | ||
| currentValue = nextRange.getLeftNodeValue(); | ||
| } else { | ||
| final Bytes nextKey = nextRange.getRightNodeKey(); | ||
| if (nextKey.equals(currentKey) || nextKey.equals(Bytes.EMPTY)) { | ||
| break; | ||
| } | ||
| currentKey = nextKey; | ||
| currentValue = nextRange.getRightNodeValue(); | ||
| } | ||
| } | ||
|
|
||
| return Map.entry(currentKey, currentValue); | ||
| } | ||
|
|
||
| @Override | ||
| public TrieUpdater updater() { | ||
| return new LayeredTrieUpdater(this); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Wrapper around the overlay's TrieUpdater that tracks deleted keys and handles re-insertions. | ||
| */ | ||
| private static class LayeredTrieUpdater implements WorldStateUpdater { | ||
| private final LayeredWorldStateStorage delegate; | ||
|
|
||
| LayeredTrieUpdater(final LayeredWorldStateStorage delegate) { | ||
| this.delegate = delegate; | ||
| } | ||
|
|
||
| @Override | ||
| public void putFlatLeaf(Bytes key, FlattenedLeaf value) { | ||
| delegate.putFlatLeaf(key, value); | ||
| } | ||
|
|
||
| @Override | ||
| public void removeFlatLeafValue(Bytes key) { | ||
| delegate.removeFlatLeafValue(key); | ||
| } | ||
|
|
||
| @Override | ||
| public void putTrieNode(Bytes location, Bytes nodeHash, Bytes value) { | ||
| delegate.putTrieNode(location, nodeHash, value); | ||
| } | ||
|
|
||
| @Override | ||
| public void commit() { | ||
| delegate.commit(); | ||
| } | ||
|
|
||
| @Override | ||
| public void setBlockHash(final Hash blockHash) { | ||
| delegate.setBlockHash(blockHash); | ||
| } | ||
|
|
||
| @Override | ||
| public void setBlockNumber(final long blockNumber) { | ||
| delegate.setBlockNumber(blockNumber); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Long> getWorldStateBlockNumber() { | ||
| final Optional<Long> overlayResult = super.getWorldStateBlockNumber(); | ||
| return overlayResult.isPresent() ? overlayResult : parent.getWorldStateBlockNumber(); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Hash> getWorldStateBlockHash() { | ||
| final Optional<Hash> overlayResult = super.getWorldStateBlockHash(); | ||
| return overlayResult.isPresent() ? overlayResult : parent.getWorldStateBlockHash(); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Hash> getWorldStateRootHash() { | ||
| final Optional<Hash> overlayResult = super.getWorldStateRootHash(); | ||
| return overlayResult.isPresent() ? overlayResult : parent.getWorldStateRootHash(); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Hash> getZkStateRootHash(final long blockNumber) { | ||
| final Optional<Hash> overlayResult = super.getZkStateRootHash(blockNumber); | ||
| return overlayResult.isPresent() ? overlayResult : parent.getZkStateRootHash(blockNumber); | ||
| } | ||
|
|
||
| @Override | ||
| public WorldStateStorage snapshot() { | ||
| // Snapshots not supported on layered storage | ||
| throw new UnsupportedOperationException("Cannot snapshot a layered storage"); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| // Don't close parent — it's managed elsewhere | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.