diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java index 44c79c46b8d..80bd2ce86eb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java @@ -47,7 +47,7 @@ public BonsaiSnapshotWorldStateKeyValueStorage( final ObservableMetricsSystem metricsSystem) { super( parentWorldStateStorage.flatDbMode, - parentWorldStateStorage.flatDbReaderStrategy, + parentWorldStateStorage.flatDbStrategy, segmentedWorldStateStorage, trieLogStorage, metricsSystem); @@ -77,7 +77,8 @@ private boolean isClosedGet() { public BonsaiUpdater updater() { return new Updater( ((SnappedKeyValueStorage) composedWorldStateStorage).getSnapshotTransaction(), - trieLogStorage.startTransaction()); + trieLogStorage.startTransaction(), + flatDbStrategy); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java index 1201fffb3b8..9756b280e45 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java @@ -21,9 +21,9 @@ 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 BonsaiWorldStateKeyValueStorage( 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 FlatDbMode getFlatDbMode() { return flatDbMode; } - public FlatDbReaderStrategy getFlatDbReaderStrategy() { - return flatDbReaderStrategy; - } - @Override public Optional 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 getAccount(final Hash accountHash) { - return getFlatDbReaderStrategy() - .getAccount( + return getFlatDbStrategy() + .getFlatAccount( this::getWorldStateRootHash, this::getAccountStateTrieNode, accountHash, @@ -226,8 +238,8 @@ public Optional getStorageValueByStorageSlotKey( final Supplier> 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 Optional getStorageValueByStorageSlotKey( @Override public Map streamFlatAccounts( final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { - return getFlatDbReaderStrategy() + return getFlatDbStrategy() .streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, max); } @Override public Map 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 boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHas 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 void downgradeToPartialFlatDbMode() { @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 void clearTrieLog() { @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 static class Updater implements BonsaiUpdater { 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 BonsaiUpdater putCode(final Hash accountHash, final Bytes32 codeHash, fin // 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 BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes acc // 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 synchronized BonsaiUpdater putAccountStorageTrieNode( @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 diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbReaderStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java similarity index 69% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbReaderStrategy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java index 74c7b101c25..c561dcd8609 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbReaderStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java @@ -26,6 +26,7 @@ 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.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 FlatDbReaderStrategy(final MetricsSystem metricsSystem) { /* * Retrieves the account data for the given account hash, using the world state root hash supplier and node loader. */ - public abstract Optional getAccount( + public abstract Optional getFlatAccount( Supplier> worldStateRootHashSupplier, NodeLoader nodeLoader, Hash accountHash, @@ -94,7 +95,7 @@ public abstract Optional getAccount( * 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 getStorageValueByStorageSlotKey( + public abstract Optional getFlatStorageValueByStorageSlotKey( Supplier> worldStateRootHashSupplier, Supplier> storageRootSupplier, NodeLoader nodeLoader, @@ -105,7 +106,7 @@ public abstract Optional getStorageValueByStorageSlotKey( /* * Retrieves the code data for the given code hash and account hash. */ - public Optional getCode( + public Optional 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 Optional getCode( } } + /* + * 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); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbReaderStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java similarity index 93% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbReaderStrategy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java index efce863a802..de42ba50979 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbReaderStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java @@ -31,13 +31,13 @@ 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 FullFlatDbReaderStrategy(final MetricsSystem metricsSystem) { } @Override - public Optional getAccount( + public Optional getFlatAccount( final Supplier> worldStateRootHashSupplier, final NodeLoader nodeLoader, final Hash accountHash, @@ -71,7 +71,7 @@ public Optional getAccount( } @Override - public Optional getStorageValueByStorageSlotKey( + public Optional getFlatStorageValueByStorageSlotKey( final Supplier> worldStateRootHashSupplier, final Supplier> storageRootSupplier, final NodeLoader nodeLoader, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbReaderStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java similarity index 96% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbReaderStrategy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java index 288ff67b095..1dc4288b21a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbReaderStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java @@ -44,7 +44,7 @@ * 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 PartialFlatDbReaderStrategy(final MetricsSystem metricsSystem) { } @Override - public Optional getAccount( + public Optional getFlatAccount( final Supplier> worldStateRootHashSupplier, final NodeLoader nodeLoader, final Hash accountHash, @@ -111,7 +111,7 @@ public Optional getAccount( } @Override - public Optional getStorageValueByStorageSlotKey( + public Optional getFlatStorageValueByStorageSlotKey( final Supplier> worldStateRootHashSupplier, final Supplier> storageRootSupplier, final NodeLoader nodeLoader, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java index f28e24b0970..b0a48b83043 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java @@ -498,7 +498,9 @@ public void rollback() { @Override public Hash frontierRootHash() { return calculateRootHash( - Optional.of(new BonsaiWorldStateKeyValueStorage.Updater(noOpSegmentedTx, noOpTx)), + Optional.of( + new BonsaiWorldStateKeyValueStorage.Updater( + noOpSegmentedTx, noOpTx, worldStateStorage.getFlatDbStrategy())), accumulator.copy()); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java similarity index 98% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java index 6f67f657f02..32a8272237f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateKeyValueStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -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 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 @@ void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { // clear storage.clear(); - assertThat(storage.getFlatDbReaderStrategy()).isNotNull(); + assertThat(storage.getFlatDbStrategy()).isNotNull(); assertThat(storage.getAccount(Hash.ZERO)).isEmpty(); } diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index e544419b8ab..a091f97c151 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -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') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java index 55d04d470c4..df8a8c48941 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java @@ -23,6 +23,7 @@ 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 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 getNearestTo(final SegmentIdentifier segmentIdentifier, Bytes key) + throws StorageException; + /** * Contains key. * @@ -144,4 +157,22 @@ Set getAllValuesFromKeysThat( * @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 value) { + + /** + * Convenience method to map the Optional value to Bytes. + * + * @return Optional of Bytes. + */ + public Optional wrapBytes() { + return value.map(Bytes::wrap); + } + } } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java index 6a123069001..b1a0920a516 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java @@ -33,8 +33,11 @@ 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 Optional get(final SegmentIdentifier segment, final byte[] key) return snapTx.get(segment, key); } + @Override + public Optional 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> stream(final SegmentIdentifier segment) { throwIfClosed(); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index 9462f9bcb14..952da71f0bc 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -44,6 +44,7 @@ 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 Optional get(final SegmentIdentifier segment, final byte[] key) } } + @Override + public Optional 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> stream(final SegmentIdentifier segmentIdentifier) { final RocksIterator rocksIterator = getDB().newIterator(safeColumnHandle(segmentIdentifier)); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java index 0897493eb19..ad3438d381b 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java @@ -137,6 +137,19 @@ public void remove(final SegmentIdentifier segmentId, final byte[] key) { } } + /** + * get a RocksIterator that reads through the transaction to represent the current state. + * + *

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. * diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java index 3b1139cd179..dbd62eaff9a 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java @@ -32,7 +32,7 @@ */ 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"; diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java index ddaf567433f..6713772a03d 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java @@ -93,6 +93,26 @@ public Optional get(final SegmentIdentifier segmentId, final byte[] key) } } + @Override + public Optional getNearestTo( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + Optional ourNearest = super.getNearestTo(segmentIdentifier, key); + Optional 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> stream(final SegmentIdentifier segmentId) { throwIfClosed(); diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java index fd48384c366..ff8ca7249cb 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java @@ -24,6 +24,7 @@ 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 Optional get(final SegmentIdentifier segmentIdentifier, final byt } } + @Override + public Optional getNearestTo( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + + final Lock lock = rwLock.readLock(); + lock.lock(); + try { + // TODO: revisit this for sort performance + Comparator>> comparing = + Comparator.comparing( + (Map.Entry> 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 getAllKeysThat( final SegmentIdentifier segmentIdentifier, final Predicate returnCondition) { diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java index a02e9284fa6..404689be420 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java @@ -51,6 +51,10 @@ public SegmentedKeyValueStorageAdapter( this.storage = storage; } + SegmentedKeyValueStorage getSegmentedStore() { + return this.storage; + } + @Override public void clear() { throwIfClosed(); diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java new file mode 100644 index 00000000000..47aee3ae580 --- /dev/null +++ b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java @@ -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(); + } + } +} diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java index 90581476279..55a10ffa8d1 100644 --- a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java +++ b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java @@ -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(); + } } diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java new file mode 100644 index 00000000000..2117c1c199b --- /dev/null +++ b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java @@ -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()); + } +}