Add FlatDbStrategy (#5901)

* move FlatDbReader to FlatDbStrategy (including writes), add getNearestTo

Signed-off-by: garyschulte <garyschulte@gmail.com>
pull/5939/head
garyschulte 1 year ago committed by GitHub
parent 9d7ee2b52b
commit d81e1f3042
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java
  2. 100
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java
  3. 76
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java
  4. 8
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java
  5. 8
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java
  6. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java
  7. 5
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java
  8. 2
      plugin-api/build.gradle
  9. 31
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java
  10. 15
      plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java
  11. 14
      plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java
  12. 13
      plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java
  13. 2
      services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java
  14. 20
      services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java
  15. 27
      services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java
  16. 4
      services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java
  17. 82
      services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java
  18. 9
      services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java
  19. 33
      services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java

@ -47,7 +47,7 @@ public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKey
final ObservableMetricsSystem metricsSystem) {
super(
parentWorldStateStorage.flatDbMode,
parentWorldStateStorage.flatDbReaderStrategy,
parentWorldStateStorage.flatDbStrategy,
segmentedWorldStateStorage,
trieLogStorage,
metricsSystem);
@ -77,7 +77,8 @@ public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKey
public BonsaiUpdater updater() {
return new Updater(
((SnappedKeyValueStorage) composedWorldStateStorage).getSnapshotTransaction(),
trieLogStorage.startTransaction());
trieLogStorage.startTransaction(),
flatDbStrategy);
}
@Override

@ -21,9 +21,9 @@ import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIden
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.StorageSlotKey;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.FlatDbReaderStrategy;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.FullFlatDbReaderStrategy;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.PartialFlatDbReaderStrategy;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.FlatDbStrategy;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.FullFlatDbStrategy;
import org.hyperledger.besu.ethereum.bonsai.storage.flat.PartialFlatDbStrategy;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.ethereum.trie.MerkleTrie;
@ -67,7 +67,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
public static final byte[] FLAT_DB_MODE = "flatDbStatus".getBytes(StandardCharsets.UTF_8);
protected FlatDbMode flatDbMode;
protected FlatDbReaderStrategy flatDbReaderStrategy;
protected FlatDbStrategy flatDbStrategy;
protected final SegmentedKeyValueStorage composedWorldStateStorage;
protected final KeyValueStorage trieLogStorage;
@ -94,33 +94,49 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
public BonsaiWorldStateKeyValueStorage(
final FlatDbMode flatDbMode,
final FlatDbReaderStrategy flatDbReaderStrategy,
final FlatDbStrategy flatDbStrategy,
final SegmentedKeyValueStorage composedWorldStateStorage,
final KeyValueStorage trieLogStorage,
final ObservableMetricsSystem metricsSystem) {
this.flatDbMode = flatDbMode;
this.flatDbReaderStrategy = flatDbReaderStrategy;
this.flatDbStrategy = flatDbStrategy;
this.composedWorldStateStorage = composedWorldStateStorage;
this.trieLogStorage = trieLogStorage;
this.metricsSystem = metricsSystem;
}
public void loadFlatDbStrategy() {
this.flatDbMode =
private void loadFlatDbStrategy() {
// derive our flatdb strategy from db or default:
var newFlatDbMode = deriveFlatDbStrategy();
// if flatDbMode is not loaded or has changed, reload flatDbStrategy
if (this.flatDbMode == null || !this.flatDbMode.equals(newFlatDbMode)) {
this.flatDbMode = newFlatDbMode;
if (flatDbMode == FlatDbMode.FULL) {
this.flatDbStrategy = new FullFlatDbStrategy(metricsSystem);
} else {
this.flatDbStrategy = new PartialFlatDbStrategy(metricsSystem);
}
}
}
public FlatDbMode deriveFlatDbStrategy() {
var flatDbMode =
FlatDbMode.fromVersion(
composedWorldStateStorage
.get(TRIE_BRANCH_STORAGE, FLAT_DB_MODE)
.map(Bytes::wrap)
.orElse(
FlatDbMode.PARTIAL
.getVersion())); // for backward compatibility we use partial as
// default
.orElse(FlatDbMode.PARTIAL.getVersion()));
LOG.info("Bonsai flat db mode found {}", flatDbMode);
if (flatDbMode == FlatDbMode.FULL) {
this.flatDbReaderStrategy = new FullFlatDbReaderStrategy(metricsSystem);
} else {
this.flatDbReaderStrategy = new PartialFlatDbReaderStrategy(metricsSystem);
return flatDbMode;
}
public FlatDbStrategy getFlatDbStrategy() {
if (flatDbStrategy == null) {
loadFlatDbStrategy();
}
return flatDbStrategy;
}
@Override
@ -133,22 +149,18 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
return flatDbMode;
}
public FlatDbReaderStrategy getFlatDbReaderStrategy() {
return flatDbReaderStrategy;
}
@Override
public Optional<Bytes> getCode(final Bytes32 codeHash, final Hash accountHash) {
if (codeHash.equals(Hash.EMPTY)) {
return Optional.of(Bytes.EMPTY);
} else {
return getFlatDbReaderStrategy().getCode(codeHash, accountHash, composedWorldStateStorage);
return getFlatDbStrategy().getFlatCode(codeHash, accountHash, composedWorldStateStorage);
}
}
public Optional<Bytes> getAccount(final Hash accountHash) {
return getFlatDbReaderStrategy()
.getAccount(
return getFlatDbStrategy()
.getFlatAccount(
this::getWorldStateRootHash,
this::getAccountStateTrieNode,
accountHash,
@ -226,8 +238,8 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
final Supplier<Optional<Hash>> storageRootSupplier,
final Hash accountHash,
final StorageSlotKey storageSlotKey) {
return getFlatDbReaderStrategy()
.getStorageValueByStorageSlotKey(
return getFlatDbStrategy()
.getFlatStorageValueByStorageSlotKey(
this::getWorldStateRootHash,
storageRootSupplier,
(location, hash) -> getAccountStorageTrieNode(accountHash, location, hash),
@ -239,14 +251,14 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
@Override
public Map<Bytes32, Bytes> streamFlatAccounts(
final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) {
return getFlatDbReaderStrategy()
return getFlatDbStrategy()
.streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, max);
}
@Override
public Map<Bytes32, Bytes> streamFlatStorages(
final Hash accountHash, final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) {
return getFlatDbReaderStrategy()
return getFlatDbStrategy()
.streamStorageFlatDatabase(
composedWorldStateStorage, accountHash, startKeyHash, endKeyHash, max);
}
@ -273,6 +285,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
public void upgradeToFullFlatDbMode() {
final SegmentedKeyValueStorageTransaction transaction =
composedWorldStateStorage.startTransaction();
// TODO: consider ARCHIVE mode
transaction.put(
TRIE_BRANCH_STORAGE, FLAT_DB_MODE, FlatDbMode.FULL.getVersion().toArrayUnsafe());
transaction.commit();
@ -291,7 +304,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
@Override
public void clear() {
subscribers.forEach(BonsaiStorageSubscriber::onClearStorage);
getFlatDbReaderStrategy().clearAll(composedWorldStateStorage);
getFlatDbStrategy().clearAll(composedWorldStateStorage);
composedWorldStateStorage.clear(TRIE_BRANCH_STORAGE);
trieLogStorage.clear();
loadFlatDbStrategy(); // force reload of flat db reader strategy
@ -306,13 +319,15 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
@Override
public void clearFlatDatabase() {
subscribers.forEach(BonsaiStorageSubscriber::onClearFlatDatabaseStorage);
getFlatDbReaderStrategy().resetOnResync(composedWorldStateStorage);
getFlatDbStrategy().resetOnResync(composedWorldStateStorage);
}
@Override
public BonsaiUpdater updater() {
return new Updater(
composedWorldStateStorage.startTransaction(), trieLogStorage.startTransaction());
composedWorldStateStorage.startTransaction(),
trieLogStorage.startTransaction(),
flatDbStrategy);
}
@Override
@ -351,18 +366,21 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
private final SegmentedKeyValueStorageTransaction composedWorldStateTransaction;
private final KeyValueStorageTransaction trieLogStorageTransaction;
private final FlatDbStrategy flatDbStrategy;
public Updater(
final SegmentedKeyValueStorageTransaction composedWorldStateTransaction,
final KeyValueStorageTransaction trieLogStorageTransaction) {
final KeyValueStorageTransaction trieLogStorageTransaction,
final FlatDbStrategy flatDbStrategy) {
this.composedWorldStateTransaction = composedWorldStateTransaction;
this.trieLogStorageTransaction = trieLogStorageTransaction;
this.flatDbStrategy = flatDbStrategy;
}
@Override
public BonsaiUpdater removeCode(final Hash accountHash) {
composedWorldStateTransaction.remove(CODE_STORAGE, accountHash.toArrayUnsafe());
flatDbStrategy.removeFlatCode(composedWorldStateTransaction, accountHash);
return this;
}
@ -372,14 +390,13 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
// Don't save empty values
return this;
}
composedWorldStateTransaction.put(
CODE_STORAGE, accountHash.toArrayUnsafe(), code.toArrayUnsafe());
flatDbStrategy.putFlatCode(composedWorldStateTransaction, accountHash, codeHash, code);
return this;
}
@Override
public BonsaiUpdater removeAccountInfoState(final Hash accountHash) {
composedWorldStateTransaction.remove(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe());
flatDbStrategy.removeFlatAccount(composedWorldStateTransaction, accountHash);
return this;
}
@ -389,8 +406,7 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
// Don't save empty values
return this;
}
composedWorldStateTransaction.put(
ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe());
flatDbStrategy.putFlatAccount(composedWorldStateTransaction, accountHash, accountValue);
return this;
}
@ -441,18 +457,16 @@ public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoC
@Override
public synchronized BonsaiUpdater putStorageValueBySlotHash(
final Hash accountHash, final Hash slotHash, final Bytes storage) {
composedWorldStateTransaction.put(
ACCOUNT_STORAGE_STORAGE,
Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(),
storage.toArrayUnsafe());
flatDbStrategy.putFlatAccountStorageValueByStorageSlotHash(
composedWorldStateTransaction, accountHash, slotHash, storage);
return this;
}
@Override
public synchronized void removeStorageValueBySlotHash(
final Hash accountHash, final Hash slotHash) {
composedWorldStateTransaction.remove(
ACCOUNT_STORAGE_STORAGE, Bytes.concatenate(accountHash, slotHash).toArrayUnsafe());
flatDbStrategy.removeFlatAccountStorageValueByStorageSlotHash(
composedWorldStateTransaction, accountHash, slotHash);
}
@Override

@ -26,6 +26,7 @@ import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.plugin.services.metrics.Counter;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction;
import java.util.Map;
import java.util.Optional;
@ -40,11 +41,11 @@ import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.rlp.RLP;
/**
* This class represents a FlatDbReaderStrategy, which is responsible for reading data from flat
* databases. It implements various methods for retrieving account data, code data, and storage data
* from the corresponding KeyValueStorage.
* This class represents a FlatDbReaderStrategy, which is responsible for reading and writing data
* from flat databases. It implements various methods for storing and retrieving account data, code
* data, and storage data from the corresponding KeyValueStorage.
*/
public abstract class FlatDbReaderStrategy {
public abstract class FlatDbStrategy {
protected final MetricsSystem metricsSystem;
protected final Counter getAccountCounter;
@ -53,7 +54,7 @@ public abstract class FlatDbReaderStrategy {
protected final Counter getStorageValueCounter;
protected final Counter getStorageValueFlatDatabaseCounter;
public FlatDbReaderStrategy(final MetricsSystem metricsSystem) {
public FlatDbStrategy(final MetricsSystem metricsSystem) {
this.metricsSystem = metricsSystem;
getAccountCounter =
@ -84,7 +85,7 @@ public abstract class FlatDbReaderStrategy {
/*
* Retrieves the account data for the given account hash, using the world state root hash supplier and node loader.
*/
public abstract Optional<Bytes> getAccount(
public abstract Optional<Bytes> getFlatAccount(
Supplier<Optional<Bytes>> worldStateRootHashSupplier,
NodeLoader nodeLoader,
Hash accountHash,
@ -94,7 +95,7 @@ public abstract class FlatDbReaderStrategy {
* Retrieves the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader.
*/
public abstract Optional<Bytes> getStorageValueByStorageSlotKey(
public abstract Optional<Bytes> getFlatStorageValueByStorageSlotKey(
Supplier<Optional<Bytes>> worldStateRootHashSupplier,
Supplier<Optional<Hash>> storageRootSupplier,
NodeLoader nodeLoader,
@ -105,7 +106,7 @@ public abstract class FlatDbReaderStrategy {
/*
* Retrieves the code data for the given code hash and account hash.
*/
public Optional<Bytes> getCode(
public Optional<Bytes> getFlatCode(
final Bytes32 codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) {
if (codeHash.equals(Hash.EMPTY)) {
return Optional.of(Bytes.EMPTY);
@ -117,6 +118,65 @@ public abstract class FlatDbReaderStrategy {
}
}
/*
* Puts the account data for the given account hash, using the world state root hash supplier and node loader.
*/
public void putFlatAccount(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Bytes accountValue) {
transaction.put(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe());
}
public void removeFlatAccount(
final SegmentedKeyValueStorageTransaction transaction, final Hash accountHash) {
transaction.remove(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe());
}
/*
* Puts the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader.
*/
public void putFlatAccountStorageValueByStorageSlotHash(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Hash slotHash,
final Bytes storage) {
transaction.put(
ACCOUNT_STORAGE_STORAGE,
Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(),
storage.toArrayUnsafe());
}
/*
* Removes the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader.
*/
public void removeFlatAccountStorageValueByStorageSlotHash(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Hash slotHash) {
transaction.remove(
ACCOUNT_STORAGE_STORAGE, Bytes.concatenate(accountHash, slotHash).toArrayUnsafe());
}
/*
* Removes code for the given account hash.
*/
public void removeFlatCode(
final SegmentedKeyValueStorageTransaction transaction, final Hash accountHash) {
transaction.remove(CODE_STORAGE, accountHash.toArrayUnsafe());
}
/*
* Puts the code data for the given code hash and account hash.
*/
public void putFlatCode(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Bytes32 codeHash,
final Bytes code) {
transaction.put(CODE_STORAGE, accountHash.toArrayUnsafe(), code.toArrayUnsafe());
}
public void clearAll(final SegmentedKeyValueStorage storage) {
storage.clear(ACCOUNT_INFO_STATE);
storage.clear(ACCOUNT_STORAGE_STORAGE);

@ -31,13 +31,13 @@ import java.util.function.Supplier;
import org.apache.tuweni.bytes.Bytes;
public class FullFlatDbReaderStrategy extends FlatDbReaderStrategy {
public class FullFlatDbStrategy extends FlatDbStrategy {
protected final Counter getAccountNotFoundInFlatDatabaseCounter;
protected final Counter getStorageValueNotFoundInFlatDatabaseCounter;
public FullFlatDbReaderStrategy(final MetricsSystem metricsSystem) {
public FullFlatDbStrategy(final MetricsSystem metricsSystem) {
super(metricsSystem);
getAccountNotFoundInFlatDatabaseCounter =
@ -54,7 +54,7 @@ public class FullFlatDbReaderStrategy extends FlatDbReaderStrategy {
}
@Override
public Optional<Bytes> getAccount(
public Optional<Bytes> getFlatAccount(
final Supplier<Optional<Bytes>> worldStateRootHashSupplier,
final NodeLoader nodeLoader,
final Hash accountHash,
@ -71,7 +71,7 @@ public class FullFlatDbReaderStrategy extends FlatDbReaderStrategy {
}
@Override
public Optional<Bytes> getStorageValueByStorageSlotKey(
public Optional<Bytes> getFlatStorageValueByStorageSlotKey(
final Supplier<Optional<Bytes>> worldStateRootHashSupplier,
final Supplier<Optional<Hash>> storageRootSupplier,
final NodeLoader nodeLoader,

@ -44,7 +44,7 @@ import org.apache.tuweni.rlp.RLP;
* methods, which checks if the data is present in the flat database, and if not, queries the merkle
* trie
*/
public class PartialFlatDbReaderStrategy extends FlatDbReaderStrategy {
public class PartialFlatDbStrategy extends FlatDbStrategy {
protected final Counter getAccountMerkleTrieCounter;
protected final Counter getAccountMissingMerkleTrieCounter;
@ -52,7 +52,7 @@ public class PartialFlatDbReaderStrategy extends FlatDbReaderStrategy {
protected final Counter getStorageValueMerkleTrieCounter;
protected final Counter getStorageValueMissingMerkleTrieCounter;
public PartialFlatDbReaderStrategy(final MetricsSystem metricsSystem) {
public PartialFlatDbStrategy(final MetricsSystem metricsSystem) {
super(metricsSystem);
getAccountMerkleTrieCounter =
metricsSystem.createCounter(
@ -80,7 +80,7 @@ public class PartialFlatDbReaderStrategy extends FlatDbReaderStrategy {
}
@Override
public Optional<Bytes> getAccount(
public Optional<Bytes> getFlatAccount(
final Supplier<Optional<Bytes>> worldStateRootHashSupplier,
final NodeLoader nodeLoader,
final Hash accountHash,
@ -111,7 +111,7 @@ public class PartialFlatDbReaderStrategy extends FlatDbReaderStrategy {
}
@Override
public Optional<Bytes> getStorageValueByStorageSlotKey(
public Optional<Bytes> getFlatStorageValueByStorageSlotKey(
final Supplier<Optional<Bytes>> worldStateRootHashSupplier,
final Supplier<Optional<Hash>> storageRootSupplier,
final NodeLoader nodeLoader,

@ -498,7 +498,9 @@ public class BonsaiWorldState
@Override
public Hash frontierRootHash() {
return calculateRootHash(
Optional.of(new BonsaiWorldStateKeyValueStorage.Updater(noOpSegmentedTx, noOpTx)),
Optional.of(
new BonsaiWorldStateKeyValueStorage.Updater(
noOpSegmentedTx, noOpTx, worldStateStorage.getFlatDbStrategy())),
accumulator.copy());
}

@ -12,7 +12,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.bonsai;
package org.hyperledger.besu.ethereum.bonsai.storage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY;
@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.StorageSlotKey;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.core.TrieGenerator;
import org.hyperledger.besu.ethereum.rlp.RLP;
@ -372,7 +371,7 @@ public class BonsaiWorldStateKeyValueStorageTest {
// clear
storage.clear();
assertThat(storage.getFlatDbReaderStrategy()).isNotNull();
assertThat(storage.getFlatDbStrategy()).isNotNull();
assertThat(storage.getAccount(Hash.ZERO)).isEmpty();
}

@ -69,7 +69,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'tpSnjt4HgqSiOTJhBbYdB0r1nFX4QZbicjfloI71Wf0='
knownHash = 'BhqPyj1fT50NWuHTgzgCmW1ynAPj/2QiGWraq5OwgOQ='
}
check.dependsOn('checkAPIChanges')

@ -23,6 +23,7 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tuweni.bytes.Bytes;
/** Service provided by Besu to facilitate persistent data storage. */
public interface SegmentedKeyValueStorage extends Closeable {
@ -37,6 +38,18 @@ public interface SegmentedKeyValueStorage extends Closeable {
*/
Optional<byte[]> get(SegmentIdentifier segment, byte[] key) throws StorageException;
/**
* Find the key and corresponding value "nearest to" the specified key. Nearest is defined as
* either matching the supplied key or the key lexicographically prior to it.
*
* @param segmentIdentifier segment to scan
* @param key key for which we are searching for the nearest match.
* @return Optional of NearestKeyValue-wrapped matched key and corresponding value.
* @throws StorageException the storage exception
*/
Optional<NearestKeyValue> getNearestTo(final SegmentIdentifier segmentIdentifier, Bytes key)
throws StorageException;
/**
* Contains key.
*
@ -144,4 +157,22 @@ public interface SegmentedKeyValueStorage extends Closeable {
* @return boolean indicating whether the underlying storage is closed.
*/
boolean isClosed();
/**
* record type used to wrap responses from getNearestTo, includes the matched key and the value.
*
* @param key the matched (nearest) key
* @param value the corresponding value
*/
record NearestKeyValue(Bytes key, Optional<byte[]> value) {
/**
* Convenience method to map the Optional value to Bytes.
*
* @return Optional of Bytes.
*/
public Optional<Bytes> wrapBytes() {
return value.map(Bytes::wrap);
}
}
}

@ -33,8 +33,11 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tuweni.bytes.Bytes;
import org.rocksdb.AbstractRocksIterator;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.OptimisticTransactionDB;
import org.rocksdb.RocksIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -73,6 +76,18 @@ public class RocksDBColumnarKeyValueSnapshot
return snapTx.get(segment, key);
}
@Override
public Optional<NearestKeyValue> getNearestTo(
final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException {
try (final RocksIterator rocksIterator = snapTx.getIterator(segmentIdentifier)) {
rocksIterator.seekForPrev(key.toArrayUnsafe());
return Optional.of(rocksIterator)
.filter(AbstractRocksIterator::isValid)
.map(it -> new NearestKeyValue(Bytes.of(it.key()), Optional.of(it.value())));
}
}
@Override
public Stream<Pair<byte[], byte[]>> stream(final SegmentIdentifier segment) {
throwIfClosed();

@ -44,6 +44,7 @@ import com.google.common.base.Splitter;
import com.google.common.collect.Streams;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tuweni.bytes.Bytes;
import org.rocksdb.AbstractRocksIterator;
import org.rocksdb.BlockBasedTableConfig;
import org.rocksdb.BloomFilter;
import org.rocksdb.ColumnFamilyDescriptor;
@ -318,6 +319,19 @@ public abstract class RocksDBColumnarKeyValueStorage implements SegmentedKeyValu
}
}
@Override
public Optional<NearestKeyValue> getNearestTo(
final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException {
try (final RocksIterator rocksIterator =
getDB().newIterator(safeColumnHandle(segmentIdentifier))) {
rocksIterator.seekForPrev(key.toArrayUnsafe());
return Optional.of(rocksIterator)
.filter(AbstractRocksIterator::isValid)
.map(it -> new NearestKeyValue(Bytes.of(it.key()), Optional.of(it.value())));
}
}
@Override
public Stream<Pair<byte[], byte[]>> stream(final SegmentIdentifier segmentIdentifier) {
final RocksIterator rocksIterator = getDB().newIterator(safeColumnHandle(segmentIdentifier));

@ -137,6 +137,19 @@ public class RocksDBSnapshotTransaction
}
}
/**
* get a RocksIterator that reads through the transaction to represent the current state.
*
* <p>be sure to close this iterator, like in a try-with-resources block, otherwise a native
* memory leak might occur.
*
* @param segmentId id for the segment to iterate over.
* @return RocksIterator
*/
public RocksIterator getIterator(final SegmentIdentifier segmentId) {
return snapTx.getIterator(readOptions, columnFamilyMapper.apply(segmentId));
}
/**
* Stream.
*

@ -32,7 +32,7 @@ import org.apache.tuweni.bytes.Bytes;
*/
public class InMemoryKeyValueStorage extends SegmentedKeyValueStorageAdapter {
private static final SegmentIdentifier SEGMENT_IDENTIFIER =
static final SegmentIdentifier SEGMENT_IDENTIFIER =
new SegmentIdentifier() {
private static final String NAME = "SEGMENT_IDENTIFIER";

@ -93,6 +93,26 @@ public class LayeredKeyValueStorage extends SegmentedInMemoryKeyValueStorage
}
}
@Override
public Optional<NearestKeyValue> getNearestTo(
final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException {
Optional<NearestKeyValue> ourNearest = super.getNearestTo(segmentIdentifier, key);
Optional<NearestKeyValue> parentNearest = parent.getNearestTo(segmentIdentifier, key);
if (ourNearest.isPresent() && parentNearest.isPresent()) {
// Both are present, return the one closer to the key
int ourDistance = ourNearest.get().key().commonPrefixLength(key);
int parentDistance = parentNearest.get().key().commonPrefixLength(key);
return (ourDistance <= parentDistance) ? ourNearest : parentNearest;
} else if (ourNearest.isPresent()) {
// Only ourNearest is present
return ourNearest;
} else {
// return parentNearest, which may be an empty Optional
return parentNearest;
}
}
@Override
public Stream<Pair<byte[], byte[]>> stream(final SegmentIdentifier segmentId) {
throwIfClosed();

@ -24,6 +24,7 @@ import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;
import java.io.PrintStream;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -113,6 +114,32 @@ public class SegmentedInMemoryKeyValueStorage
}
}
@Override
public Optional<NearestKeyValue> getNearestTo(
final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException {
final Lock lock = rwLock.readLock();
lock.lock();
try {
// TODO: revisit this for sort performance
Comparator<Map.Entry<Bytes, Optional<byte[]>>> comparing =
Comparator.comparing(
(Map.Entry<Bytes, Optional<byte[]>> a) -> a.getKey().commonPrefixLength(key))
.thenComparing(Map.Entry.comparingByKey());
return this.hashValueStore
.computeIfAbsent(segmentIdentifier, s -> new HashMap<>())
.entrySet()
.stream()
// only return keys equal to or less than
.filter(e -> e.getKey().compareTo(key) <= 0)
.sorted(comparing.reversed())
.findFirst()
.map(z -> new NearestKeyValue(z.getKey(), z.getValue()));
} finally {
lock.unlock();
}
}
@Override
public Set<byte[]> getAllKeysThat(
final SegmentIdentifier segmentIdentifier, final Predicate<byte[]> returnCondition) {

@ -51,6 +51,10 @@ public class SegmentedKeyValueStorageAdapter implements KeyValueStorage {
this.storage = storage;
}
SegmentedKeyValueStorage getSegmentedStore() {
return this.storage;
}
@Override
public void clear() {
throwIfClosed();

@ -0,0 +1,82 @@
/*
* 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.services.kvstore;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage.SEGMENT_IDENTIFIER;
import org.hyperledger.besu.kvstore.AbstractKeyValueStorageTest;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction;
import java.util.stream.IntStream;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
public abstract class AbstractSegmentedKeyValueStorageTest extends AbstractKeyValueStorageTest {
public abstract SegmentedKeyValueStorage createSegmentedStore();
@Test
public void assertSegmentedIsNearestTo() throws Exception {
try (final var store = this.createSegmentedStore()) {
// create 10 entries
final SegmentedKeyValueStorageTransaction tx = store.startTransaction();
IntStream.range(1, 10)
.forEach(
i -> {
final byte[] key = bytesFromHexString("000" + i);
final byte[] value = bytesFromHexString("0FFF");
tx.put(SEGMENT_IDENTIFIER, key, value);
// different common prefix, and reversed order of bytes:
final byte[] key2 = bytesFromHexString("010" + (10 - i));
final byte[] value2 = bytesFromHexString("0FFF");
tx.put(SEGMENT_IDENTIFIER, key2, value2);
});
tx.commit();
// assert 0009 is closest to 000F
var val = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("000F"));
assertThat(val).isPresent();
assertThat(val.get().key()).isEqualTo(Bytes.fromHexString("0009"));
// assert 0109 is closest to 010D
var val2 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("010D"));
assertThat(val2).isPresent();
assertThat(val2.get().key()).isEqualTo(Bytes.fromHexString("0109"));
// assert 0103 is closest to 0103
var val3 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0103"));
assertThat(val3).isPresent();
assertThat(val3.get().key()).isEqualTo(Bytes.fromHexString("0103"));
// assert 0003 is closest to 0003
var val4 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0003"));
assertThat(val4).isPresent();
assertThat(val4.get().key()).isEqualTo(Bytes.fromHexString("0003"));
// assert 0001 is closest to 0001
var val5 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0001"));
assertThat(val5).isPresent();
assertThat(val5.get().key()).isEqualTo(Bytes.fromHexString("0001"));
// assert 0000 is not present
var val6 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0000"));
assertThat(val6).isNotPresent();
}
}
}

@ -14,13 +14,18 @@
*/
package org.hyperledger.besu.services.kvstore;
import org.hyperledger.besu.kvstore.AbstractKeyValueStorageTest;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
public class InMemoryKeyValueStorageTest extends AbstractKeyValueStorageTest {
public class InMemoryKeyValueStorageTest extends AbstractSegmentedKeyValueStorageTest {
@Override
protected KeyValueStorage createStore() {
return new InMemoryKeyValueStorage();
}
@Override
public SegmentedKeyValueStorage createSegmentedStore() {
return new SegmentedInMemoryKeyValueStorage();
}
}

@ -0,0 +1,33 @@
/*
* 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.services.kvstore;
import static org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage.SEGMENT_IDENTIFIER;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
public class LayeredKeyValueStorageTest extends AbstractSegmentedKeyValueStorageTest {
@Override
protected KeyValueStorage createStore() {
return new SegmentedKeyValueStorageAdapter(SEGMENT_IDENTIFIER, createSegmentedStore());
}
@Override
public SegmentedKeyValueStorage createSegmentedStore() {
return new LayeredKeyValueStorage(new SegmentedInMemoryKeyValueStorage());
}
}
Loading…
Cancel
Save