From 048623a2e3fa09ca6816b2bcdade24d7896bd0ef Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 23 Oct 2023 13:29:10 +1000 Subject: [PATCH 01/11] Move TrieLogProvider setup code to AbstractTrieLogManager Signed-off-by: Simon Dudley --- .../cache/CachedWorldStorageManager.java | 80 ------------------- .../trielog/AbstractTrieLogManager.java | 76 +++++++++++++++++- 2 files changed, 74 insertions(+), 82 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java index d0f1be97d89..5a340e7e293 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java @@ -21,16 +21,11 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.AbstractTrieLogManager; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.BesuContext; -import org.hyperledger.besu.plugin.services.TrieLogService; -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; import java.util.ArrayList; import java.util.Comparator; @@ -39,10 +34,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import java.util.stream.LongStream; -import java.util.stream.Stream; -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,76 +219,4 @@ public void onClearTrieLog() { public void onCloseStorage() { this.cachedWorldStatesByHash.clear(); } - - @VisibleForTesting - @Override - protected TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { - // if we have a TrieLogService from pluginContext, use it. - var trieLogServicez = - Optional.ofNullable(pluginContext) - .flatMap(context -> context.getService(TrieLogService.class)); - - if (trieLogServicez.isPresent()) { - var trieLogService = trieLogServicez.get(); - // push the TrieLogProvider into the TrieLogService - trieLogService.configureTrieLogProvider(getTrieLogProvider()); - - // configure plugin observers: - trieLogService.getObservers().forEach(trieLogObservers::subscribe); - - // return the TrieLogFactory implementation from the TrieLogService - return trieLogService.getTrieLogFactory(); - } else { - // Otherwise default to TrieLogFactoryImpl - return new TrieLogFactoryImpl(); - } - } - - @VisibleForTesting - TrieLogProvider getTrieLogProvider() { - return new TrieLogProvider() { - @Override - public Optional getTrieLogLayer(final Hash blockHash) { - return CachedWorldStorageManager.this.getTrieLogLayer(blockHash); - } - - @Override - public Optional getTrieLogLayer(final long blockNumber) { - return CachedWorldStorageManager.this - .blockchain - .getBlockHeader(blockNumber) - .map(BlockHeader::getHash) - .flatMap(CachedWorldStorageManager.this::getTrieLogLayer); - } - - @Override - public List getTrieLogsByRange( - final long fromBlockNumber, final long toBlockNumber) { - return rangeAsStream(fromBlockNumber, toBlockNumber) - .map(blockchain::getBlockHeader) - .map( - headerOpt -> - headerOpt.flatMap( - header -> - CachedWorldStorageManager.this - .getTrieLogLayer(header.getBlockHash()) - .map( - layer -> - new TrieLogRangeTuple( - header.getBlockHash(), header.getNumber(), layer)))) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); - } - - Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { - if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { - throw new IllegalArgumentException("Requested Range too large"); - } - long left = Math.min(fromBlockNumber, toBlockNumber); - long right = Math.max(fromBlockNumber, toBlockNumber); - return LongStream.range(left, right).boxed(); - } - }; - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java index 90aff386e83..7ab357c745c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java @@ -24,13 +24,18 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.TrieLogService; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent.TrieLogObserver; import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; import org.hyperledger.besu.util.Subscribers; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.LongStream; +import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes32; @@ -63,8 +68,6 @@ protected AbstractTrieLogManager( this.trieLogFactory = setupTrieLogFactory(pluginContext); } - protected abstract TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext); - @Override public synchronized void saveTrieLog( final BonsaiWorldStateUpdateAccumulator localUpdater, @@ -161,4 +164,73 @@ public synchronized long subscribe(final TrieLogObserver sub) { public synchronized void unsubscribe(final long id) { trieLogObservers.unsubscribe(id); } + + private TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { + // if we have a TrieLogService from pluginContext, use it. + var trieLogServicez = + Optional.ofNullable(pluginContext) + .flatMap(context -> context.getService(TrieLogService.class)); + + if (trieLogServicez.isPresent()) { + var trieLogService = trieLogServicez.get(); + // push the TrieLogProvider into the TrieLogService + trieLogService.configureTrieLogProvider(getTrieLogProvider()); + + // configure plugin observers: + trieLogService.getObservers().forEach(trieLogObservers::subscribe); + + // return the TrieLogFactory implementation from the TrieLogService + return trieLogService.getTrieLogFactory(); + } else { + // Otherwise default to TrieLogFactoryImpl + return new TrieLogFactoryImpl(); + } + } + + private TrieLogProvider getTrieLogProvider() { + return new TrieLogProvider() { + @Override + public Optional getTrieLogLayer(final Hash blockHash) { + return AbstractTrieLogManager.this.getTrieLogLayer(blockHash); + } + + @Override + public Optional getTrieLogLayer(final long blockNumber) { + return AbstractTrieLogManager.this + .blockchain + .getBlockHeader(blockNumber) + .map(BlockHeader::getHash) + .flatMap(AbstractTrieLogManager.this::getTrieLogLayer); + } + + @Override + public List getTrieLogsByRange( + final long fromBlockNumber, final long toBlockNumber) { + return rangeAsStream(fromBlockNumber, toBlockNumber) + .map(blockchain::getBlockHeader) + .map( + headerOpt -> + headerOpt.flatMap( + header -> + AbstractTrieLogManager.this + .getTrieLogLayer(header.getBlockHash()) + .map( + layer -> + new TrieLogRangeTuple( + header.getBlockHash(), header.getNumber(), layer)))) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + } + + Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { + if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { + throw new IllegalArgumentException("Requested Range too large"); + } + long left = Math.min(fromBlockNumber, toBlockNumber); + long right = Math.max(fromBlockNumber, toBlockNumber); + return LongStream.range(left, right).boxed(); + } + }; + } } From 24bdcf68052d31e8636c86331928378df2c5c3b9 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 23 Oct 2023 16:04:12 +1000 Subject: [PATCH 02/11] Decouple CachedWorldStorageManager from TrieLogManager Separate out the concepts of world state caching from trie log management Make AbstractTrieLogManager a concrete implemenation (to be further renamed/refactored next commit) Signed-off-by: Simon Dudley --- .../bonsai/BonsaiWorldStateProvider.java | 37 +++++++----- .../cache/CachedWorldStorageManager.java | 55 ++++++++--------- .../trielog/AbstractTrieLogManager.java | 30 +--------- .../bonsai/trielog/TrieLogManager.java | 15 ----- .../bonsai/worldview/BonsaiWorldState.java | 12 +++- .../bonsai/BonsaiSnapshotIsolationTests.java | 7 ++- .../bonsai/BonsaiWorldStateArchiveTest.java | 12 +++- .../bonsai/trielog/TrieLogManagerTests.java | 12 +--- .../BonsaiReferenceTestWorldState.java | 60 +++++++++++++------ 9 files changed, 119 insertions(+), 121 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index 299b826369c..b147ab83745 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.bonsai.trielog.AbstractTrieLogManager; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; @@ -62,6 +63,7 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { private final Blockchain blockchain; + private final CachedWorldStorageManager cachedWorldStorageManager; private final TrieLogManager trieLogManager; private final BonsaiWorldState persistedState; private final BonsaiWorldStateKeyValueStorage worldStateStorage; @@ -91,15 +93,12 @@ public BonsaiWorldStateProvider( final ObservableMetricsSystem metricsSystem, final BesuContext pluginContext) { + this.cachedWorldStorageManager = + new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); // TODO: de-dup constructors this.trieLogManager = - new CachedWorldStorageManager( - this, - blockchain, - worldStateStorage, - metricsSystem, - maxLayersToLoad.orElse(RETAINED_LAYERS), - pluginContext); + new AbstractTrieLogManager( + blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS), pluginContext); this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; @@ -108,16 +107,18 @@ public BonsaiWorldStateProvider( .getBlockHeader(persistedState.getWorldStateBlockHash()) .ifPresent( blockHeader -> - this.trieLogManager.addCachedLayer( + this.cachedWorldStorageManager.addCachedLayer( blockHeader, persistedState.getWorldStateRootHash(), persistedState)); } @VisibleForTesting BonsaiWorldStateProvider( + final CachedWorldStorageManager cachedWorldStorageManager, final TrieLogManager trieLogManager, final BonsaiWorldStateKeyValueStorage worldStateStorage, final Blockchain blockchain, final CachedMerkleTrieLoader cachedMerkleTrieLoader) { + this.cachedWorldStorageManager = cachedWorldStorageManager; this.trieLogManager = trieLogManager; this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; @@ -127,13 +128,13 @@ public BonsaiWorldStateProvider( .getBlockHeader(persistedState.getWorldStateBlockHash()) .ifPresent( blockHeader -> - this.trieLogManager.addCachedLayer( + this.cachedWorldStorageManager.addCachedLayer( blockHeader, persistedState.getWorldStateRootHash(), persistedState)); } @Override public Optional get(final Hash rootHash, final Hash blockHash) { - return trieLogManager + return cachedWorldStorageManager .getWorldState(blockHash) .or( () -> { @@ -148,7 +149,7 @@ public Optional get(final Hash rootHash, final Hash blockHash) { @Override public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { - return trieLogManager.containWorldStateStorage(blockHash) + return cachedWorldStorageManager.containWorldStateStorage(blockHash) || persistedState.blockHash().equals(blockHash) || worldStateStorage.isWorldStateAvailable(rootHash, blockHash); } @@ -167,10 +168,10 @@ public Optional getMutable( trieLogManager.getMaxLayersToLoad()); return Optional.empty(); } - return trieLogManager + return cachedWorldStorageManager .getWorldState(blockHeader.getHash()) - .or(() -> trieLogManager.getNearestWorldState(blockHeader)) - .or(() -> trieLogManager.getHeadWorldState(blockchain::getBlockHeader)) + .or(() -> cachedWorldStorageManager.getNearestWorldState(blockHeader)) + .or(() -> cachedWorldStorageManager.getHeadWorldState(blockchain::getBlockHeader)) .flatMap( bonsaiWorldState -> rollMutableStateToBlockHash(bonsaiWorldState, blockHeader.getHash())) @@ -354,11 +355,15 @@ public TrieLogManager getTrieLogManager() { return trieLogManager; } + public CachedWorldStorageManager getCachedWorldStorageManager() { + return cachedWorldStorageManager; + } + @Override public void resetArchiveStateTo(final BlockHeader blockHeader) { persistedState.resetWorldStateTo(blockHeader); - this.trieLogManager.reset(); - this.trieLogManager.addCachedLayer( + this.cachedWorldStorageManager.reset(); + this.cachedWorldStorageManager.addCachedLayer( blockHeader, persistedState.getWorldStateRootHash(), persistedState); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java index 5a340e7e293..f2434bbf912 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java @@ -20,12 +20,9 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.AbstractTrieLogManager; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.plugin.BesuContext; import java.util.ArrayList; import java.util.Comparator; @@ -39,44 +36,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CachedWorldStorageManager extends AbstractTrieLogManager - implements BonsaiStorageSubscriber { +public class CachedWorldStorageManager implements BonsaiStorageSubscriber { + public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class); private final BonsaiWorldStateProvider archive; private final ObservableMetricsSystem metricsSystem; - CachedWorldStorageManager( + private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; + private final Map cachedWorldStatesByHash; + + private CachedWorldStorageManager( final BonsaiWorldStateProvider archive, - final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, - final long maxLayersToLoad, final Map cachedWorldStatesByHash, - final BesuContext pluginContext, final ObservableMetricsSystem metricsSystem) { - super(blockchain, worldStateStorage, maxLayersToLoad, cachedWorldStatesByHash, pluginContext); worldStateStorage.subscribe(this); + this.rootWorldStateStorage = worldStateStorage; + this.cachedWorldStatesByHash = cachedWorldStatesByHash; this.archive = archive; this.metricsSystem = metricsSystem; } public CachedWorldStorageManager( final BonsaiWorldStateProvider archive, - final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, - final ObservableMetricsSystem metricsSystem, - final long maxLayersToLoad, - final BesuContext pluginContext) { - this( - archive, - blockchain, - worldStateStorage, - maxLayersToLoad, - new ConcurrentHashMap<>(), - pluginContext, - metricsSystem); + final ObservableMetricsSystem metricsSystem) { + this(archive, worldStateStorage, new ConcurrentHashMap<>(), metricsSystem); } - @Override public synchronized void addCachedLayer( final BlockHeader blockHeader, final Hash worldStateRootHash, @@ -124,7 +111,20 @@ public synchronized void addCachedLayer( scrubCachedLayers(blockHeader.getNumber()); } - @Override + private synchronized void scrubCachedLayers(final long newMaxHeight) { + if (cachedWorldStatesByHash.size() > RETAINED_LAYERS) { + final long waterline = newMaxHeight - RETAINED_LAYERS; + cachedWorldStatesByHash.values().stream() + .filter(layer -> layer.getBlockNumber() < waterline) + .toList() + .forEach( + layer -> { + cachedWorldStatesByHash.remove(layer.getBlockHash()); + layer.close(); + }); + } + } + public Optional getWorldState(final Hash blockHash) { if (cachedWorldStatesByHash.containsKey(blockHash)) { // return a new worldstate using worldstate storage and an isolated copy of the updater @@ -142,7 +142,6 @@ public Optional getWorldState(final Hash blockHash) { return Optional.empty(); } - @Override public Optional getNearestWorldState(final BlockHeader blockHeader) { LOG.atDebug() .setMessage("getting nearest worldstate for {}") @@ -175,7 +174,6 @@ public Optional getNearestWorldState(final BlockHeader blockHe archive, new BonsaiWorldStateLayerStorage(storage))); } - @Override public Optional getHeadWorldState( final Function> hashBlockHeaderFunction) { @@ -195,7 +193,10 @@ public Optional getHeadWorldState( }); } - @Override + public boolean containWorldStateStorage(final Hash blockHash) { + return cachedWorldStatesByHash.containsKey(blockHash); + } + public void reset() { this.cachedWorldStatesByHash.clear(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java index 7ab357c745c..14afc2ef3b5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java @@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.bonsai.trielog; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedBonsaiWorldView; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiUpdater; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; @@ -32,38 +31,32 @@ import org.hyperledger.besu.util.Subscribers; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.LongStream; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractTrieLogManager implements TrieLogManager { +public class AbstractTrieLogManager implements TrieLogManager { private static final Logger LOG = LoggerFactory.getLogger(AbstractTrieLogManager.class); - public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs protected final Blockchain blockchain; protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; - protected final Map cachedWorldStatesByHash; protected final long maxLayersToLoad; protected final Subscribers trieLogObservers = Subscribers.create(); protected final TrieLogFactory trieLogFactory; - protected AbstractTrieLogManager( + public AbstractTrieLogManager( final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, final long maxLayersToLoad, - final Map cachedWorldStatesByHash, final BesuContext pluginContext) { this.blockchain = blockchain; this.rootWorldStateStorage = worldStateStorage; - this.cachedWorldStatesByHash = cachedWorldStatesByHash; this.maxLayersToLoad = maxLayersToLoad; this.trieLogFactory = setupTrieLogFactory(pluginContext); } @@ -110,20 +103,6 @@ TrieLog prepareTrieLog( return trieLog; } - public synchronized void scrubCachedLayers(final long newMaxHeight) { - if (cachedWorldStatesByHash.size() > RETAINED_LAYERS) { - final long waterline = newMaxHeight - RETAINED_LAYERS; - cachedWorldStatesByHash.values().stream() - .filter(layer -> layer.getBlockNumber() < waterline) - .toList() - .forEach( - layer -> { - cachedWorldStatesByHash.remove(layer.getBlockHash()); - layer.close(); - }); - } - } - private void persistTrieLog( final BlockHeader blockHeader, final Hash worldStateRootHash, @@ -140,11 +119,6 @@ private void persistTrieLog( .put(blockHeader.getHash().toArrayUnsafe(), trieLogFactory.serialize(trieLog)); } - @Override - public boolean containWorldStateStorage(final Hash blockHash) { - return cachedWorldStatesByHash.containsKey(blockHash); - } - @Override public long getMaxLayersToLoad() { return maxLayersToLoad; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java index 5da83180a1c..4a7b1d8c55d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; import java.util.Optional; -import java.util.function.Function; public interface TrieLogManager { @@ -33,22 +32,8 @@ void saveTrieLog( final BlockHeader forBlockHeader, final BonsaiWorldState forWorldState); - void addCachedLayer( - BlockHeader blockHeader, Hash worldStateRootHash, BonsaiWorldState forWorldState); - - boolean containWorldStateStorage(final Hash blockHash); - - Optional getWorldState(final Hash blockHash); - - Optional getNearestWorldState(final BlockHeader blockHeader); - - Optional getHeadWorldState( - final Function> hashBlockHeaderFunction); - long getMaxLayersToLoad(); - void reset(); - Optional getTrieLogLayer(final Hash blockHash); long subscribe(final TrieLogEvent.TrieLogObserver sub); 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 ed81c90f439..d4c4c223a24 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 @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; @@ -69,6 +70,7 @@ public class BonsaiWorldState protected BonsaiWorldStateKeyValueStorage worldStateStorage; protected final CachedMerkleTrieLoader cachedMerkleTrieLoader; + protected final CachedWorldStorageManager cachedWorldStorageManager; protected final TrieLogManager trieLogManager; private BonsaiWorldStateUpdateAccumulator accumulator; @@ -79,12 +81,17 @@ public class BonsaiWorldState public BonsaiWorldState( final BonsaiWorldStateProvider archive, final BonsaiWorldStateKeyValueStorage worldStateStorage) { - this(worldStateStorage, archive.getCachedMerkleTrieLoader(), archive.getTrieLogManager()); + this( + worldStateStorage, + archive.getCachedMerkleTrieLoader(), + archive.getCachedWorldStorageManager(), + archive.getTrieLogManager()); } protected BonsaiWorldState( final BonsaiWorldStateKeyValueStorage worldStateStorage, final CachedMerkleTrieLoader cachedMerkleTrieLoader, + final CachedWorldStorageManager cachedWorldStorageManager, final TrieLogManager trieLogManager) { this.worldStateStorage = worldStateStorage; this.worldStateRootHash = @@ -101,6 +108,7 @@ protected BonsaiWorldState( (addr, value) -> cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value)); this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; + this.cachedWorldStorageManager = cachedWorldStorageManager; this.trieLogManager = trieLogManager; } @@ -396,7 +404,7 @@ public void persist(final BlockHeader blockHeader) { trieLogManager.saveTrieLog(localCopy, newWorldStateRootHash, blockHeader, this); // not save a frozen state in the cache if (!isFrozen) { - trieLogManager.addCachedLayer(blockHeader, newWorldStateRootHash, this); + cachedWorldStorageManager.addCachedLayer(blockHeader, newWorldStateRootHash, this); } }; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java index ed4b4549674..03407d2868f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java @@ -61,8 +61,11 @@ public void testIsolatedFromHead_behindHead() { assertThat(res.isSuccessful()).isTrue(); assertThat(res2.isSuccessful()).isTrue(); - assertThat(archive.getTrieLogManager().containWorldStateStorage(firstBlock.getHash())).isTrue(); - assertThat(archive.getTrieLogManager().containWorldStateStorage(secondBlock.getHash())) + assertThat( + archive.getCachedWorldStorageManager().containWorldStateStorage(firstBlock.getHash())) + .isTrue(); + assertThat( + archive.getCachedWorldStorageManager().containWorldStateStorage(secondBlock.getHash())) .isTrue(); assertThat(archive.getMutable().get(testAddress)).isNotNull(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java index b492b67ffdb..0721522ffb1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; @@ -74,6 +75,7 @@ public class BonsaiWorldStateArchiveTest { @Mock SegmentedKeyValueStorageTransaction segmentedKeyValueStorageTransaction; BonsaiWorldStateProvider bonsaiWorldStateArchive; + @Mock CachedWorldStorageManager cachedWorldStorageManager; @Mock TrieLogManager trieLogManager; @BeforeEach @@ -100,6 +102,7 @@ public void testGetMutableReturnPersistedStateWhenNeeded() { .thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe())); bonsaiWorldStateArchive = new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, @@ -123,7 +126,7 @@ public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); when(blockchain.getChainHeadHeader()).thenReturn(chainHead); assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)).isEmpty(); - verify(trieLogManager, Mockito.never()).getWorldState(any(Hash.class)); + verify(cachedWorldStorageManager, Mockito.never()).getWorldState(any(Hash.class)); } @Test @@ -131,6 +134,7 @@ public void testGetMutableWhenLoadLessThanLimitLayersBack() { bonsaiWorldStateArchive = new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, @@ -142,7 +146,7 @@ public void testGetMutableWhenLoadLessThanLimitLayersBack() { when(mockWorldState.freeze()).thenReturn(mockWorldState); when(trieLogManager.getMaxLayersToLoad()).thenReturn(Long.valueOf(512)); - when(trieLogManager.getWorldState(blockHeader.getHash())) + when(cachedWorldStorageManager.getWorldState(blockHeader.getHash())) .thenReturn(Optional.of(mockWorldState)); when(blockchain.getChainHeadHeader()).thenReturn(chainHead); assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)) @@ -162,6 +166,7 @@ public void testGetMutableWithStorageInconsistencyRollbackTheState() { bonsaiWorldStateArchive = spy( new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, worldStateStorage, blockchain, @@ -186,6 +191,7 @@ public void testGetMutableWithStorageConsistencyNotRollbackTheState() { bonsaiWorldStateArchive = spy( new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, worldStateStorage, blockchain, @@ -222,6 +228,7 @@ public void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState bonsaiWorldStateArchive = spy( new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, worldStateStorage, blockchain, @@ -261,6 +268,7 @@ public void testGetMutableWithRollbackNotOverrideTrieLogLayer() { bonsaiWorldStateArchive = spy( new BonsaiWorldStateProvider( + cachedWorldStorageManager, trieLogManager, new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), blockchain, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index 35ce1b77c06..d5a1440554b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -19,15 +19,12 @@ import static org.mockito.Mockito.spy; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,7 +45,6 @@ public class TrieLogManagerTests { @Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage; @Mock BonsaiWorldState worldState; - @Mock BonsaiWorldStateProvider archive; @Mock Blockchain blockchain; BonsaiWorldStateUpdateAccumulator bonsaiUpdater = spy(new BonsaiWorldStateUpdateAccumulator(worldState, (__, ___) -> {}, (__, ___) -> {})); @@ -58,13 +54,7 @@ public class TrieLogManagerTests { @BeforeEach public void setup() { trieLogManager = - new CachedWorldStorageManager( - archive, - blockchain, - bonsaiWorldStateKeyValueStorage, - new NoOpMetricsSystem(), - 512, - null); + new AbstractTrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); } @Test diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java index ff8a33814cc..972a2d307c5 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogAddedEvent; @@ -54,9 +55,10 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState protected BonsaiReferenceTestWorldState( final BonsaiReferenceTestWorldStateStorage worldStateStorage, final CachedMerkleTrieLoader cachedMerkleTrieLoader, + final CachedWorldStorageManager cachedWorldStorageManager, final TrieLogManager trieLogManager, final BonsaiPreImageProxy preImageProxy) { - super(worldStateStorage, cachedMerkleTrieLoader, trieLogManager); + super(worldStateStorage, cachedMerkleTrieLoader, cachedWorldStorageManager, trieLogManager); this.refTestStorage = worldStateStorage; this.preImageProxy = preImageProxy; setAccumulator( @@ -74,7 +76,11 @@ protected BonsaiReferenceTestWorldState( public ReferenceTestWorldState copy() { var layerCopy = new BonsaiReferenceTestWorldStateStorage(worldStateStorage, preImageProxy); return new BonsaiReferenceTestWorldState( - layerCopy, cachedMerkleTrieLoader, trieLogManager, preImageProxy); + layerCopy, + cachedMerkleTrieLoader, + cachedWorldStorageManager, + trieLogManager, + preImageProxy); } /** @@ -109,9 +115,16 @@ public static BonsaiReferenceTestWorldState create( new InMemoryKeyValueStorageProvider(), metricsSystem), preImageProxy); + final NoOpCachedWorldStorageManager noOpCachedWorldStorageManager = + new NoOpCachedWorldStorageManager(); + final BonsaiReferenceTestWorldState worldState = new BonsaiReferenceTestWorldState( - worldStateStorage, cachedMerkleTrieLoader, trieLogManager, preImageProxy); + worldStateStorage, + cachedMerkleTrieLoader, + noOpCachedWorldStorageManager, + trieLogManager, + preImageProxy); final WorldUpdater updater = worldState.updater(); for (final Map.Entry entry : accounts.entrySet()) { @@ -131,19 +144,14 @@ public void disableRootHashVerification() { disableRootHashVerification = true; } - static class NoOpTrieLogManager implements TrieLogManager { - private final Subscribers trieLogObservers = Subscribers.create(); - private final TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); + static class NoOpCachedWorldStorageManager extends CachedWorldStorageManager { - @Override - public void saveTrieLog( - final BonsaiWorldStateUpdateAccumulator localUpdater, - final Hash forWorldStateRootHash, - final BlockHeader forBlockHeader, - final BonsaiWorldState forWorldState) { - // notify trie log added observers, synchronously - TrieLog trieLog = trieLogFactory.create(localUpdater, forBlockHeader); - trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); + public NoOpCachedWorldStorageManager() { + super( + null, + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()), + new NoOpMetricsSystem()); } @Override @@ -174,12 +182,28 @@ public Optional getHeadWorldState( } @Override - public long getMaxLayersToLoad() { - return 0; + public void reset() {} + } + + static class NoOpTrieLogManager implements TrieLogManager { + private final Subscribers trieLogObservers = Subscribers.create(); + private final TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); + + @Override + public void saveTrieLog( + final BonsaiWorldStateUpdateAccumulator localUpdater, + final Hash forWorldStateRootHash, + final BlockHeader forBlockHeader, + final BonsaiWorldState forWorldState) { + // notify trie log added observers, synchronously + TrieLog trieLog = trieLogFactory.create(localUpdater, forBlockHeader); + trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); } @Override - public void reset() {} + public long getMaxLayersToLoad() { + return 0; + } @Override public Optional getTrieLogLayer(final Hash blockHash) { From 7a6552c3bbae716e199299f4cb8e14fe4c4204e4 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 23 Oct 2023 16:17:45 +1000 Subject: [PATCH 03/11] Rename AbstractTrieLogManager to DefaultTrieLogManager Signed-off-by: Simon Dudley --- .../ethereum/bonsai/BonsaiWorldStateProvider.java | 4 ++-- ...eLogManager.java => DefaultTrieLogManager.java} | 14 +++++++------- .../bonsai/trielog/TrieLogManagerTests.java | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/{AbstractTrieLogManager.java => DefaultTrieLogManager.java} (94%) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index b147ab83745..4a35e65e0da 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -23,7 +23,7 @@ import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.AbstractTrieLogManager; +import org.hyperledger.besu.ethereum.bonsai.trielog.DefaultTrieLogManager; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; @@ -97,7 +97,7 @@ public BonsaiWorldStateProvider( new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); // TODO: de-dup constructors this.trieLogManager = - new AbstractTrieLogManager( + new DefaultTrieLogManager( blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS), pluginContext); this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java similarity index 94% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java index 14afc2ef3b5..8b945dac163 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/AbstractTrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java @@ -39,8 +39,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AbstractTrieLogManager implements TrieLogManager { - private static final Logger LOG = LoggerFactory.getLogger(AbstractTrieLogManager.class); +public class DefaultTrieLogManager implements TrieLogManager { + private static final Logger LOG = LoggerFactory.getLogger(DefaultTrieLogManager.class); public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs protected final Blockchain blockchain; protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; @@ -50,7 +50,7 @@ public class AbstractTrieLogManager implements TrieLogManager { protected final TrieLogFactory trieLogFactory; - public AbstractTrieLogManager( + public DefaultTrieLogManager( final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, final long maxLayersToLoad, @@ -165,16 +165,16 @@ private TrieLogProvider getTrieLogProvider() { return new TrieLogProvider() { @Override public Optional getTrieLogLayer(final Hash blockHash) { - return AbstractTrieLogManager.this.getTrieLogLayer(blockHash); + return DefaultTrieLogManager.this.getTrieLogLayer(blockHash); } @Override public Optional getTrieLogLayer(final long blockNumber) { - return AbstractTrieLogManager.this + return DefaultTrieLogManager.this .blockchain .getBlockHeader(blockNumber) .map(BlockHeader::getHash) - .flatMap(AbstractTrieLogManager.this::getTrieLogLayer); + .flatMap(DefaultTrieLogManager.this::getTrieLogLayer); } @Override @@ -186,7 +186,7 @@ public List getTrieLogsByRange( headerOpt -> headerOpt.flatMap( header -> - AbstractTrieLogManager.this + DefaultTrieLogManager.this .getTrieLogLayer(header.getBlockHash()) .map( layer -> diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index d5a1440554b..a4b5012a80d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -54,7 +54,7 @@ public class TrieLogManagerTests { @BeforeEach public void setup() { trieLogManager = - new AbstractTrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); + new DefaultTrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); } @Test From 0bb66f1d21f6ec9c337f1f32bbb5ad7cb619b50f Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Mon, 23 Oct 2023 17:16:07 +1000 Subject: [PATCH 04/11] Make TrieLogManager the default implementation and remove the interface Signed-off-by: Simon Dudley --- .../bonsai/BonsaiWorldStateProvider.java | 3 +- .../bonsai/trielog/DefaultTrieLogManager.java | 210 ------------------ .../bonsai/trielog/TrieLogManager.java | 175 ++++++++++++++- .../bonsai/trielog/TrieLogManagerTests.java | 3 +- .../BonsaiReferenceTestWorldState.java | 14 +- 5 files changed, 177 insertions(+), 228 deletions(-) delete mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index 4a35e65e0da..bcb7f9f65fe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.DefaultTrieLogManager; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; @@ -97,7 +96,7 @@ public BonsaiWorldStateProvider( new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); // TODO: de-dup constructors this.trieLogManager = - new DefaultTrieLogManager( + new TrieLogManager( blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS), pluginContext); this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java deleted file mode 100644 index 8b945dac163..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/DefaultTrieLogManager.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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.trielog; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiUpdater; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.plugin.BesuContext; -import org.hyperledger.besu.plugin.services.TrieLogService; -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent.TrieLogObserver; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; -import org.hyperledger.besu.util.Subscribers; - -import java.util.List; -import java.util.Optional; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DefaultTrieLogManager implements TrieLogManager { - private static final Logger LOG = LoggerFactory.getLogger(DefaultTrieLogManager.class); - public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs - protected final Blockchain blockchain; - protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; - - protected final long maxLayersToLoad; - protected final Subscribers trieLogObservers = Subscribers.create(); - - protected final TrieLogFactory trieLogFactory; - - public DefaultTrieLogManager( - final Blockchain blockchain, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final long maxLayersToLoad, - final BesuContext pluginContext) { - this.blockchain = blockchain; - this.rootWorldStateStorage = worldStateStorage; - this.maxLayersToLoad = maxLayersToLoad; - this.trieLogFactory = setupTrieLogFactory(pluginContext); - } - - @Override - public synchronized void saveTrieLog( - final BonsaiWorldStateUpdateAccumulator localUpdater, - final Hash forWorldStateRootHash, - final BlockHeader forBlockHeader, - final BonsaiWorldState forWorldState) { - // do not overwrite a trielog layer that already exists in the database. - // if it's only in memory we need to save it - // for example, in case of reorg we don't replace a trielog layer - if (rootWorldStateStorage.getTrieLog(forBlockHeader.getHash()).isEmpty()) { - final BonsaiUpdater stateUpdater = forWorldState.getWorldStateStorage().updater(); - boolean success = false; - try { - final TrieLog trieLog = prepareTrieLog(forBlockHeader, localUpdater); - persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater); - - // notify trie log added observers, synchronously - trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); - - success = true; - } finally { - if (success) { - stateUpdater.commit(); - } else { - stateUpdater.rollback(); - } - } - } - } - - @VisibleForTesting - TrieLog prepareTrieLog( - final BlockHeader blockHeader, final BonsaiWorldStateUpdateAccumulator localUpdater) { - LOG.atDebug() - .setMessage("Adding layered world state for {}") - .addArgument(blockHeader::toLogString) - .log(); - final TrieLog trieLog = trieLogFactory.create(localUpdater, blockHeader); - trieLog.freeze(); - return trieLog; - } - - private void persistTrieLog( - final BlockHeader blockHeader, - final Hash worldStateRootHash, - final TrieLog trieLog, - final BonsaiUpdater stateUpdater) { - LOG.atDebug() - .setMessage("Persisting trie log for block hash {} and world state root {}") - .addArgument(blockHeader::toLogString) - .addArgument(worldStateRootHash::toHexString) - .log(); - - stateUpdater - .getTrieLogStorageTransaction() - .put(blockHeader.getHash().toArrayUnsafe(), trieLogFactory.serialize(trieLog)); - } - - @Override - public long getMaxLayersToLoad() { - return maxLayersToLoad; - } - - @Override - public Optional getTrieLogLayer(final Hash blockHash) { - return rootWorldStateStorage.getTrieLog(blockHash).map(trieLogFactory::deserialize); - } - - @Override - public synchronized long subscribe(final TrieLogObserver sub) { - return trieLogObservers.subscribe(sub); - } - - @Override - public synchronized void unsubscribe(final long id) { - trieLogObservers.unsubscribe(id); - } - - private TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { - // if we have a TrieLogService from pluginContext, use it. - var trieLogServicez = - Optional.ofNullable(pluginContext) - .flatMap(context -> context.getService(TrieLogService.class)); - - if (trieLogServicez.isPresent()) { - var trieLogService = trieLogServicez.get(); - // push the TrieLogProvider into the TrieLogService - trieLogService.configureTrieLogProvider(getTrieLogProvider()); - - // configure plugin observers: - trieLogService.getObservers().forEach(trieLogObservers::subscribe); - - // return the TrieLogFactory implementation from the TrieLogService - return trieLogService.getTrieLogFactory(); - } else { - // Otherwise default to TrieLogFactoryImpl - return new TrieLogFactoryImpl(); - } - } - - private TrieLogProvider getTrieLogProvider() { - return new TrieLogProvider() { - @Override - public Optional getTrieLogLayer(final Hash blockHash) { - return DefaultTrieLogManager.this.getTrieLogLayer(blockHash); - } - - @Override - public Optional getTrieLogLayer(final long blockNumber) { - return DefaultTrieLogManager.this - .blockchain - .getBlockHeader(blockNumber) - .map(BlockHeader::getHash) - .flatMap(DefaultTrieLogManager.this::getTrieLogLayer); - } - - @Override - public List getTrieLogsByRange( - final long fromBlockNumber, final long toBlockNumber) { - return rangeAsStream(fromBlockNumber, toBlockNumber) - .map(blockchain::getBlockHeader) - .map( - headerOpt -> - headerOpt.flatMap( - header -> - DefaultTrieLogManager.this - .getTrieLogLayer(header.getBlockHash()) - .map( - layer -> - new TrieLogRangeTuple( - header.getBlockHash(), header.getNumber(), layer)))) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); - } - - Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { - if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { - throw new IllegalArgumentException("Requested Range too large"); - } - long left = Math.min(fromBlockNumber, toBlockNumber); - long right = Math.max(fromBlockNumber, toBlockNumber); - return LongStream.range(left, right).boxed(); - } - }; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java index 4a7b1d8c55d..cabd5a2d300 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java @@ -16,27 +16,188 @@ package org.hyperledger.besu.ethereum.bonsai.trielog; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.TrieLogService; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; +import org.hyperledger.besu.util.Subscribers; +import java.util.List; import java.util.Optional; +import java.util.stream.LongStream; +import java.util.stream.Stream; -public interface TrieLogManager { +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; - void saveTrieLog( +public class TrieLogManager { + private static final Logger LOG = LoggerFactory.getLogger(TrieLogManager.class); + public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs + protected final Blockchain blockchain; + protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; + + protected final long maxLayersToLoad; + protected final Subscribers trieLogObservers = Subscribers.create(); + + protected final TrieLogFactory trieLogFactory; + + public TrieLogManager( + final Blockchain blockchain, + final BonsaiWorldStateKeyValueStorage worldStateStorage, + final long maxLayersToLoad, + final BesuContext pluginContext) { + this.blockchain = blockchain; + this.rootWorldStateStorage = worldStateStorage; + this.maxLayersToLoad = maxLayersToLoad; + this.trieLogFactory = setupTrieLogFactory(pluginContext); + } + + public synchronized void saveTrieLog( final BonsaiWorldStateUpdateAccumulator localUpdater, final Hash forWorldStateRootHash, final BlockHeader forBlockHeader, - final BonsaiWorldState forWorldState); + final BonsaiWorldState forWorldState) { + // do not overwrite a trielog layer that already exists in the database. + // if it's only in memory we need to save it + // for example, in case of reorg we don't replace a trielog layer + if (rootWorldStateStorage.getTrieLog(forBlockHeader.getHash()).isEmpty()) { + final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = + forWorldState.getWorldStateStorage().updater(); + boolean success = false; + try { + final TrieLog trieLog = prepareTrieLog(forBlockHeader, localUpdater); + persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater); + + // notify trie log added observers, synchronously + trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); + + success = true; + } finally { + if (success) { + stateUpdater.commit(); + } else { + stateUpdater.rollback(); + } + } + } + } + + private TrieLog prepareTrieLog( + final BlockHeader blockHeader, final BonsaiWorldStateUpdateAccumulator localUpdater) { + LOG.atDebug() + .setMessage("Adding layered world state for {}") + .addArgument(blockHeader::toLogString) + .log(); + final TrieLog trieLog = trieLogFactory.create(localUpdater, blockHeader); + trieLog.freeze(); + return trieLog; + } + + private void persistTrieLog( + final BlockHeader blockHeader, + final Hash worldStateRootHash, + final TrieLog trieLog, + final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater) { + LOG.atDebug() + .setMessage("Persisting trie log for block hash {} and world state root {}") + .addArgument(blockHeader::toLogString) + .addArgument(worldStateRootHash::toHexString) + .log(); + + stateUpdater + .getTrieLogStorageTransaction() + .put(blockHeader.getHash().toArrayUnsafe(), trieLogFactory.serialize(trieLog)); + } + + public long getMaxLayersToLoad() { + return maxLayersToLoad; + } + + public Optional getTrieLogLayer(final Hash blockHash) { + return rootWorldStateStorage.getTrieLog(blockHash).map(trieLogFactory::deserialize); + } + + public synchronized long subscribe(final TrieLogEvent.TrieLogObserver sub) { + return trieLogObservers.subscribe(sub); + } + + public synchronized void unsubscribe(final long id) { + trieLogObservers.unsubscribe(id); + } + + private TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { + // if we have a TrieLogService from pluginContext, use it. + var trieLogServicez = + Optional.ofNullable(pluginContext) + .flatMap(context -> context.getService(TrieLogService.class)); + + if (trieLogServicez.isPresent()) { + var trieLogService = trieLogServicez.get(); + // push the TrieLogProvider into the TrieLogService + trieLogService.configureTrieLogProvider(getTrieLogProvider()); + + // configure plugin observers: + trieLogService.getObservers().forEach(trieLogObservers::subscribe); + + // return the TrieLogFactory implementation from the TrieLogService + return trieLogService.getTrieLogFactory(); + } else { + // Otherwise default to TrieLogFactoryImpl + return new TrieLogFactoryImpl(); + } + } - long getMaxLayersToLoad(); + private TrieLogProvider getTrieLogProvider() { + return new TrieLogProvider() { + @Override + public Optional getTrieLogLayer(final Hash blockHash) { + return TrieLogManager.this.getTrieLogLayer(blockHash); + } - Optional getTrieLogLayer(final Hash blockHash); + @Override + public Optional getTrieLogLayer(final long blockNumber) { + return TrieLogManager.this + .blockchain + .getBlockHeader(blockNumber) + .map(BlockHeader::getHash) + .flatMap(TrieLogManager.this::getTrieLogLayer); + } - long subscribe(final TrieLogEvent.TrieLogObserver sub); + @Override + public List getTrieLogsByRange( + final long fromBlockNumber, final long toBlockNumber) { + return rangeAsStream(fromBlockNumber, toBlockNumber) + .map(blockchain::getBlockHeader) + .map( + headerOpt -> + headerOpt.flatMap( + header -> + TrieLogManager.this + .getTrieLogLayer(header.getBlockHash()) + .map( + layer -> + new TrieLogRangeTuple( + header.getBlockHash(), header.getNumber(), layer)))) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + } - void unsubscribe(final long id); + Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { + if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { + throw new IllegalArgumentException("Requested Range too large"); + } + long left = Math.min(fromBlockNumber, toBlockNumber); + long right = Math.max(fromBlockNumber, toBlockNumber); + return LongStream.range(left, right).boxed(); + } + }; + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index a4b5012a80d..0c911d33556 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -53,8 +53,7 @@ public class TrieLogManagerTests { @BeforeEach public void setup() { - trieLogManager = - new DefaultTrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); + trieLogManager = new TrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); } @Test diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java index 972a2d307c5..9fbc60443ef 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogAddedEvent; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; @@ -32,8 +31,6 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; -import org.hyperledger.besu.util.Subscribers; import java.util.Map; import java.util.Optional; @@ -185,10 +182,13 @@ public Optional getHeadWorldState( public void reset() {} } - static class NoOpTrieLogManager implements TrieLogManager { - private final Subscribers trieLogObservers = Subscribers.create(); - private final TrieLogFactory trieLogFactory = new TrieLogFactoryImpl(); + static class NoOpTrieLogManager extends TrieLogManager { + public NoOpTrieLogManager() { + super(null, null, 0, null); + } + + @SuppressWarnings("UnsynchronizedOverridesSynchronized") @Override public void saveTrieLog( final BonsaiWorldStateUpdateAccumulator localUpdater, @@ -206,7 +206,7 @@ public long getMaxLayersToLoad() { } @Override - public Optional getTrieLogLayer(final Hash blockHash) { + public Optional getTrieLogLayer(final Hash blockHash) { return Optional.empty(); } From 840b3c82865593f3198e0d30eb527bdc43059d65 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 24 Oct 2023 15:24:31 +1000 Subject: [PATCH 05/11] Add trie log pruning after TrieLogManager.persist Feature toggled by --Xtrie-log-pruning-enabled Each time a trie log is persisted, the current trie log is cached and the pruner is run against the oldest entries in the cache. This makes no attempt to manage the backlog of old trie logs, it will only prune what has been added to the cache, i.e. trie logs that have been added since the feature was enabled. Pruner limit exists in case of exceptional circumstances, but we should only ever be pruning all the forks for a single block number during each prune execution. Signed-off-by: Simon Dudley --- .../org/hyperledger/besu/cli/BesuCommand.java | 9 ++ .../cli/ConfigurationOverviewBuilder.java | 15 ++ .../unstable/TrieLogPruningOptions.java | 64 ++++++++ .../controller/BesuControllerBuilder.java | 33 +++- .../besu/cli/CommandTestAbstract.java | 1 + .../controller/BesuControllerBuilderTest.java | 6 +- .../bonsai/BonsaiWorldStateProvider.java | 16 +- .../BonsaiWorldStateKeyValueStorage.java | 4 + .../bonsai/trielog/TrieLogManager.java | 8 +- .../bonsai/trielog/TrieLogPruner.java | 148 ++++++++++++++++++ .../trielog/TrieLogPrunerConfiguration.java | 28 ++++ .../core/InMemoryKeyValueStorageProvider.java | 4 +- .../BlockImportExceptionHandlingTest.java | 8 +- .../bonsai/AbstractIsolationTests.java | 4 +- .../bonsai/BonsaiWorldStateArchiveTest.java | 4 +- .../besu/ethereum/bonsai/LogRollingTests.java | 11 +- .../besu/ethereum/bonsai/RollingImport.java | 8 +- .../bonsai/trielog/TrieLogManagerTests.java | 8 +- .../bonsai/trielog/TrieLogPrunerTest.java | 98 ++++++++++++ .../BonsaiReferenceTestWorldState.java | 2 +- 20 files changed, 460 insertions(+), 19 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TrieLogPruningOptions.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerConfiguration.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index d7ce4dfa539..f3410b37163 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -73,6 +73,7 @@ import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions; import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; +import org.hyperledger.besu.cli.options.unstable.TrieLogPruningOptions; import org.hyperledger.besu.cli.presynctasks.PreSynchronizationTaskRunner; import org.hyperledger.besu.cli.presynctasks.PrivateDatabaseMigrationPreSyncTask; import org.hyperledger.besu.cli.subcommands.PasswordSubCommand; @@ -295,6 +296,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final EvmOptions unstableEvmOptions = EvmOptions.create(); private final IpcOptions unstableIpcOptions = IpcOptions.create(); private final ChainPruningOptions unstableChainPruningOptions = ChainPruningOptions.create(); + private final TrieLogPruningOptions unstableTrieLogPruningOptions = + TrieLogPruningOptions.create(); // stable CLI options private final DataStorageOptions dataStorageOptions = DataStorageOptions.create(); @@ -1591,6 +1594,7 @@ private void handleUnstableOptions() { .put("EVM Options", unstableEvmOptions) .put("IPC Options", unstableIpcOptions) .put("Chain Data Pruning Options", unstableChainPruningOptions) + .put("Trie Log Pruning Options", unstableTrieLogPruningOptions) .build(); UnstableOptionsSubCommand.createUnstableOptions(commandLine, unstableOptions); @@ -2296,6 +2300,7 @@ public BesuControllerBuilder getControllerBuilder() { .maxRemotelyInitiatedPeers(maxRemoteInitiatedPeers) .randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority) .chainPruningConfiguration(unstableChainPruningOptions.toDomainObject()) + .trieLogPrunerConfiguration(unstableTrieLogPruningOptions.toDomainObject()) .cacheLastBlocks(numberOfblocksToCache); } @@ -3553,6 +3558,10 @@ private String generateConfigurationOverview() { builder.setHighSpecEnabled(); } + if (unstableTrieLogPruningOptions.getTrieLogPruningEnabled()) { + builder.setTrieLogPruningEnabled(); + } + builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()); builder.setPluginContext(besuComponent.getBesuPluginContext()); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java index 508b195a8eb..5e178710dee 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java @@ -49,6 +49,7 @@ public class ConfigurationOverviewBuilder { private Collection engineApis; private String engineJwtFilePath; private boolean isHighSpec = false; + private boolean isTrieLogPruning = false; private TransactionPoolConfiguration.Implementation txPoolImplementation; private Map environment; private BesuPluginContextImpl besuPluginContext; @@ -169,6 +170,16 @@ public ConfigurationOverviewBuilder setHighSpecEnabled() { return this; } + /** + * Sets high spec enabled. + * + * @return the builder + */ + public ConfigurationOverviewBuilder setTrieLogPruningEnabled() { + isTrieLogPruning = true; + return this; + } + /** * Sets the txpool implementation in use. * @@ -258,6 +269,10 @@ public String build() { lines.add("Using " + txPoolImplementation + " transaction pool implementation"); + if (isTrieLogPruning) { + lines.add("Trie log pruning enabled"); + } + lines.add(""); lines.add("Host:"); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TrieLogPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TrieLogPruningOptions.java new file mode 100644 index 00000000000..a73272578ab --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TrieLogPruningOptions.java @@ -0,0 +1,64 @@ +/* + * 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.cli.options.unstable; + +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPrunerConfiguration; + +import java.util.Arrays; +import java.util.List; + +import picocli.CommandLine; + +/** The trie log pruning CLI options. */ +public class TrieLogPruningOptions implements CLIOptions { + + private static final String TRIE_LOG_PRUNING_ENABLED_FLAG = "--Xtrie-log-pruning-enabled"; + + @CommandLine.Option( + hidden = true, + names = {TRIE_LOG_PRUNING_ENABLED_FLAG}, + description = "Enable trie log pruning (default: ${DEFAULT-VALUE})") + private final Boolean trieLogPruningEnabled = Boolean.FALSE; + + /** + * Create trie log pruning options. + * + * @return the trie log pruning options + */ + public static TrieLogPruningOptions create() { + return new TrieLogPruningOptions(); + } + + /** + * Gets trie log pruning enabled. + * + * @return the trie log pruning enabled + */ + public Boolean getTrieLogPruningEnabled() { + return trieLogPruningEnabled; + } + + @Override + public TrieLogPrunerConfiguration toDomainObject() { + return new TrieLogPrunerConfiguration(trieLogPruningEnabled); + } + + @Override + public List getCLIOptions() { + return Arrays.asList(TRIE_LOG_PRUNING_ENABLED_FLAG, trieLogPruningEnabled.toString()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 802be1215b5..4593203fa77 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -34,6 +34,8 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPrunerConfiguration; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.ChainDataPruner; @@ -180,6 +182,9 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides /** The Chain pruner configuration. */ protected ChainPrunerConfiguration chainPrunerConfiguration = ChainPrunerConfiguration.DEFAULT; + protected TrieLogPrunerConfiguration trieLogPrunerConfiguration = + TrieLogPrunerConfiguration.DEFAULT; + private NetworkingConfiguration networkingConfiguration; private Boolean randomPeerPriority; private Optional transactionSelectorFactory = Optional.empty(); @@ -508,7 +513,19 @@ public BesuControllerBuilder chainPruningConfiguration( } /** - * Chain pruning configuration besu controller builder. + * Trie log pruning configuration besu controller builder. + * + * @param trieLogPrunerConfiguration the trie log pruner configuration + * @return the besu controller builder + */ + public BesuControllerBuilder trieLogPrunerConfiguration( + final TrieLogPrunerConfiguration trieLogPrunerConfiguration) { + this.trieLogPrunerConfiguration = trieLogPrunerConfiguration; + return this; + } + + /** + * Cache last blocks configuration besu controller builder. * * @param numberOfBlocksToCache the number of blocks to cache * @return the besu controller builder @@ -613,8 +630,14 @@ public BesuController build() { .map(BesuComponent::getCachedMerkleTrieLoader) .orElseGet(() -> new CachedMerkleTrieLoader(metricsSystem)); + final TrieLogPruner trieLogPruner = + trieLogPrunerConfiguration.getTrieLogPruningEnabled() + ? new TrieLogPruner((BonsaiWorldStateKeyValueStorage) worldStateStorage, blockchain) + : TrieLogPruner.noOpTrieLogPruner(); + final WorldStateArchive worldStateArchive = - createWorldStateArchive(worldStateStorage, blockchain, cachedMerkleTrieLoader); + createWorldStateArchive( + worldStateStorage, blockchain, cachedMerkleTrieLoader, trieLogPruner); if (blockchain.getChainHeadBlockNumber() < 1) { genesisState.writeStateTo(worldStateArchive.getMutable()); @@ -1070,7 +1093,8 @@ private Optional createSnapProtocolManager( WorldStateArchive createWorldStateArchive( final WorldStateStorage worldStateStorage, final Blockchain blockchain, - final CachedMerkleTrieLoader cachedMerkleTrieLoader) { + final CachedMerkleTrieLoader cachedMerkleTrieLoader, + final TrieLogPruner trieLogPruner) { switch (dataStorageConfiguration.getDataStorageFormat()) { case BONSAI: return new BonsaiWorldStateProvider( @@ -1079,7 +1103,8 @@ WorldStateArchive createWorldStateArchive( Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()), cachedMerkleTrieLoader, metricsSystem, - besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null)); + besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null), + trieLogPruner); case FOREST: default: diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 1c991d64935..b83260d0b37 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -236,6 +236,7 @@ public void initMocks() throws Exception { when(mockControllerBuilder.randomPeerPriority(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.chainPruningConfiguration(any())).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.trieLogPrunerConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.lowerBoundPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxRemotelyInitiatedPeers(anyInt())) diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java index c7c6466e602..c7544d67d73 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.MiningParameters; @@ -164,7 +165,10 @@ public void shouldDisablePruningIfBonsaiIsEnabled() { doReturn(worldStateArchive) .when(besuControllerBuilder) .createWorldStateArchive( - any(WorldStateStorage.class), any(Blockchain.class), any(CachedMerkleTrieLoader.class)); + any(WorldStateStorage.class), + any(Blockchain.class), + any(CachedMerkleTrieLoader.class), + any(TrieLogPruner.class)); doReturn(mockWorldState).when(worldStateArchive).getMutable(); when(storageProvider.createWorldStateStorage(DataStorageFormat.BONSAI)) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index bcb7f9f65fe..09bf8f0c13a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -73,7 +74,8 @@ public BonsaiWorldStateProvider( final Blockchain blockchain, final CachedMerkleTrieLoader cachedMerkleTrieLoader, final ObservableMetricsSystem metricsSystem, - final BesuContext pluginContext) { + final BesuContext pluginContext, + final TrieLogPruner trieLogPruner) { this( (BonsaiWorldStateKeyValueStorage) provider.createWorldStateStorage(DataStorageFormat.BONSAI), @@ -81,7 +83,8 @@ public BonsaiWorldStateProvider( Optional.empty(), cachedMerkleTrieLoader, metricsSystem, - pluginContext); + pluginContext, + trieLogPruner); } public BonsaiWorldStateProvider( @@ -90,14 +93,19 @@ public BonsaiWorldStateProvider( final Optional maxLayersToLoad, final CachedMerkleTrieLoader cachedMerkleTrieLoader, final ObservableMetricsSystem metricsSystem, - final BesuContext pluginContext) { + final BesuContext pluginContext, + final TrieLogPruner trieLogPruner) { this.cachedWorldStorageManager = new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); // TODO: de-dup constructors this.trieLogManager = new TrieLogManager( - blockchain, worldStateStorage, maxLayersToLoad.orElse(RETAINED_LAYERS), pluginContext); + blockchain, + worldStateStorage, + maxLayersToLoad.orElse(RETAINED_LAYERS), + pluginContext, + trieLogPruner); this.blockchain = blockchain; this.worldStateStorage = worldStateStorage; this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; 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 9756b280e45..7860e514fd7 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 @@ -335,6 +335,10 @@ public long prune(final Predicate inUseCheck) { throw new RuntimeException("Bonsai Tries do not work with pruning."); } + public boolean pruneTrieLog(final byte[] blockHashBytes) { + return trieLogStorage.tryDelete(blockHashBytes); + } + @Override public long addNodeAddedListener(final NodesAddedListener listener) { throw new RuntimeException("addNodeAddedListener not available"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java index cabd5a2d300..60aff6ec2c3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java @@ -47,16 +47,19 @@ public class TrieLogManager { protected final Subscribers trieLogObservers = Subscribers.create(); protected final TrieLogFactory trieLogFactory; + private final TrieLogPruner trieLogPruner; public TrieLogManager( final Blockchain blockchain, final BonsaiWorldStateKeyValueStorage worldStateStorage, final long maxLayersToLoad, - final BesuContext pluginContext) { + final BesuContext pluginContext, + final TrieLogPruner trieLogPruner) { this.blockchain = blockchain; this.rootWorldStateStorage = worldStateStorage; this.maxLayersToLoad = maxLayersToLoad; this.trieLogFactory = setupTrieLogFactory(pluginContext); + this.trieLogPruner = trieLogPruner; } public synchronized void saveTrieLog( @@ -82,6 +85,9 @@ public synchronized void saveTrieLog( } finally { if (success) { stateUpdater.commit(); + trieLogPruner.cacheForLaterPruning( + forBlockHeader.getNumber(), forBlockHeader.getBlockHash().toArrayUnsafe()); + trieLogPruner.pruneFromCache(); } else { stateUpdater.rollback(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java new file mode 100644 index 00000000000..b74614160f3 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java @@ -0,0 +1,148 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.trielog; + +/* + * Copyright contributors to Hyperledger Besu. + * + * 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 + */ + +import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; +import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.chain.Blockchain; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Multimap; +import com.google.common.collect.TreeMultimap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TrieLogPruner { + + private static final Logger LOG = LoggerFactory.getLogger(TrieLogPruner.class); + + private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; + private final Blockchain blockchain; + private final long numBlocksToRetain; + + // This is just in case, should only be pruning 1 block + its forks each run + private static final int DEFAULT_PRUNING_LIMIT = 1000; + private final int pruningLimit; + + private static final Multimap knownTrieLogKeysByDescendingBlockNumber = + TreeMultimap.create(Comparator.reverseOrder(), Comparator.comparingInt(Arrays::hashCode)); + + public TrieLogPruner( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, final Blockchain blockchain) { + this( + rootWorldStateStorage, + blockchain, + CachedWorldStorageManager.RETAINED_LAYERS, + DEFAULT_PRUNING_LIMIT); + } + + @VisibleForTesting + TrieLogPruner( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final Blockchain blockchain, + final long numBlocksToRetain, + final int pruningLimit) { + this.rootWorldStateStorage = rootWorldStateStorage; + this.blockchain = blockchain; + this.numBlocksToRetain = numBlocksToRetain; + this.pruningLimit = pruningLimit; + } + + void cacheForLaterPruning(final long blockNumber, final byte[] trieLogKey) { + knownTrieLogKeysByDescendingBlockNumber.put(blockNumber, trieLogKey); + } + + void pruneFromCache() { + final long retainAboveThisBlock = blockchain.getChainHeadBlockNumber() - numBlocksToRetain; + LOG.atDebug() + .setMessage("(chainHeadNumber: {} - numBlocksToRetain: {}) = retainAboveThisBlock: {}") + .addArgument(blockchain.getChainHeadBlockNumber()) + .addArgument(numBlocksToRetain) + .addArgument(retainAboveThisBlock) + .log(); + + final var toPrune = + knownTrieLogKeysByDescendingBlockNumber.asMap().entrySet().stream() + .dropWhile((e) -> e.getKey() > retainAboveThisBlock) + .limit(pruningLimit); + + final List blockNumbersToRemove = new ArrayList<>(); + + final AtomicInteger count = new AtomicInteger(); + toPrune.forEach( + (e) -> { + for (byte[] trieLogKey : e.getValue()) { + rootWorldStateStorage.pruneTrieLog(trieLogKey); + count.getAndIncrement(); + } + blockNumbersToRemove.add(e.getKey()); + }); + + blockNumbersToRemove.forEach(knownTrieLogKeysByDescendingBlockNumber::removeAll); + LOG.atTrace() + .setMessage("pruned {} trie logs for blocks {}") + .addArgument(count) + .addArgument(blockNumbersToRemove) + .log(); + LOG.atDebug() + .setMessage("pruned {} trie logs from {} blocks") + .addArgument(count) + .addArgument(blockNumbersToRemove.size()) + .log(); + } + + public static TrieLogPruner noOpTrieLogPruner() { + return new NoOpTrieLogPruner(null, null); + } + + public static class NoOpTrieLogPruner extends TrieLogPruner { + private NoOpTrieLogPruner( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, final Blockchain blockchain) { + super(rootWorldStateStorage, blockchain); + } + + @Override + void cacheForLaterPruning(final long blockNumber, final byte[] trieLogKey) { + // no-op + } + + @Override + void pruneFromCache() { + // no-op + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerConfiguration.java new file mode 100644 index 00000000000..2d1cf4c404c --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerConfiguration.java @@ -0,0 +1,28 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.trielog; + +public class TrieLogPrunerConfiguration { + public static final TrieLogPrunerConfiguration DEFAULT = new TrieLogPrunerConfiguration(false); + private final boolean enabled; + + public TrieLogPrunerConfiguration(final boolean enabled) { + this.enabled = enabled; + } + + public boolean getTrieLogPruningEnabled() { + return enabled; + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index 3687643c6d9..68b4608754c 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -90,7 +91,8 @@ public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem(), - null); + null, + TrieLogPruner.noOpTrieLogPruner()); } public static MutableWorldState createInMemoryWorldState() { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index b936ed28fa3..a2f4627ddd6 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -89,7 +90,12 @@ public class BlockImportExceptionHandlingTest { // do we need to also test with a DefaultWorldStateArchive? spy( new BonsaiWorldStateProvider( - storageProvider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null)); + storageProvider, + blockchain, + cachedMerkleTrieLoader, + new NoOpMetricsSystem(), + null, + TrieLogPruner.noOpTrieLogPruner())); private final BonsaiWorldState persisted = spy( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java index da12289afa8..72b7efc14d6 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -147,7 +148,8 @@ public void createStorage() { Optional.of(16L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), new NoOpMetricsSystem(), - null); + null, + TrieLogPruner.noOpTrieLogPruner()); var ws = archive.getMutable(); genesisState.writeStateTo(ws); protocolContext = new ProtocolContext(blockchain, archive, null, Optional.empty()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java index 0721522ffb1..48a28e9c212 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java @@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -121,7 +122,8 @@ public void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { Optional.of(512L), new CachedMerkleTrieLoader(new NoOpMetricsSystem()), new NoOpMetricsSystem(), - null); + null, + TrieLogPruner.noOpTrieLogPruner()); final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); when(blockchain.getChainHeadHeader()).thenReturn(chainHead); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java index 0e2fb0c0d1a..4f2e036e559 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -132,7 +133,12 @@ void createStorage() { new CachedMerkleTrieLoader(new NoOpMetricsSystem()); archive = new BonsaiWorldStateProvider( - provider, blockchain, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null); + provider, + blockchain, + cachedMerkleTrieLoader, + new NoOpMetricsSystem(), + null, + TrieLogPruner.noOpTrieLogPruner()); accountStorage = provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); codeStorage = provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE); @@ -152,7 +158,8 @@ void createStorage() { blockchain, secondOptimizedMerkleTrieLoader, new NoOpMetricsSystem(), - null); + null, + TrieLogPruner.noOpTrieLogPruner()); secondAccountStorage = secondProvider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); secondCodeStorage = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java index 46e5b6af9f8..0db3e8e67d1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; @@ -55,7 +56,12 @@ public static void main(final String[] arg) throws IOException { new CachedMerkleTrieLoader(new NoOpMetricsSystem()); final BonsaiWorldStateProvider archive = new BonsaiWorldStateProvider( - provider, null, cachedMerkleTrieLoader, new NoOpMetricsSystem(), null); + provider, + null, + cachedMerkleTrieLoader, + new NoOpMetricsSystem(), + null, + TrieLogPruner.noOpTrieLogPruner()); final BonsaiWorldState bonsaiState = new BonsaiWorldState( archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java index 0c911d33556..64bf1b1bcc1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java @@ -53,7 +53,13 @@ public class TrieLogManagerTests { @BeforeEach public void setup() { - trieLogManager = new TrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); + trieLogManager = + new TrieLogManager( + blockchain, + bonsaiWorldStateKeyValueStorage, + 512, + null, + TrieLogPruner.noOpTrieLogPruner()); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java new file mode 100644 index 00000000000..e16b08db7b5 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java @@ -0,0 +1,98 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.trielog; + +import static org.mockito.Mockito.times; + +import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.chain.Blockchain; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +public class TrieLogPrunerTest { + + private BonsaiWorldStateKeyValueStorage rootWorldStateStorage; + private Blockchain blockchain; + + @BeforeEach + public void setup() { + rootWorldStateStorage = Mockito.mock(BonsaiWorldStateKeyValueStorage.class); + blockchain = Mockito.mock(Blockchain.class); + } + + @SuppressWarnings("BannedMethod") + @Test + public void trieLogs_pruned_in_reverse_order_within_pruning_limit() { + Configurator.setLevel(LogManager.getLogger(TrieLogPruner.class).getName(), Level.TRACE); + + // Given + + // pruning window is below numBlocksToRetain and inside the pruningLimit offset. + final long blocksToRetain = 3; + final int pruningLimit = 2; + TrieLogPruner trieLogPruner = + new TrieLogPruner(rootWorldStateStorage, blockchain, blocksToRetain, pruningLimit); + + final byte[] key0 = new byte[] {1, 2, 3}; // older block outside the prune window + final byte[] key1 = new byte[] {1, 2, 3}; // block inside the prune window + final byte[] key2 = new byte[] {4, 5, 6}; // same block (fork) + final byte[] key3 = new byte[] {7, 8, 9}; // different block inside the prune window + final byte[] key4 = new byte[] {10, 11, 12}; // retained block + final byte[] key5 = new byte[] {13, 14, 15}; // different retained block + final byte[] key6 = new byte[] {7, 8, 9}; // another retained block + final long block0 = 1000L; + final long block1 = 1001L; + final long block2 = 1002L; + final long block3 = 1003L; + final long block4 = 1004L; + final long block5 = 1005L; + + trieLogPruner.cacheForLaterPruning(block0, key0); // older block outside prune window + trieLogPruner.cacheForLaterPruning(block1, key1); // block inside the prune window + trieLogPruner.cacheForLaterPruning(block1, key2); // same block number (fork) + trieLogPruner.cacheForLaterPruning(block2, key3); // different block inside prune window + trieLogPruner.cacheForLaterPruning(block3, key4); // retained block + trieLogPruner.cacheForLaterPruning(block4, key5); // different retained block + trieLogPruner.cacheForLaterPruning(block5, key6); // another retained block + + Mockito.when(blockchain.getChainHeadBlockNumber()).thenReturn(block5); + + // When + trieLogPruner.pruneFromCache(); + + // Then + InOrder inOrder = Mockito.inOrder(rootWorldStateStorage); + inOrder.verify(rootWorldStateStorage, times(1)).pruneTrieLog(key3); + inOrder.verify(rootWorldStateStorage, times(1)).pruneTrieLog(key1); + inOrder.verify(rootWorldStateStorage, times(1)).pruneTrieLog(key2); + + // Subsequent run should add one more block, then prune two oldest remaining keys + long block6 = 1006L; + trieLogPruner.cacheForLaterPruning(block6, new byte[] {1, 2, 3}); + Mockito.when(blockchain.getChainHeadBlockNumber()).thenReturn(block6); + + trieLogPruner.pruneFromCache(); + + inOrder.verify(rootWorldStateStorage, times(1)).pruneTrieLog(key4); + inOrder.verify(rootWorldStateStorage, times(1)).pruneTrieLog(key0); + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java index 9fbc60443ef..2660a29291a 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java @@ -185,7 +185,7 @@ public void reset() {} static class NoOpTrieLogManager extends TrieLogManager { public NoOpTrieLogManager() { - super(null, null, 0, null); + super(null, null, 0, null, null); } @SuppressWarnings("UnsynchronizedOverridesSynchronized") From ec6f52b55a1683f22c6f39af2b574d6e256e6533 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Tue, 24 Oct 2023 16:18:38 +1000 Subject: [PATCH 06/11] Remove a BonsaiWorldStateProvider constructor Was only used for supporting test code and can instead reuse static factory from InMemoryKeyValueStorageProvider Signed-off-by: Simon Dudley --- .../bonsai/BonsaiWorldStateProvider.java | 20 ---------------- .../core/InMemoryKeyValueStorageProvider.java | 7 +++++- .../BlockImportExceptionHandlingTest.java | 14 +---------- .../besu/ethereum/bonsai/LogRollingTests.java | 23 ++----------------- .../besu/ethereum/bonsai/RollingImport.java | 12 +--------- 5 files changed, 10 insertions(+), 66 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java index 09bf8f0c13a..d01193e4a8e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java @@ -33,10 +33,8 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProof; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -69,24 +67,6 @@ public class BonsaiWorldStateProvider implements WorldStateArchive { private final BonsaiWorldStateKeyValueStorage worldStateStorage; private final CachedMerkleTrieLoader cachedMerkleTrieLoader; - public BonsaiWorldStateProvider( - final StorageProvider provider, - final Blockchain blockchain, - final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final ObservableMetricsSystem metricsSystem, - final BesuContext pluginContext, - final TrieLogPruner trieLogPruner) { - this( - (BonsaiWorldStateKeyValueStorage) - provider.createWorldStateStorage(DataStorageFormat.BONSAI), - blockchain, - Optional.empty(), - cachedMerkleTrieLoader, - metricsSystem, - pluginContext, - trieLogPruner); - } - public BonsaiWorldStateProvider( final BonsaiWorldStateKeyValueStorage worldStateStorage, final Blockchain blockchain, diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index 68b4608754c..46e985ea402 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; @@ -36,6 +37,8 @@ import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; +import java.util.Optional; + public class InMemoryKeyValueStorageProvider extends KeyValueStorageProvider { public InMemoryKeyValueStorageProvider() { @@ -87,8 +90,10 @@ public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem()); return new BonsaiWorldStateProvider( - inMemoryKeyValueStorageProvider, + (BonsaiWorldStateKeyValueStorage) + inMemoryKeyValueStorageProvider.createWorldStateStorage(DataStorageFormat.BONSAI), blockchain, + Optional.empty(), cachedMerkleTrieLoader, new NoOpMetricsSystem(), null, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index a2f4627ddd6..9f1e30ff3ba 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -24,9 +24,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -83,19 +81,10 @@ public class BlockImportExceptionHandlingTest { private final WorldStateStorage worldStateStorage = new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - private CachedMerkleTrieLoader cachedMerkleTrieLoader; - private final WorldStateArchive worldStateArchive = // contains a BonsaiWorldState which we need to spy on. // do we need to also test with a DefaultWorldStateArchive? - spy( - new BonsaiWorldStateProvider( - storageProvider, - blockchain, - cachedMerkleTrieLoader, - new NoOpMetricsSystem(), - null, - TrieLogPruner.noOpTrieLogPruner())); + spy(InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(blockchain)); private final BonsaiWorldState persisted = spy( @@ -115,7 +104,6 @@ public void setup() { mainnetBlockValidator = new MainnetBlockValidator( blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); - cachedMerkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem()); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java index 4f2e036e559..054e1b88cc7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java @@ -22,11 +22,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -129,16 +127,7 @@ class LogRollingTests { @BeforeEach void createStorage() { provider = new InMemoryKeyValueStorageProvider(); - final CachedMerkleTrieLoader cachedMerkleTrieLoader = - new CachedMerkleTrieLoader(new NoOpMetricsSystem()); - archive = - new BonsaiWorldStateProvider( - provider, - blockchain, - cachedMerkleTrieLoader, - new NoOpMetricsSystem(), - null, - TrieLogPruner.noOpTrieLogPruner()); + archive = InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(blockchain); accountStorage = provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); codeStorage = provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.CODE_STORAGE); @@ -150,16 +139,8 @@ void createStorage() { provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); secondProvider = new InMemoryKeyValueStorageProvider(); - final CachedMerkleTrieLoader secondOptimizedMerkleTrieLoader = - new CachedMerkleTrieLoader(new NoOpMetricsSystem()); secondArchive = - new BonsaiWorldStateProvider( - secondProvider, - blockchain, - secondOptimizedMerkleTrieLoader, - new NoOpMetricsSystem(), - null, - TrieLogPruner.noOpTrieLogPruner()); + InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(blockchain); secondAccountStorage = secondProvider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE); secondCodeStorage = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java index 0db3e8e67d1..397ebf1f9ab 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java @@ -22,11 +22,9 @@ import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; @@ -52,16 +50,8 @@ public static void main(final String[] arg) throws IOException { new RollingFileReader((i, c) -> Path.of(String.format(arg[0] + "-%04d.rdat", i)), false); final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider(); - final CachedMerkleTrieLoader cachedMerkleTrieLoader = - new CachedMerkleTrieLoader(new NoOpMetricsSystem()); final BonsaiWorldStateProvider archive = - new BonsaiWorldStateProvider( - provider, - null, - cachedMerkleTrieLoader, - new NoOpMetricsSystem(), - null, - TrieLogPruner.noOpTrieLogPruner()); + InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(null); final BonsaiWorldState bonsaiState = new BonsaiWorldState( archive, new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem())); From 164c77f5d710582afe4334e4e7e966754fd9d9b5 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 25 Oct 2023 14:15:43 +1000 Subject: [PATCH 07/11] javadoc Signed-off-by: Simon Dudley --- .../org/hyperledger/besu/controller/BesuControllerBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 4593203fa77..0dda38a9414 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -181,7 +181,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides private int maxRemotelyInitiatedPeers; /** The Chain pruner configuration. */ protected ChainPrunerConfiguration chainPrunerConfiguration = ChainPrunerConfiguration.DEFAULT; - + /** The Trie log pruner configuration. */ protected TrieLogPrunerConfiguration trieLogPrunerConfiguration = TrieLogPrunerConfiguration.DEFAULT; From e0ad3792876b9e208109ab203cd7d1f9745cdd95 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 25 Oct 2023 15:09:08 +1000 Subject: [PATCH 08/11] copy paste error Signed-off-by: Simon Dudley --- .../ethereum/bonsai/trielog/TrieLogPruner.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java index b74614160f3..7d87db654d6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java @@ -15,21 +15,6 @@ package org.hyperledger.besu.ethereum.bonsai.trielog; -/* - * Copyright contributors to Hyperledger Besu. - * - * 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 - */ - import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.Blockchain; From af1e314b07c5eda7587072f426ff382e45c9383d Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Wed, 25 Oct 2023 15:56:40 +1000 Subject: [PATCH 09/11] Preload cache with trielogs limited by pruningLimit Inialised once on startup Signed-off-by: Simon Dudley --- .../controller/BesuControllerBuilder.java | 1 + .../BonsaiWorldStateKeyValueStorage.java | 5 +++ .../bonsai/trielog/TrieLogPruner.java | 42 +++++++++++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 0dda38a9414..b1722b793dc 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -634,6 +634,7 @@ public BesuController build() { trieLogPrunerConfiguration.getTrieLogPruningEnabled() ? new TrieLogPruner((BonsaiWorldStateKeyValueStorage) worldStateStorage, blockchain) : TrieLogPruner.noOpTrieLogPruner(); + trieLogPruner.initialise(); final WorldStateArchive worldStateArchive = createWorldStateArchive( 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 7860e514fd7..452bcdfe601 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 @@ -47,6 +47,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -203,6 +204,10 @@ public Optional getTrieLog(final Hash blockHash) { return trieLogStorage.get(blockHash.toArrayUnsafe()); } + public Stream streamTrieLogs(final int limit) { + return trieLogStorage.streamKeys().limit(limit); + } + public Optional getStateTrieNode(final Bytes location) { return composedWorldStateStorage .get(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java index 7d87db654d6..c4be1468430 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java @@ -15,19 +15,25 @@ package org.hyperledger.besu.ethereum.bonsai.trielog; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Multimap; import com.google.common.collect.TreeMultimap; +import org.apache.tuweni.bytes.Bytes32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,14 +41,13 @@ public class TrieLogPruner { private static final Logger LOG = LoggerFactory.getLogger(TrieLogPruner.class); + private static final int DEFAULT_PRUNING_LIMIT = 1000; + private final int pruningLimit; + private final int loadingLimit; private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; private final Blockchain blockchain; private final long numBlocksToRetain; - // This is just in case, should only be pruning 1 block + its forks each run - private static final int DEFAULT_PRUNING_LIMIT = 1000; - private final int pruningLimit; - private static final Multimap knownTrieLogKeysByDescendingBlockNumber = TreeMultimap.create(Comparator.reverseOrder(), Comparator.comparingInt(Arrays::hashCode)); @@ -65,6 +70,30 @@ public TrieLogPruner( this.blockchain = blockchain; this.numBlocksToRetain = numBlocksToRetain; this.pruningLimit = pruningLimit; + this.loadingLimit = pruningLimit; // same as pruningLimit for now + } + + public void initialise() { + preloadCache(); + } + + void preloadCache() { + LOG.atInfo() + .setMessage("Loading first {} trie logs from database...") + .addArgument(loadingLimit) + .log(); + final Stream trieLogs = rootWorldStateStorage.streamTrieLogs(loadingLimit); + final AtomicLong count = new AtomicLong(); + trieLogs.forEach( + hashAsBytes -> { + Hash hash = Hash.wrap(Bytes32.wrap(hashAsBytes)); + final Optional header = blockchain.getBlockHeader(hash); + if (header.isPresent()) { + knownTrieLogKeysByDescendingBlockNumber.put(header.get().getNumber(), hashAsBytes); + count.getAndIncrement(); + } + }); + LOG.atInfo().log("Loaded {} trie logs from database", count); } void cacheForLaterPruning(final long blockNumber, final byte[] trieLogKey) { @@ -120,6 +149,11 @@ private NoOpTrieLogPruner( super(rootWorldStateStorage, blockchain); } + @Override + public void initialise() { + // no-op + } + @Override void cacheForLaterPruning(final long blockNumber, final byte[] trieLogKey) { // no-op From d921ad4edc2c144e6d899f4cf48d2f59b42da5b6 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 26 Oct 2023 10:28:54 +1000 Subject: [PATCH 10/11] Log when adding to prune cache Signed-off-by: Simon Dudley --- .../besu/ethereum/bonsai/trielog/TrieLogPruner.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java index c4be1468430..9b253efa8fc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java @@ -97,6 +97,11 @@ void preloadCache() { } void cacheForLaterPruning(final long blockNumber, final byte[] trieLogKey) { + LOG.atTrace() + .setMessage("caching trie log for later pruning blockNumber {}; trieLogKey (blockHash) {}") + .addArgument(blockNumber) + .addArgument(Bytes32.wrap(trieLogKey).toHexString()) + .log(); knownTrieLogKeysByDescendingBlockNumber.put(blockNumber, trieLogKey); } From d19a691ec0eb142b74ededd6f5c1c48012189331 Mon Sep 17 00:00:00 2001 From: Simon Dudley Date: Thu, 26 Oct 2023 10:30:08 +1000 Subject: [PATCH 11/11] Prune any orphaned blocks included in prune window during load Signed-off-by: Simon Dudley --- .../besu/ethereum/bonsai/trielog/TrieLogPruner.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java index 9b253efa8fc..faca907afcc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java @@ -91,6 +91,9 @@ void preloadCache() { if (header.isPresent()) { knownTrieLogKeysByDescendingBlockNumber.put(header.get().getNumber(), hashAsBytes); count.getAndIncrement(); + } else { + // prune orphaned blocks (sometimes created during block production) + rootWorldStateStorage.pruneTrieLog(hashAsBytes); } }); LOG.atInfo().log("Loaded {} trie logs from database", count);