Skip to content
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

SnapServer using FlatDB Strategy #5887

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5aa2c93
move FlatDbReader to FlatDbStrategy (including writes), add getNearestTo
garyschulte Sep 19, 2023
2a86756
javadoc, remove archive interface references, update TODO
garyschulte Sep 19, 2023
b42b060
review feedback
garyschulte Sep 20, 2023
e2b1e96
[skip ci] skip ci - initial commit
garyschulte Sep 16, 2023
b6778ad
initial test coverage for snap server account range
garyschulte Sep 22, 2023
70c5d4e
small streams refactoring, tests for accounts
garyschulte Sep 22, 2023
1487721
[skip ci] skip ci ; initial account storage message handling, proving…
garyschulte Sep 23, 2023
053f913
snapsync server storage range implementation and initial test coverage
garyschulte Sep 25, 2023
a40b9ba
getTrieNodes and test coverage and rebase on main
garyschulte Sep 29, 2023
fe2303f
changes for state-root-less retrieval of code by hash in snapserver
garyschulte Oct 2, 2023
0c8c8d6
add caffeine cache for stateroot -> blockHash in CachedTrieLogManager…
garyschulte Oct 3, 2023
c17d999
handle empy ranges in snap client
garyschulte Oct 3, 2023
d1e82d2
Compact encoding fix for getTrie, ignore empty ranges in StackTrie, c…
garyschulte Oct 4, 2023
6dfe4a5
changes from test-snap-server branch
garyschulte Oct 24, 2023
0aa6b01
revisit proofs that are included with partial storage responses
garyschulte Oct 25, 2023
4a64eb9
add limits for trienode and code requests, and test coverage
garyschulte Oct 27, 2023
7e6dff5
add start/stop of snap on the basis of initial sync, refactor caching…
garyschulte Nov 2, 2023
906aaf1
add FlatWorldStateArchive interface, add uncached states to cache, st…
garyschulte Nov 2, 2023
118c947
use adCachedLayer when fetching worldstate not already in cache
garyschulte Nov 3, 2023
92a4f0a
aggregate of fixes from goerli testing with different clients
garyschulte Nov 10, 2023
1bd1dd2
gate snap server on full flat param for now
garyschulte Nov 11, 2023
de19787
send next slot range in the case of an empty storage range
garyschulte Nov 11, 2023
093d1e8
fix snap server startup condition, bump up request limit fudge factor
garyschulte Nov 12, 2023
729c294
ensure we send proof of exclusion and no slots for the last empty sto…
garyschulte Nov 12, 2023
ce92839
some log cleanup
garyschulte Nov 12, 2023
215cd0c
refactor check for full flat
garyschulte Jan 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,6 @@ public BesuController build() {
peerValidators,
Optional.empty());

final Optional<SnapProtocolManager> maybeSnapProtocolManager =
createSnapProtocolManager(peerValidators, ethPeers, snapMessages, worldStateArchive);

final PivotBlockSelector pivotBlockSelector =
createPivotSelector(
protocolSchedule, protocolContext, ethContext, syncState, metricsSystem);
Expand All @@ -764,6 +761,9 @@ public BesuController build() {

protocolContext.setSynchronizer(Optional.of(synchronizer));

final Optional<SnapProtocolManager> maybeSnapProtocolManager =
createSnapProtocolManager(protocolContext, peerValidators, ethPeers, snapMessages);

final MiningCoordinator miningCoordinator =
createMiningCoordinator(
protocolSchedule,
Expand Down Expand Up @@ -1086,12 +1086,12 @@ protected ProtocolContext createProtocolContext(
}

private Optional<SnapProtocolManager> createSnapProtocolManager(
final ProtocolContext protocolContext,
final List<PeerValidator> peerValidators,
final EthPeers ethPeers,
final EthMessages snapMessages,
final WorldStateArchive worldStateArchive) {
final EthMessages snapMessages) {
return Optional.of(
new SnapProtocolManager(peerValidators, ethPeers, snapMessages, worldStateArchive));
new SnapProtocolManager(peerValidators, ethPeers, snapMessages, protocolContext));
}

WorldStateArchive createWorldStateArchive(
Expand Down
1 change: 1 addition & 0 deletions ethereum/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {

implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.google.guava:guava'
implementation 'com.github.ben-manes.caffeine:caffeine'
implementation 'com.google.dagger:dagger'
annotationProcessor 'com.google.dagger:dagger-compiler'
implementation 'io.opentelemetry:opentelemetry-api'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@
import org.hyperledger.besu.testutil.MockExecutorService;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand All @@ -59,7 +60,8 @@ public class PrunerIntegrationTest {

private final BlockDataGenerator gen = new BlockDataGenerator();
private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem();
private final Map<Bytes, Optional<byte[]>> hashValueStore = new HashMap<>();
private final NavigableMap<Bytes, Optional<byte[]>> hashValueStore =
new TreeMap<>(Comparator.comparing(Bytes::toHexString));
private final InMemoryKeyValueStorage stateStorage = new TestInMemoryStorage(hashValueStore);
private final WorldStateStorage worldStateStorage =
new ForestWorldStateKeyValueStorage(stateStorage);
Expand Down Expand Up @@ -260,7 +262,7 @@ private MerkleTrie<Bytes32, Bytes> createStorageTrie(final Bytes32 rootHash) {
// Proxy class so that we have access to the constructor that takes our own map
private static class TestInMemoryStorage extends InMemoryKeyValueStorage {

public TestInMemoryStorage(final Map<Bytes, Optional<byte[]>> hashValueStore) {
public TestInMemoryStorage(final NavigableMap<Bytes, Optional<byte[]>> hashValueStore) {
super(hashValueStore);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -41,6 +42,8 @@
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The WorldStateProofProvider class is responsible for providing proofs for world state entries. It
Expand All @@ -49,6 +52,7 @@
public class WorldStateProofProvider {

private final WorldStateStorage worldStateStorage;
private static final Logger LOG = LoggerFactory.getLogger(WorldStateProofProvider.class);

public WorldStateProofProvider(final WorldStateStorage worldStateStorage) {
this.worldStateStorage = worldStateStorage;
Expand Down Expand Up @@ -85,7 +89,8 @@ private SortedMap<UInt256, Proof<Bytes>> getStorageProofs(
final List<UInt256> accountStorageKeys) {
final MerkleTrie<Bytes32, Bytes> storageTrie =
newAccountStorageTrie(accountHash, account.getStorageRoot());
final NavigableMap<UInt256, Proof<Bytes>> storageProofs = new TreeMap<>();
final NavigableMap<UInt256, Proof<Bytes>> storageProofs =
new TreeMap<>(Comparator.comparing(Bytes32::toHexString));
accountStorageKeys.forEach(
key -> storageProofs.put(key, storageTrie.getValueWithProof(Hash.hash(key))));
return storageProofs;
Expand Down Expand Up @@ -153,19 +158,26 @@ public boolean isValidRangeProof(
final SortedMap<Bytes32, Bytes> keys) {

// check if it's monotonic increasing
if (!Ordering.natural().isOrdered(keys.keySet())) {
if (keys.size() > 1 && !Ordering.natural().isOrdered(keys.keySet())) {
return false;
}

// when proof is empty we need to have all the keys to reconstruct the trie
// when proof is empty and we requested the full range, we should
// have all the keys to reconstruct the trie
if (proofs.isEmpty()) {
final MerkleTrie<Bytes, Bytes> trie = new SimpleMerklePatriciaTrie<>(Function.identity());
// add the received keys in the trie
for (Map.Entry<Bytes32, Bytes> key : keys.entrySet()) {
trie.put(key.getKey(), key.getValue());
if (startKeyHash.equals(Bytes32.ZERO)) {
final MerkleTrie<Bytes, Bytes> trie = new SimpleMerklePatriciaTrie<>(Function.identity());
// add the received keys in the trie
for (Map.Entry<Bytes32, Bytes> key : keys.entrySet()) {
trie.put(key.getKey(), key.getValue());
}
return rootHash.equals(trie.getRootHash());
} else {
// TODO: possibly accept a node loader so we can verify this with already
// completed partial storage requests
LOG.info("failing proof due to incomplete range without proofs");
return false;
}

return rootHash.equals(trie.getRootHash());
}

// reconstruct a part of the trie with the proof
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@
import org.hyperledger.besu.ethereum.trie.bonsai.cache.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.trie.bonsai.cache.CachedWorldStorageManager;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.flat.FullFlatDbStrategy;
import org.hyperledger.besu.ethereum.trie.bonsai.trielog.TrieLogManager;
import org.hyperledger.besu.ethereum.trie.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.trie.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.FlatWorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.worldstate.WorldState;
import org.hyperledger.besu.plugin.BesuContext;
Expand All @@ -54,7 +55,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BonsaiWorldStateProvider implements WorldStateArchive {
public class BonsaiWorldStateProvider implements FlatWorldStateArchive {

private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateProvider.class);

Expand Down Expand Up @@ -130,7 +131,7 @@ public Optional<WorldState> get(final Hash rootHash, final Hash blockHash) {

@Override
public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) {
return cachedWorldStorageManager.containWorldStateStorage(blockHash)
return cachedWorldStorageManager.contains(blockHash)
|| persistedState.blockHash().equals(blockHash)
|| worldStateStorage.isWorldStateAvailable(rootHash, blockHash);
}
Expand Down Expand Up @@ -336,8 +337,9 @@ public TrieLogManager getTrieLogManager() {
return trieLogManager;
}

public CachedWorldStorageManager getCachedWorldStorageManager() {
return cachedWorldStorageManager;
@Override
public Optional<CachedWorldStorageManager> getCachedWorldStorageManager() {
return Optional.of(cachedWorldStorageManager);
}

@Override
Expand All @@ -348,6 +350,11 @@ public void resetArchiveStateTo(final BlockHeader blockHeader) {
blockHeader, persistedState.getWorldStateRootHash(), persistedState);
}

@Override
public boolean isFullFlat() {
return worldStateStorage.getFlatDbStrategy() instanceof FullFlatDbStrategy;
}

@Override
public <U> Optional<U> getAccountProof(
final BlockHeader blockHeader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.trie.bonsai.cache;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.trie.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage;
Expand All @@ -29,8 +30,11 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -41,6 +45,11 @@ public class CachedWorldStorageManager
private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class);
private final BonsaiWorldStateProvider archive;
private final EvmConfiguration evmConfiguration;
private final Cache<Hash, BlockHeader> stateRootToBlockHeaderCache =
Caffeine.newBuilder()
.maximumSize(RETAINED_LAYERS)
.expireAfterWrite(100, TimeUnit.MINUTES)
.build();

private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage;
private final Map<Bytes32, CachedBonsaiWorldView> cachedWorldStatesByHash;
Expand Down Expand Up @@ -104,6 +113,8 @@ public synchronized void addCachedLayer(
blockHeader,
((BonsaiWorldStateLayerStorage) forWorldState.getWorldStateStorage()).clone()));
}
// add stateroot -> blockHeader cache entry
stateRootToBlockHeaderCache.put(blockHeader.getStateRoot(), blockHeader);
}
scrubCachedLayers(blockHeader.getNumber());
}
Expand Down Expand Up @@ -192,14 +203,59 @@ public Optional<BonsaiWorldState> getHeadWorldState(
});
}

public boolean containWorldStateStorage(final Hash blockHash) {
public boolean contains(final Hash blockHash) {
return cachedWorldStatesByHash.containsKey(blockHash);
}

public void reset() {
this.cachedWorldStatesByHash.clear();
}

public void primeRootToBlockHashCache(final Blockchain blockchain, final int numEntries) {
// prime the stateroot-to-blockhash cache
long head = blockchain.getChainHeadHeader().getNumber();
for (long i = head; i > Math.max(0, head - numEntries); i--) {
blockchain
.getBlockHeader(i)
.ifPresent(header -> stateRootToBlockHeaderCache.put(header.getStateRoot(), header));
}
}

/**
* Returns the worldstate for the supplied root hash, or the head worldstate if no root hash is
* supplied. synchronized to prevent concurrent adds to the cache of the same root hash.
*
* @param rootHash optional rootHash to supply worldstate storage for
* @return Optional worldstate storage
*/
public synchronized Optional<BonsaiWorldStateKeyValueStorage> getStorageByRootHash(
final Optional<Hash> rootHash) {
if (rootHash.isPresent()) {
// if we supplied a hash, return the worldstate for that hash if it is available:
return rootHash
.map(stateRootToBlockHeaderCache::getIfPresent)
.flatMap(
header ->
Optional.ofNullable(cachedWorldStatesByHash.get(header.getHash()))
.map(CachedBonsaiWorldView::getWorldStateStorage)
.or(
() -> {
// if not cached already, maybe fetch and cache this worldstate
var maybeWorldState =
archive.getMutable(header, false).map(BonsaiWorldState.class::cast);
maybeWorldState.ifPresent(
ws -> addCachedLayer(header, header.getStateRoot(), ws));
return maybeWorldState.map(BonsaiWorldState::getWorldStateStorage);
}));
} else {
// if we did not supply a hash, return the head worldstate from cachedWorldStates
return rootWorldStateStorage
.getWorldStateBlockHash()
.map(cachedWorldStatesByHash::get)
.map(CachedBonsaiWorldView::getWorldStateStorage);
}
}

@Override
public void onClearStorage() {
this.cachedWorldStatesByHash.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public synchronized void addCachedLayer(
}

@Override
public boolean containWorldStateStorage(final Hash blockHash) {
public boolean contains(final Hash blockHash) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import kotlin.Pair;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
Expand Down Expand Up @@ -157,9 +157,7 @@ public Optional<Bytes> getAccountStorageTrieNode(

@Override
public Optional<Bytes> getTrieNodeUnsafe(final Bytes key) {
return composedWorldStateStorage
.get(TRIE_BRANCH_STORAGE, Bytes.concatenate(key).toArrayUnsafe())
.map(Bytes::wrap);
return composedWorldStateStorage.get(TRIE_BRANCH_STORAGE, key.toArrayUnsafe()).map(Bytes::wrap);
}

public Optional<byte[]> getTrieLog(final Hash blockHash) {
Expand Down Expand Up @@ -217,22 +215,42 @@ public Optional<Bytes> getStorageValueByStorageSlotKey(
}

@Override
public Map<Bytes32, Bytes> streamFlatAccounts(
public NavigableMap<Bytes32, Bytes> streamFlatAccounts(
final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) {
return flatDbStrategyProvider
.getFlatDbStrategy(composedWorldStateStorage)
.streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, max);
}

@Override
public Map<Bytes32, Bytes> streamFlatStorages(
public NavigableMap<Bytes32, Bytes> streamFlatAccounts(
final Bytes startKeyHash,
final Bytes32 endKeyHash,
final Predicate<Pair<Bytes32, Bytes>> takeWhile) {
return getFlatDbStrategy()
.streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, takeWhile);
}

@Override
public NavigableMap<Bytes32, Bytes> streamFlatStorages(
final Hash accountHash, final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) {
return flatDbStrategyProvider
.getFlatDbStrategy(composedWorldStateStorage)
.streamStorageFlatDatabase(
composedWorldStateStorage, accountHash, startKeyHash, endKeyHash, max);
}

@Override
public NavigableMap<Bytes32, Bytes> streamFlatStorages(
final Hash accountHash,
final Bytes startKeyHash,
final Bytes32 endKeyHash,
final Predicate<Pair<Bytes32, Bytes>> takeWhile) {
return getFlatDbStrategy()
.streamStorageFlatDatabase(
composedWorldStateStorage, accountHash, startKeyHash, endKeyHash, takeWhile);
}

public NavigableMap<Bytes32, AccountStorageEntry> storageEntriesFrom(
final Hash addressHash, final Bytes32 startKeyHash, final int limit) {
throw new RuntimeException("Bonsai Tries does not currently support enumerating storage");
Expand Down
Loading
Loading