Preload state trie node (#4737)

The idea behind this commit is to preload asynchronously account nodes and storage nodes from the database during the transaction processing to use these nodes during the calculate root hash step.
We've created two caches, one for account nodes and one for storage nodes. The size of these caches is 100k for accounts and 200k for storage. We've tested other values but this configuration is the one that works better.
We also use exporter cache metrics as Prometheus metrics to check cache efficiency.

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>

Co-authored-by: Ameziane H <ameziane.hamlat@consensys.net>
pull/4761/head
matkt 2 years ago committed by GitHub
parent 2c5d7728ca
commit fae615fcb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 12
      besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java
  3. 3
      ethereum/core/build.gradle
  4. 17
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiInMemoryWorldState.java
  5. 27
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiPersistedWorldState.java
  6. 31
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchive.java
  7. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorage.java
  8. 118
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java
  9. 151
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoader.java
  10. 6
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java
  11. 62
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TrieGenerator.java
  12. 7
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java
  13. 3
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java
  14. 15
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotWorldStateArchiveTest.java
  15. 22
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java
  16. 4
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java
  17. 171
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java
  18. 10
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java
  19. 6
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java
  20. 10
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateRangeProofProviderTest.java
  21. 4
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManagerTest.java
  22. 4
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrieTest.java
  23. 3
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/TaskGenerator.java
  24. 13
      gradle/verification-metadata.xml
  25. 1
      gradle/versions.gradle

@ -23,6 +23,7 @@
- Print an overview of configuration and system information at startup [#4451](https://github.com/hyperledger/besu/pull/4451)
- Do not send new payloads to backward sync if initial sync is in progress [#4720](https://github.com/hyperledger/besu/issues/4720)
- Improve the way transaction fee cap validation is done on London fee market to not depend on transient network conditions [#4598](https://github.com/hyperledger/besu/pull/4598)
- Preload and cache account and storage data from RocksDB to improve performance [#4737](https://github.com/hyperledger/besu/issues/4737)
### Bug Fixes
- Restore updating chain head and finalized block during backward sync [#4718](https://github.com/hyperledger/besu/pull/4718)

@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.BlockchainStorage;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
@ -300,8 +301,10 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
reorgLoggingThreshold,
dataDirectory.toString());
final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(metricsSystem);
final WorldStateArchive worldStateArchive =
createWorldStateArchive(worldStateStorage, blockchain);
createWorldStateArchive(worldStateStorage, blockchain, cachedMerkleTrieLoader);
if (blockchain.getChainHeadBlockNumber() < 1) {
genesisState.writeStateTo(worldStateArchive.getMutable());
@ -628,14 +631,17 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides
}
private WorldStateArchive createWorldStateArchive(
final WorldStateStorage worldStateStorage, final Blockchain blockchain) {
final WorldStateStorage worldStateStorage,
final Blockchain blockchain,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
switch (dataStorageConfiguration.getDataStorageFormat()) {
case BONSAI:
return new BonsaiWorldStateArchive(
(BonsaiWorldStateKeyValueStorage) worldStateStorage,
blockchain,
Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()),
dataStorageConfiguration.useBonsaiSnapshots());
dataStorageConfiguration.useBonsaiSnapshots(),
cachedMerkleTrieLoader);
case FOREST:
default:

@ -43,6 +43,7 @@ dependencies {
implementation project(':plugin-api')
implementation project(':services:kvstore')
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.google.guava:guava'
implementation 'io.opentelemetry:opentelemetry-api'
@ -56,6 +57,8 @@ dependencies {
implementation 'org.hyperledger.besu:bls12-381'
implementation 'org.immutables:value-annotations'
implementation 'io.prometheus:simpleclient_guava'
implementation 'org.xerial.snappy:snappy-java'
annotationProcessor 'org.immutables:value'

@ -61,12 +61,13 @@ public class BonsaiInMemoryWorldState extends BonsaiPersistedWorldState {
updateAccountStorage(worldStateUpdater, addressMapEntry);
});
// for manicured tries and composting, trim and compost here
// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
this::getAccountStateTrieNode,
(location, hash) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStateTrieNode(worldStateStorage, location, hash),
worldStateRootHash,
Function.identity(),
Function.identity());
@ -96,7 +97,8 @@ public class BonsaiInMemoryWorldState extends BonsaiPersistedWorldState {
private void updateAccountStorage(
final BonsaiWorldStateUpdater worldStateUpdater,
final Map.Entry<Address, Map<Hash, BonsaiValue<UInt256>>> storageAccountUpdate) {
final Map.Entry<Address, BonsaiWorldStateUpdater.StorageConsumingMap<BonsaiValue<UInt256>>>
storageAccountUpdate) {
final Address updatedAddress = storageAccountUpdate.getKey();
final Hash updatedAddressHash = Hash.hash(updatedAddress);
if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) {
@ -105,9 +107,14 @@ public class BonsaiInMemoryWorldState extends BonsaiPersistedWorldState {
final BonsaiAccount accountOriginal = accountValue.getPrior();
final Hash storageRoot =
(accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot();
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(updatedAddressHash, location, key),
(location, key) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStorageTrieNode(
worldStateStorage, updatedAddressHash, location, key),
storageRoot,
Function.identity(),
Function.identity());

@ -66,7 +66,17 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld
Bytes32.wrap(worldStateStorage.getWorldStateRootHash().orElse(Hash.EMPTY_TRIE_HASH)));
worldStateBlockHash =
Hash.wrap(Bytes32.wrap(worldStateStorage.getWorldStateBlockHash().orElse(Hash.ZERO)));
updater = new BonsaiWorldStateUpdater(this);
updater =
new BonsaiWorldStateUpdater(
this,
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadAccount(worldStateStorage, worldStateRootHash, addr),
(addr, value) ->
archive
.getCachedMerkleTrieLoader()
.preLoadStorageSlot(worldStateStorage, addr, value));
}
public BonsaiWorldStateArchive getArchive() {
@ -116,7 +126,10 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld
// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
this::getAccountStateTrieNode,
(location, hash) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStateTrieNode(worldStateStorage, location, hash),
worldStateRootHash,
Function.identity(),
Function.identity());
@ -174,8 +187,8 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld
private void updateAccountStorageState(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Map.Entry<Address, Map<Hash, BonsaiValue<UInt256>>> storageAccountUpdate :
worldStateUpdater.getStorageToUpdate().entrySet()) {
for (final Map.Entry<Address, BonsaiWorldStateUpdater.StorageConsumingMap<BonsaiValue<UInt256>>>
storageAccountUpdate : worldStateUpdater.getStorageToUpdate().entrySet()) {
final Address updatedAddress = storageAccountUpdate.getKey();
final Hash updatedAddressHash = Hash.hash(updatedAddress);
if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) {
@ -186,7 +199,11 @@ public class BonsaiPersistedWorldState implements MutableWorldState, BonsaiWorld
(accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot();
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(updatedAddressHash, location, key),
(location, key) ->
archive
.getCachedMerkleTrieLoader()
.getAccountStorageTrieNode(
worldStateStorage, updatedAddressHash, location, key),
storageRoot,
Function.identity(),
Function.identity());

@ -54,34 +54,44 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
private final TrieLogManager trieLogManager;
private final BonsaiPersistedWorldState persistedState;
private final BonsaiWorldStateKeyValueStorage worldStateStorage;
private final CachedMerkleTrieLoader cachedMerkleTrieLoader;
private final boolean useSnapshots;
public BonsaiWorldStateArchive(final StorageProvider provider, final Blockchain blockchain) {
public BonsaiWorldStateArchive(
final StorageProvider provider,
final Blockchain blockchain,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
this(
(BonsaiWorldStateKeyValueStorage)
provider.createWorldStateStorage(DataStorageFormat.BONSAI),
blockchain,
Optional.empty(),
provider.isWorldStateSnappable());
provider.isWorldStateSnappable(),
cachedMerkleTrieLoader);
}
public BonsaiWorldStateArchive(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final Optional<Long> maxLayersToLoad) {
final Optional<Long> maxLayersToLoad,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
// overload while snapshots are an experimental option:
this(
worldStateStorage,
blockchain,
maxLayersToLoad,
DataStorageConfiguration.DEFAULT_BONSAI_USE_SNAPSHOTS);
DataStorageConfiguration.DEFAULT_BONSAI_USE_SNAPSHOTS,
cachedMerkleTrieLoader);
}
public BonsaiWorldStateArchive(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final Optional<Long> maxLayersToLoad,
final boolean useSnapshots) {
final boolean useSnapshots,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
this(
useSnapshots
? new SnapshotTrieLogManager(
@ -90,7 +100,8 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS)),
worldStateStorage,
blockchain,
useSnapshots);
useSnapshots,
cachedMerkleTrieLoader);
}
@VisibleForTesting
@ -98,12 +109,14 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
final TrieLogManager trieLogManager,
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final boolean useSnapshots) {
final boolean useSnapshots,
final CachedMerkleTrieLoader cachedMerkleTrieLoader) {
this.trieLogManager = trieLogManager;
this.blockchain = blockchain;
this.worldStateStorage = worldStateStorage;
this.persistedState = new BonsaiPersistedWorldState(this, worldStateStorage);
this.useSnapshots = useSnapshots;
this.cachedMerkleTrieLoader = cachedMerkleTrieLoader;
blockchain.observeBlockAdded(this::blockAddedHandler);
}
@ -278,6 +291,10 @@ public class BonsaiWorldStateArchive implements WorldStateArchive {
return (BonsaiWorldStateUpdater) mutableState.updater();
}
public CachedMerkleTrieLoader getCachedMerkleTrieLoader() {
return cachedMerkleTrieLoader;
}
@Override
public MutableWorldState getMutable() {
return persistedState;

@ -183,7 +183,6 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
.get(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe())
.map(Bytes::wrap);
if (response.isEmpty()) {
// after a snapsync/fastsync we only have the trie branches.
final Optional<Bytes> account = getAccount(accountHash);
final Optional<Bytes> worldStateRootHash = getWorldStateRootHash();
if (account.isPresent() && worldStateRootHash.isPresent()) {

@ -29,7 +29,6 @@ import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
@ -39,30 +38,44 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.collect.ForwardingMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.jetbrains.annotations.NotNull;
public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldView, BonsaiAccount>
implements BonsaiWorldView {
private final Map<Address, BonsaiValue<BonsaiAccount>> accountsToUpdate =
new ConcurrentHashMap<>();
private final AccountConsumingMap<BonsaiValue<BonsaiAccount>> accountsToUpdate;
private final Consumer<BonsaiValue<BonsaiAccount>> accountPreloader;
private final Consumer<Hash> storagePreloader;
private final Map<Address, BonsaiValue<Bytes>> codeToUpdate = new ConcurrentHashMap<>();
private final Set<Address> storageToClear = Collections.synchronizedSet(new HashSet<>());
// storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to
// enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the
// alternative was to keep a giant pre-image cache of the entire trie.
private final Map<Address, Map<Hash, BonsaiValue<UInt256>>> storageToUpdate =
private final Map<Address, StorageConsumingMap<BonsaiValue<UInt256>>> storageToUpdate =
new ConcurrentHashMap<>();
BonsaiWorldStateUpdater(final BonsaiWorldView world) {
this(world, (__, ___) -> {}, (__, ___) -> {});
}
BonsaiWorldStateUpdater(
final BonsaiWorldView world,
final Consumer<BonsaiValue<BonsaiAccount>> accountPreloader,
final Consumer<Hash> storagePreloader) {
super(world);
this.accountsToUpdate = new AccountConsumingMap<>(new ConcurrentHashMap<>(), accountPreloader);
this.accountPreloader = accountPreloader;
this.storagePreloader = storagePreloader;
}
public BonsaiWorldStateUpdater copy() {
final BonsaiWorldStateUpdater copy = new BonsaiWorldStateUpdater(wrappedWorldView());
final BonsaiWorldStateUpdater copy =
new BonsaiWorldStateUpdater(wrappedWorldView(), accountPreloader, storagePreloader);
copy.cloneFromUpdater(this);
return copy;
}
@ -127,7 +140,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
return storageToClear;
}
Map<Address, Map<Hash, BonsaiValue<UInt256>>> getStorageToUpdate() {
Map<Address, StorageConsumingMap<BonsaiValue<UInt256>>> getStorageToUpdate() {
return storageToUpdate;
}
@ -184,7 +197,11 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
// mark all updated storage as to be cleared
final Map<Hash, BonsaiValue<UInt256>> deletedStorageUpdates =
storageToUpdate.computeIfAbsent(deletedAddress, k -> new ConcurrentHashMap<>());
storageToUpdate.computeIfAbsent(
deletedAddress,
k ->
new StorageConsumingMap<>(
deletedAddress, new ConcurrentHashMap<>(), storagePreloader));
final Iterator<Map.Entry<Hash, BonsaiValue<UInt256>>> iter =
deletedStorageUpdates.entrySet().iterator();
while (iter.hasNext()) {
@ -256,8 +273,12 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
pendingCode.setUpdated(updatedAccount.getCode());
}
final Map<Hash, BonsaiValue<UInt256>> pendingStorageUpdates =
storageToUpdate.computeIfAbsent(updatedAddress, __ -> new ConcurrentHashMap<>());
final StorageConsumingMap<BonsaiValue<UInt256>> pendingStorageUpdates =
storageToUpdate.computeIfAbsent(
updatedAddress,
__ ->
new StorageConsumingMap<>(
updatedAddress, new ConcurrentHashMap<>(), storagePreloader));
if (tracked.getStorageWasCleared()) {
storageToClear.add(updatedAddress);
pendingStorageUpdates.clear();
@ -333,7 +354,11 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
valueUInt.ifPresent(
v ->
storageToUpdate
.computeIfAbsent(address, key -> new HashMap<>())
.computeIfAbsent(
address,
key ->
new StorageConsumingMap<>(
address, new ConcurrentHashMap<>(), storagePreloader))
.put(slotHash, new BonsaiValue<>(v, v)));
return valueUInt;
}
@ -412,7 +437,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
blockHash);
}
for (final Map.Entry<Address, Map<Hash, BonsaiValue<UInt256>>> updatesStorage :
for (final Map.Entry<Address, StorageConsumingMap<BonsaiValue<UInt256>>> updatesStorage :
storageToUpdate.entrySet()) {
final Address address = updatesStorage.getKey();
for (final Map.Entry<Hash, BonsaiValue<UInt256>> slotUpdate :
@ -588,7 +613,8 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
private Map<Hash, BonsaiValue<UInt256>> maybeCreateStorageMap(
final Map<Hash, BonsaiValue<UInt256>> storageMap, final Address address) {
if (storageMap == null) {
final Map<Hash, BonsaiValue<UInt256>> newMap = new HashMap<>();
final StorageConsumingMap<BonsaiValue<UInt256>> newMap =
new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader);
storageToUpdate.put(address, newMap);
return newMap;
} else {
@ -617,7 +643,10 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
if (storageValue.isPresent()) {
slotValue = new BonsaiValue<>(storageValue.get(), storageValue.get());
storageToUpdate
.computeIfAbsent(address, k -> new ConcurrentHashMap<>())
.computeIfAbsent(
address,
k ->
new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader))
.put(slotHash, slotValue);
}
}
@ -687,4 +716,67 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
&& storageToClear.isEmpty()
&& codeToUpdate.isEmpty());
}
public static class AccountConsumingMap<T> extends ForwardingMap<Address, T> {
private final ConcurrentHashMap<Address, T> accounts;
private final Consumer<T> consumer;
public AccountConsumingMap(
final ConcurrentHashMap<Address, T> accounts, final Consumer<T> consumer) {
this.accounts = accounts;
this.consumer = consumer;
}
@Override
public T put(@NotNull final Address address, @NotNull final T value) {
consumer.process(address, value);
return accounts.put(address, value);
}
public Consumer<T> getConsumer() {
return consumer;
}
@Override
protected Map<Address, T> delegate() {
return accounts;
}
}
public static class StorageConsumingMap<T> extends ForwardingMap<Hash, T> {
private final Address address;
private final ConcurrentHashMap<Hash, T> storages;
private final Consumer<Hash> consumer;
public StorageConsumingMap(
final Address address,
final ConcurrentHashMap<Hash, T> storages,
final Consumer<Hash> consumer) {
this.address = address;
this.storages = storages;
this.consumer = consumer;
}
@Override
public T put(@NotNull final Hash slotHash, @NotNull final T value) {
consumer.process(address, slotHash);
return storages.put(slotHash, value);
}
public Consumer<Hash> getConsumer() {
return consumer;
}
@Override
protected Map<Hash, T> delegate() {
return storages;
}
}
public interface Consumer<T> {
void process(final Address address, T value);
}
}

@ -0,0 +1,151 @@
/*
* Copyright Hyperledger Besu contributors.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.MerkleTrieException;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.prometheus.client.guava.cache.CacheMetricsCollector;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class CachedMerkleTrieLoader {
private static final int ACCOUNT_CACHE_SIZE = 100_000;
private static final int STORAGE_CACHE_SIZE = 200_000;
private final Cache<Bytes, Bytes> accountNodes =
CacheBuilder.newBuilder().recordStats().maximumSize(ACCOUNT_CACHE_SIZE).build();
private final Cache<Bytes, Bytes> storageNodes =
CacheBuilder.newBuilder().recordStats().maximumSize(STORAGE_CACHE_SIZE).build();
public CachedMerkleTrieLoader(final ObservableMetricsSystem metricsSystem) {
CacheMetricsCollector cacheMetrics = new CacheMetricsCollector();
cacheMetrics.addCache("accountsNodes", accountNodes);
cacheMetrics.addCache("storageNodes", storageNodes);
if (metricsSystem instanceof PrometheusMetricsSystem)
((PrometheusMetricsSystem) metricsSystem)
.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics);
}
public void preLoadAccount(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Hash worldStateRootHash,
final Address account) {
CompletableFuture.runAsync(
() -> cacheAccountNodes(worldStateStorage, worldStateRootHash, account));
}
@VisibleForTesting
public void cacheAccountNodes(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Hash worldStateRootHash,
final Address account) {
try {
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) -> {
Optional<Bytes> node = worldStateStorage.getAccountStateTrieNode(location, hash);
node.ifPresent(bytes -> accountNodes.put(Hash.hash(bytes), bytes));
return node;
},
worldStateRootHash,
Function.identity(),
Function.identity());
accountTrie.get(Hash.hash(account));
} catch (MerkleTrieException e) {
// ignore exception for the cache
}
}
public void preLoadStorageSlot(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Address account,
final Hash slotHash) {
CompletableFuture.runAsync(() -> cacheStorageNodes(worldStateStorage, account, slotHash));
}
@VisibleForTesting
public void cacheStorageNodes(
final BonsaiWorldStateKeyValueStorage worldStateStorage,
final Address account,
final Hash slotHash) {
final Hash accountHash = Hash.hash(account);
worldStateStorage
.getStateTrieNode(Bytes.concatenate(accountHash, Bytes.EMPTY))
.ifPresent(
storageRoot -> {
try {
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) -> {
Optional<Bytes> node =
worldStateStorage.getAccountStorageTrieNode(
accountHash, location, hash);
node.ifPresent(bytes -> storageNodes.put(Hash.hash(bytes), bytes));
return node;
},
Hash.hash(storageRoot),
Function.identity(),
Function.identity());
storageTrie.get(slotHash);
} catch (MerkleTrieException e) {
// ignore exception for the cache
}
});
}
public Optional<Bytes> getAccountStateTrieNode(
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage,
final Bytes location,
final Bytes32 nodeHash) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE);
} else {
return Optional.ofNullable(accountNodes.getIfPresent(nodeHash))
.or(() -> worldStateKeyValueStorage.getAccountStateTrieNode(location, nodeHash));
}
}
public Optional<Bytes> getAccountStorageTrieNode(
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage,
final Hash accountHash,
final Bytes location,
final Bytes32 nodeHash) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
return Optional.of(MerklePatriciaTrie.EMPTY_TRIE_NODE);
} else {
return Optional.ofNullable(storageNodes.getIfPresent(nodeHash))
.or(
() ->
worldStateKeyValueStorage.getAccountStorageTrieNode(
accountHash, location, nodeHash));
}
}
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
@ -66,7 +67,10 @@ public class InMemoryKeyValueStorageProvider extends KeyValueStorageProvider {
final Blockchain blockchain) {
final InMemoryKeyValueStorageProvider inMemoryKeyValueStorageProvider =
new InMemoryKeyValueStorageProvider();
return new BonsaiWorldStateArchive(inMemoryKeyValueStorageProvider, blockchain);
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
return new BonsaiWorldStateArchive(
inMemoryKeyValueStorageProvider, blockchain, cachedMerkleTrieLoader);
}
public static MutableWorldState createInMemoryWorldState() {

@ -16,14 +16,16 @@ package org.hyperledger.besu.ethereum.core;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@ -31,31 +33,43 @@ import org.apache.tuweni.units.bigints.UInt256;
public class TrieGenerator {
public static MerklePatriciaTrie<Bytes32, Bytes> generateTrie(
public static MerklePatriciaTrie<Bytes, Bytes> generateTrie(
final WorldStateStorage worldStateStorage, final int nbAccounts) {
final List<Hash> accountHash = new ArrayList<>();
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
return generateTrie(
worldStateStorage,
IntStream.range(0, nbAccounts)
.mapToObj(operand -> Hash.wrap(Bytes32.leftPad(Bytes.of(operand + 1))))
.collect(Collectors.toList()));
}
public static MerklePatriciaTrie<Bytes, Bytes> generateTrie(
final WorldStateStorage worldStateStorage, final List<Hash> accounts) {
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
emptyAccountStateTrie(worldStateStorage);
// Add some storage values
for (int i = 0; i < nbAccounts; i++) {
for (int i = 0; i < accounts.size(); i++) {
final WorldStateStorage.Updater updater = worldStateStorage.updater();
accountHash.add(Hash.wrap(Bytes32.leftPad(Bytes.of(i + 1))));
final MerklePatriciaTrie<Bytes32, Bytes> storageTrie =
emptyStorageTrie(worldStateStorage, accountHash.get(i));
writeStorageValue(storageTrie, UInt256.ONE, UInt256.valueOf(2L));
writeStorageValue(storageTrie, UInt256.valueOf(2L), UInt256.valueOf(4L));
writeStorageValue(storageTrie, UInt256.valueOf(3L), UInt256.valueOf(6L));
final MerklePatriciaTrie<Bytes, Bytes> storageTrie =
emptyStorageTrie(worldStateStorage, accounts.get(i));
writeStorageValue(updater, storageTrie, accounts.get(i), UInt256.ONE, UInt256.valueOf(2L));
writeStorageValue(
updater, storageTrie, accounts.get(i), UInt256.valueOf(2L), UInt256.valueOf(4L));
writeStorageValue(
updater, storageTrie, accounts.get(i), UInt256.valueOf(3L), UInt256.valueOf(6L));
int accountIndex = i;
storageTrie.commit(
(location, hash, value) ->
updater.putAccountStorageTrieNode(
accountHash.get(accountIndex), location, hash, value));
updater.putAccountStorageTrieNode(accounts.get(accountIndex), location, hash, value));
final Bytes code = Bytes32.leftPad(Bytes.of(i + 10));
final Hash codeHash = Hash.hash(code);
final StateTrieAccountValue accountValue =
new StateTrieAccountValue(1L, Wei.of(2L), Hash.wrap(storageTrie.getRootHash()), codeHash);
accountStateTrie.put(accountHash.get(i), RLP.encode(accountValue::writeTo));
accountStateTrie.put(accounts.get(i), RLP.encode(accountValue::writeTo));
if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) {
((BonsaiWorldStateKeyValueStorage.Updater) updater)
.putAccountInfoState(accounts.get(i), RLP.encode(accountValue::writeTo));
updater.putCode(accounts.get(i), code);
}
accountStateTrie.commit(updater::putAccountStateTrieNode);
updater.putCode(codeHash, code);
// Persist updates
@ -65,13 +79,21 @@ public class TrieGenerator {
}
private static void writeStorageValue(
final MerklePatriciaTrie<Bytes32, Bytes> storageTrie,
final WorldStateStorage.Updater updater,
final MerklePatriciaTrie<Bytes, Bytes> storageTrie,
final Hash hash,
final UInt256 key,
final UInt256 value) {
storageTrie.put(storageKeyHash(key), encodeStorageValue(value));
final Hash keyHash = storageKeyHash(key);
final Bytes encodedValue = encodeStorageValue(value);
storageTrie.put(keyHash, encodeStorageValue(value));
if (updater instanceof BonsaiWorldStateKeyValueStorage.Updater) {
((BonsaiWorldStateKeyValueStorage.Updater) updater)
.putStorageValueBySlotHash(hash, keyHash, encodedValue);
}
}
private static Bytes32 storageKeyHash(final UInt256 storageKey) {
private static Hash storageKeyHash(final UInt256 storageKey) {
return Hash.hash(storageKey);
}
@ -79,7 +101,7 @@ public class TrieGenerator {
return RLP.encode(out -> out.writeBytes(storageValue.toMinimalBytes()));
}
public static MerklePatriciaTrie<Bytes32, Bytes> emptyStorageTrie(
public static MerklePatriciaTrie<Bytes, Bytes> emptyStorageTrie(
final WorldStateStorage worldStateStorage, final Hash accountHash) {
return new StoredMerklePatriciaTrie<>(
(location, hash) ->
@ -88,7 +110,7 @@ public class TrieGenerator {
b -> b);
}
public static MerklePatriciaTrie<Bytes32, Bytes> emptyAccountStateTrie(
public static MerklePatriciaTrie<Bytes, Bytes> emptyAccountStateTrie(
final WorldStateStorage worldStateStorage) {
return new StoredMerklePatriciaTrie<>(
worldStateStorage::getAccountStateTrieNode, b -> b, b -> b);

@ -26,6 +26,7 @@ import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.bonsai.BonsaiPersistedWorldState;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateArchive;
import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.CachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.chain.BadBlockManager;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
@ -44,6 +45,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.exception.StorageException;
import java.util.Optional;
@ -75,10 +77,12 @@ public class BlockImportExceptionHandlingTest {
private final WorldStateStorage worldStateStorage =
new BonsaiWorldStateKeyValueStorage(storageProvider);
private CachedMerkleTrieLoader cachedMerkleTrieLoader;
private final WorldStateArchive worldStateArchive =
// contains a BonsaiPersistedWorldState which we need to spy on.
// do we need to also test with a DefaultWorldStateArchive?
spy(new BonsaiWorldStateArchive(storageProvider, blockchain));
spy(new BonsaiWorldStateArchive(storageProvider, blockchain, cachedMerkleTrieLoader));
private final BonsaiPersistedWorldState persisted =
spy(
@ -98,6 +102,7 @@ public class BlockImportExceptionHandlingTest {
mainnetBlockValidator =
new MainnetBlockValidator(
blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager);
cachedMerkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem());
}
@Test

@ -114,7 +114,8 @@ public abstract class AbstractIsolationTests {
createKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.BONSAI),
blockchain,
Optional.of(16L),
shouldUseSnapshots());
shouldUseSnapshots(),
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
var ws = archive.getMutable();
genesisState.writeStateTo(ws);
ws.persist(blockchain.getChainHeadHeader());

@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
@ -62,10 +63,13 @@ public class BonsaiSnapshotWorldStateArchiveTest {
BonsaiWorldStateArchive bonsaiWorldStateArchive;
CachedMerkleTrieLoader cachedMerkleTrieLoader;
@Before
public void setUp() {
when(storageProvider.getStorageBySegmentIdentifier(any(KeyValueSegmentIdentifier.class)))
.thenReturn(keyValueStorage);
cachedMerkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem());
}
@Test
@ -85,7 +89,8 @@ public class BonsaiSnapshotWorldStateArchiveTest {
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(1L),
true);
true,
cachedMerkleTrieLoader);
assertThat(bonsaiWorldStateArchive.getMutable(null, chainHead.getHash(), true))
.containsInstanceOf(BonsaiPersistedWorldState.class);
@ -95,7 +100,10 @@ public class BonsaiSnapshotWorldStateArchiveTest {
public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() {
bonsaiWorldStateArchive =
new BonsaiWorldStateArchive(
new BonsaiWorldStateKeyValueStorage(storageProvider), blockchain, Optional.of(512L));
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
cachedMerkleTrieLoader);
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(512).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
@ -136,7 +144,8 @@ public class BonsaiSnapshotWorldStateArchiveTest {
new SnapshotTrieLogManager(blockchain, worldStateStorage, 12L, worldStatesByHash),
worldStateStorage,
blockchain,
true));
true,
cachedMerkleTrieLoader));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);

@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
@ -86,7 +87,8 @@ public class BonsaiWorldStateArchiveTest {
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(1L),
false);
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
assertThat(bonsaiWorldStateArchive.getMutable(null, chainHead.getHash(), true))
.containsInstanceOf(BonsaiPersistedWorldState.class);
@ -99,7 +101,8 @@ public class BonsaiWorldStateArchiveTest {
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
false);
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(512).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
@ -114,7 +117,8 @@ public class BonsaiWorldStateArchiveTest {
new BonsaiWorldStateKeyValueStorage(storageProvider),
blockchain,
Optional.of(512L),
false);
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem()));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
final BlockHeader chainHead = blockBuilder.number(511).buildHeader();
@ -146,7 +150,8 @@ public class BonsaiWorldStateArchiveTest {
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
worldStateStorage,
blockchain,
false));
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
final BlockHeader blockHeader = blockBuilder.number(0).buildHeader();
when(blockchain.getBlockHeader(eq(blockHeader.getHash()))).thenReturn(Optional.of(blockHeader));
@ -173,7 +178,8 @@ public class BonsaiWorldStateArchiveTest {
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
worldStateStorage,
blockchain,
false));
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
@ -220,7 +226,8 @@ public class BonsaiWorldStateArchiveTest {
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
worldStateStorage,
blockchain,
false));
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);
@ -271,7 +278,8 @@ public class BonsaiWorldStateArchiveTest {
blockchain, worldStateStorage, 12L, layeredWorldStatesByHash),
worldStateStorage,
blockchain,
false));
false,
new CachedMerkleTrieLoader(new NoOpMetricsSystem())));
var worldState = (BonsaiPersistedWorldState) bonsaiWorldStateArchive.getMutable();
var updater = spy(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState));
when(bonsaiWorldStateArchive.getUpdaterFromPersistedState(worldState)).thenReturn(updater);

@ -168,7 +168,7 @@ public class BonsaiWorldStateKeyValueStorageTest {
@Test
public void getAccount_loadFromTrieWhenEmpty() {
final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage());
MerklePatriciaTrie<Bytes32, Bytes> trie = TrieGenerator.generateTrie(storage, 1);
MerklePatriciaTrie<Bytes, Bytes> trie = TrieGenerator.generateTrie(storage, 1);
final TreeMap<Bytes32, Bytes> accounts =
(TreeMap<Bytes32, Bytes>)
trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1));
@ -192,7 +192,7 @@ public class BonsaiWorldStateKeyValueStorageTest {
@Test
public void getStorage_loadFromTrieWhenEmpty() {
final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage());
final MerklePatriciaTrie<Bytes32, Bytes> trie = TrieGenerator.generateTrie(storage, 1);
final MerklePatriciaTrie<Bytes, Bytes> trie = TrieGenerator.generateTrie(storage, 1);
final TreeMap<Bytes32, Bytes> accounts =
(TreeMap<Bytes32, Bytes>)
trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1));

@ -0,0 +1,171 @@
/*
* Copyright Hyperledger Besu contributors.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.TrieGenerator;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.StoredMerklePatriciaTrie;
import org.hyperledger.besu.ethereum.trie.TrieIterator;
import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class CachedMerkleTrieLoaderTest {
private CachedMerkleTrieLoader merkleTrieLoader;
private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider();
private final BonsaiWorldStateKeyValueStorage inMemoryWorldState =
Mockito.spy(new BonsaiWorldStateKeyValueStorage(storageProvider));
final List<Address> accounts =
List.of(Address.fromHexString("0xdeadbeef"), Address.fromHexString("0xdeadbeee"));
private MerklePatriciaTrie<Bytes, Bytes> trie;
@Before
public void setup() {
trie =
TrieGenerator.generateTrie(
inMemoryWorldState, accounts.stream().map(Hash::hash).collect(Collectors.toList()));
merkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem());
}
@Test
public void shouldAddAccountNodesInCacheDuringPreload() {
merkleTrieLoader.cacheAccountNodes(
inMemoryWorldState, Hash.wrap(trie.getRootHash()), accounts.get(0));
final BonsaiWorldStateKeyValueStorage emptyStorage =
new BonsaiWorldStateKeyValueStorage(new InMemoryKeyValueStorageProvider());
StoredMerklePatriciaTrie<Bytes, Bytes> cachedTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
merkleTrieLoader.getAccountStateTrieNode(emptyStorage, location, hash),
trie.getRootHash(),
Function.identity(),
Function.identity());
final Hash hashAccountZero = Hash.hash(accounts.get(0));
assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero));
}
@Test
public void shouldAddStorageNodesInCacheDuringPreload() {
final Hash hashAccountZero = Hash.hash(accounts.get(0));
final StateTrieAccountValue stateTrieAccountValue =
StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow()));
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash),
stateTrieAccountValue.getStorageRoot(),
Function.identity(),
Function.identity());
final List<Bytes> originalSlots = new ArrayList<>();
storageTrie.visitLeafs(
(keyHash, node) -> {
merkleTrieLoader.cacheStorageNodes(
inMemoryWorldState, accounts.get(0), Hash.wrap(keyHash));
originalSlots.add(node.getRlp());
return TrieIterator.State.CONTINUE;
});
final List<Bytes> cachedSlots = new ArrayList<>();
final BonsaiWorldStateKeyValueStorage emptyStorage =
new BonsaiWorldStateKeyValueStorage(new InMemoryKeyValueStorageProvider());
final StoredMerklePatriciaTrie<Bytes, Bytes> cachedTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
merkleTrieLoader.getAccountStorageTrieNode(
emptyStorage, hashAccountZero, location, hash),
stateTrieAccountValue.getStorageRoot(),
Function.identity(),
Function.identity());
cachedTrie.visitLeafs(
(keyHash, node) -> {
cachedSlots.add(node.getRlp());
return TrieIterator.State.CONTINUE;
});
assertThat(originalSlots).isNotEmpty();
assertThat(originalSlots).isEqualTo(cachedSlots);
}
@Test
public void shouldFallbackWhenAccountNodesIsNotInCache() {
final StoredMerklePatriciaTrie<Bytes, Bytes> cachedTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
merkleTrieLoader.getAccountStateTrieNode(inMemoryWorldState, location, hash),
trie.getRootHash(),
Function.identity(),
Function.identity());
final Hash hashAccountZero = Hash.hash(accounts.get(0));
assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero));
}
@Test
public void shouldFallbackWhenStorageNodesIsNotInCache() {
final Hash hashAccountZero = Hash.hash(accounts.get(0));
final StateTrieAccountValue stateTrieAccountValue =
StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow()));
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash),
stateTrieAccountValue.getStorageRoot(),
Function.identity(),
Function.identity());
final List<Bytes> originalSlots = new ArrayList<>();
storageTrie.visitLeafs(
(keyHash, node) -> {
originalSlots.add(node.getRlp());
return TrieIterator.State.CONTINUE;
});
final List<Bytes> cachedSlots = new ArrayList<>();
final StoredMerklePatriciaTrie<Bytes, Bytes> cachedTrie =
new StoredMerklePatriciaTrie<>(
(location, hash) ->
merkleTrieLoader.getAccountStorageTrieNode(
inMemoryWorldState, hashAccountZero, location, hash),
stateTrieAccountValue.getStorageRoot(),
Function.identity(),
Function.identity());
cachedTrie.visitLeafs(
(keyHash, node) -> {
cachedSlots.add(node.getRlp());
return TrieIterator.State.CONTINUE;
});
assertThat(originalSlots).isNotEmpty();
assertThat(originalSlots).isEqualTo(cachedSlots);
}
}

@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.log.LogsBloomFilter;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
@ -109,7 +110,9 @@ public class LogRollingTests {
@Before
public void createStorage() {
final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider();
archive = new BonsaiWorldStateArchive(provider, blockchain);
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
archive = new BonsaiWorldStateArchive(provider, blockchain, cachedMerkleTrieLoader);
accountStorage =
(InMemoryKeyValueStorage)
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE);
@ -128,7 +131,10 @@ public class LogRollingTests {
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE);
final InMemoryKeyValueStorageProvider secondProvider = new InMemoryKeyValueStorageProvider();
secondArchive = new BonsaiWorldStateArchive(secondProvider, blockchain);
final CachedMerkleTrieLoader secondOptimizedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
secondArchive =
new BonsaiWorldStateArchive(secondProvider, blockchain, secondOptimizedMerkleTrieLoader);
secondAccountStorage =
(InMemoryKeyValueStorage)
secondProvider.getStorageBySegmentIdentifier(

@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage;
import org.hyperledger.besu.util.io.RollingFileReader;
@ -38,7 +39,10 @@ public class RollingImport {
new RollingFileReader((i, c) -> Path.of(String.format(arg[0] + "-%04d.rdat", i)), false);
final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider();
final BonsaiWorldStateArchive archive = new BonsaiWorldStateArchive(provider, null);
final CachedMerkleTrieLoader cachedMerkleTrieLoader =
new CachedMerkleTrieLoader(new NoOpMetricsSystem());
final BonsaiWorldStateArchive archive =
new BonsaiWorldStateArchive(provider, null, cachedMerkleTrieLoader);
final InMemoryKeyValueStorage accountStorage =
(InMemoryKeyValueStorage)
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE);

@ -53,7 +53,7 @@ public class WorldStateRangeProofProviderTest {
@Test
public void rangeProofValidationNominalCase() {
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
// collect accounts in range
final RangeStorageEntriesCollector collector =
@ -82,7 +82,7 @@ public class WorldStateRangeProofProviderTest {
@Test
public void rangeProofValidationMissingAccount() {
MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
// collect accounts in range
final RangeStorageEntriesCollector collector =
@ -120,7 +120,7 @@ public class WorldStateRangeProofProviderTest {
@Test
public void rangeProofValidationNoMonotonicIncreasing() {
MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
// generate the invalid proof
@ -157,7 +157,7 @@ public class WorldStateRangeProofProviderTest {
@Test
public void rangeProofValidationEmptyProof() {
MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
// generate the invalid proof
@ -185,7 +185,7 @@ public class WorldStateRangeProofProviderTest {
@Test
public void rangeProofValidationInvalidEmptyProof() {
MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
// generate the invalid proof

@ -101,7 +101,7 @@ public final class RangeManagerTest {
final WorldStateStorage worldStateStorage =
new WorldStateKeyValueStorage(new InMemoryKeyValueStorage());
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
final RangeStorageEntriesCollector collector =
@ -140,7 +140,7 @@ public final class RangeManagerTest {
final WorldStateStorage worldStateStorage =
new WorldStateKeyValueStorage(new InMemoryKeyValueStorage());
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, 15);
final RangeStorageEntriesCollector collector =

@ -47,7 +47,7 @@ public class StackTrieTest {
final WorldStateStorage recreatedWorldStateStorage =
new WorldStateKeyValueStorage(new InMemoryKeyValueStorage());
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, nbAccounts);
final StackTrie stackTrie =
@ -99,7 +99,7 @@ public class StackTrieTest {
final WorldStateStorage recreatedWorldStateStorage =
new WorldStateKeyValueStorage(new InMemoryKeyValueStorage());
final MerklePatriciaTrie<Bytes32, Bytes> accountStateTrie =
final MerklePatriciaTrie<Bytes, Bytes> accountStateTrie =
TrieGenerator.generateTrie(worldStateStorage, nbAccounts);
final StackTrie stackTrie =

@ -49,8 +49,7 @@ public class TaskGenerator {
final WorldStateProofProvider worldStateProofProvider =
new WorldStateProofProvider(worldStateStorage);
final MerklePatriciaTrie<Bytes32, Bytes> trie =
TrieGenerator.generateTrie(worldStateStorage, 1);
final MerklePatriciaTrie<Bytes, Bytes> trie = TrieGenerator.generateTrie(worldStateStorage, 1);
final RangeStorageEntriesCollector collector =
RangeStorageEntriesCollector.createCollector(
Bytes32.ZERO, RangeManager.MAX_RANGE, 1, Integer.MAX_VALUE);

@ -2322,6 +2322,11 @@
<sha256 value="e8e10f63abf4796a9db6654f92146473e75da7e2ad443066b812f85e88cfc6e7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="parent" version="0.16.0">
<artifact name="parent-0.16.0.pom">
<sha256 value="722b55119097b04d711479dfb60dd54b27b5920a1aeb7702027c44a215ffc596" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="parent" version="0.9.0">
<artifact name="parent-0.9.0.pom">
<sha256 value="d0a11b469235119373cc8653639b5c0ef151cef7ef24156bdb40e86d06cae6a5" origin="Generated by Gradle"/>
@ -2343,6 +2348,14 @@
<sha256 value="09995ffae8df4a170fc991b7c30518afb4235d7bbdf7dc2757d73978509b2a90" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_guava" version="0.16.0">
<artifact name="simpleclient_guava-0.16.0.jar">
<sha256 value="49959fe20423803b8958fe35ce6cdcc47e58e2251b191ad53eb7ef6fc46c4ae1" origin="Generated by Gradle"/>
</artifact>
<artifact name="simpleclient_guava-0.16.0.pom">
<sha256 value="1d42aa72798870f20e997ae6c4d14ec3b26b3ef715602b2a8f32404e3657bdb1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.prometheus" name="simpleclient_hotspot" version="0.9.0">
<artifact name="simpleclient_hotspot-0.9.0.jar">
<sha256 value="927fc7069f9a8ebd1a8420c3bb1675a957bd359b95e50664aa96e387d4b0a256" origin="Generated by Gradle"/>

@ -100,6 +100,7 @@ dependencyManagement {
dependency 'io.prometheus:simpleclient_common:0.9.0'
dependency 'io.prometheus:simpleclient_hotspot:0.9.0'
dependency 'io.prometheus:simpleclient_pushgateway:0.9.0'
dependency 'io.prometheus:simpleclient_guava:0.16.0'
dependency 'io.reactivex.rxjava2:rxjava:2.2.21'

Loading…
Cancel
Save