From fce33c162dded929d4ab4bf3aab0b5c458577621 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 11 Nov 2022 16:47:40 +1000 Subject: [PATCH 01/29] Add chain pruner Signed-off-by: wcgcyx --- .../ethereum/chain/BlockchainStorage.java | 8 ++ .../besu/ethereum/chain/ChainPruner.java | 132 ++++++++++++++++++ .../keyvalue/KeyValueSegmentIdentifier.java | 3 +- ...ueStoragePrefixedKeyBlockchainStorage.java | 20 +++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java index 4f4aa87a23c..c610a3e0d2b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java @@ -72,8 +72,16 @@ interface Updater { void removeBlockHash(long blockNumber); + void removeBlockHeader(final Hash blockHash); + + void removeBlockBody(final Hash blockHash); + + void removeTransactionReceipts(final Hash blockHash); + void removeTransactionLocation(Hash transactionHash); + void removeTotalDifficulty(final Hash blockHash); + void commit(); void rollback(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java new file mode 100644 index 00000000000..64bdfe2ac30 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java @@ -0,0 +1,132 @@ +package org.hyperledger.besu.ethereum.chain; + +import com.google.common.collect.Lists; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Optional; + +public class ChainPruner implements BlockAddedObserver { + + private static final Logger LOG = LoggerFactory.getLogger(ChainPruner.class); + + private static final Bytes PRUNING_MARK_KEY = + Bytes.wrap("blockNumberTail".getBytes(StandardCharsets.UTF_8)); + + private static final Bytes VARIABLES_PREFIX = Bytes.of(1); + private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); + + private final BlockchainStorage blockchainStorage; + private final KeyValueStorage prunerStorage; + private final long blocksToKeep; + + public ChainPruner( + final BlockchainStorage blockchainStorage, + final KeyValueStorage prunerStorage, + final long blocksToKeep) { + this.blockchainStorage = blockchainStorage; + this.prunerStorage = prunerStorage; + this.blocksToKeep = blocksToKeep; + } + + @Override + public void onBlockAdded(final BlockAddedEvent event) { + LOG.debug("New block added event: " + event); + // Get pruning mark + long blockNumber = event.getBlock().getHeader().getNumber(); + Optional maybePruningMark = getPruningMark(); + if (maybePruningMark.isEmpty()) { + // Set initial pruning mark + maybePruningMark = Optional.of(blockNumber); + } + long pruningMark = maybePruningMark.get(); + if (blockNumber < pruningMark) { + // Ignore and warn if block number < pruning mark, this normally indicates the blocksToKeep is too small. + LOG.warn("Block added event: " + event + " has a block number of " + blockNumber + " < pruning mark " + pruningMark); + return; + } + // Append block into fork blocks. + KeyValueStorageTransaction tx = prunerStorage.startTransaction(); + Collection forkBlocks = getForkBlocks(blockNumber); + forkBlocks.add(event.getBlock().getHash()); + setForkBlocks(tx, blockNumber, forkBlocks); + // If a block is a new canonical head, start pruning. + if (event.isNewCanonicalHead()) { + while (blockNumber - pruningMark >= blocksToKeep) { + LOG.debug("Pruning chain data at pruning mark: " + pruningMark); + // Get a collection of old fork blocks that need to be pruned. + Collection oldForkBlocks = getForkBlocks(pruningMark); + BlockchainStorage.Updater updater = blockchainStorage.updater(); + for (Hash toPrune : oldForkBlocks) { + Optional maybeBody = blockchainStorage.getBlockBody(toPrune); + if (maybeBody.isEmpty()) { + continue; + } + // Prune block header, body, receipts, total difficulty and transaction locations. + updater.removeBlockHeader(toPrune); + updater.removeBlockBody(toPrune); + updater.removeTransactionReceipts(toPrune); + updater.removeTotalDifficulty(toPrune); + maybeBody.get().getTransactions().forEach(t -> updater.removeTransactionLocation(t.getHash())); + } + // Prune canonical chain mapping and commit. + updater.removeBlockHash(pruningMark); + updater.commit(); + // Remove old fork blocks. + removeForkBlocks(tx, pruningMark); + pruningMark++; + } + } + // Update pruning mark and commit + setPruningMark(tx, pruningMark); + tx.commit(); + } + + private Optional getPruningMark() { + return get(VARIABLES_PREFIX, PRUNING_MARK_KEY).map(UInt256::fromBytes).map(UInt256::toLong); + } + + private Collection getForkBlocks(final long blockNumber) { + return get(FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)) + .map(bytes -> RLP.input(bytes).readList(in -> bytesToHash(in.readBytes32()))) + .orElse(Lists.newArrayList()); + } + + private void setPruningMark(final KeyValueStorageTransaction transaction, final long pruningMark) { + set(transaction, VARIABLES_PREFIX, PRUNING_MARK_KEY, UInt256.valueOf(pruningMark)); + } + + private void setForkBlocks(final KeyValueStorageTransaction transaction, final long blockNumber, final Collection forkBlocks) { + set(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber), RLP.encode(o -> o.writeList(forkBlocks, (val, out) -> out.writeBytes(val)))); + } + + private void removeForkBlocks(final KeyValueStorageTransaction transaction, final long blockNumber) { + remove(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)); + } + + private Optional get(final Bytes prefix, final Bytes key) { + return prunerStorage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); + } + + private void set(final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key, final Bytes value) { + transaction.put(Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); + } + + private void remove(final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key) { + transaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); + } + + private Hash bytesToHash(final Bytes bytes) { + return Hash.wrap(Bytes32.wrap(bytes, 0)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java index 316316fcc7c..87a3c0f89a8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java @@ -35,7 +35,8 @@ public enum KeyValueSegmentIdentifier implements SegmentIdentifier { BACKWARD_SYNC_BLOCKS(new byte[] {14}), BACKWARD_SYNC_CHAIN(new byte[] {15}), SNAPSYNC_MISSING_ACCOUNT_RANGE(new byte[] {16}), - SNAPSYNC_ACCOUNT_TO_FIX(new byte[] {17}); + SNAPSYNC_ACCOUNT_TO_FIX(new byte[] {17}), + CHAIN_PRUNER_STATE(new byte[] {18}); private final byte[] id; private final int[] versionList; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java index f638bb4d906..8e26ede5f60 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java @@ -203,11 +203,31 @@ public void removeBlockHash(final long blockNumber) { remove(BLOCK_HASH_PREFIX, UInt256.valueOf(blockNumber)); } + @Override + public void removeBlockHeader(final Hash blockHash) { + remove(BLOCK_HEADER_PREFIX, blockHash); + } + + @Override + public void removeBlockBody(final Hash blockHash) { + remove(BLOCK_BODY_PREFIX, blockHash); + } + + @Override + public void removeTransactionReceipts(final Hash blockHash) { + remove(TRANSACTION_RECEIPTS_PREFIX, blockHash); + } + @Override public void removeTransactionLocation(final Hash transactionHash) { remove(TRANSACTION_LOCATION_PREFIX, transactionHash); } + @Override + public void removeTotalDifficulty(final Hash blockHash) { + remove(TOTAL_DIFFICULTY_PREFIX, blockHash); + } + @Override public void commit() { transaction.commit(); From be8f08123686749095df8ab7c77c8463ca7585af Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 16 Nov 2022 09:57:34 +1000 Subject: [PATCH 02/29] Add tests and options Signed-off-by: wcgcyx --- .../org/hyperledger/besu/cli/BesuCommand.java | 19 ++++- .../unstable/ChainDataPruningOptions.java | 35 ++++++++ .../controller/BesuControllerBuilder.java | 27 ++++++ ...{ChainPruner.java => ChainDataPruner.java} | 69 ++++++++++----- .../ethereum/chain/ChainDataPrunerTest.java | 85 +++++++++++++++++++ 5 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/{ChainPruner.java => ChainDataPruner.java} (74%) create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.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 7c008d4fc95..02477826390 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -56,6 +56,7 @@ import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; +import org.hyperledger.besu.cli.options.unstable.ChainDataPruningOptions; import org.hyperledger.besu.cli.options.unstable.DnsOptions; import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; import org.hyperledger.besu.cli.options.unstable.EvmOptions; @@ -288,6 +289,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final PrivacyPluginOptions unstablePrivacyPluginOptions = PrivacyPluginOptions.create(); private final EvmOptions unstableEvmOptions = EvmOptions.create(); private final IpcOptions unstableIpcOptions = IpcOptions.create(); + private final ChainDataPruningOptions unstableChainDataPruningOptions = + ChainDataPruningOptions.create(); // stable CLI options private final DataStorageOptions dataStorageOptions = DataStorageOptions.create(); @@ -1529,6 +1532,7 @@ private void handleUnstableOptions() { .put("Launcher", unstableLauncherOptions) .put("EVM Options", unstableEvmOptions) .put("IPC Options", unstableIpcOptions) + .put("Chain Data Pruning Options", unstableChainDataPruningOptions) .build(); UnstableOptionsSubCommand.createUnstableOptions(commandLine, unstableOptions); @@ -1756,6 +1760,7 @@ private void validateOptions() { validateDnsOptionsParams(); ensureValidPeerBoundParams(); validateRpcOptionsParams(); + validateChainDataPruningParams(); p2pTLSConfigOptions.checkP2PTLSOptionsDependencies(logger, commandLine); pkiBlockCreationOptions.checkPkiBlockCreationOptionsDependencies(logger, commandLine); } @@ -1894,6 +1899,15 @@ public void validateRpcOptionsParams() { } } + public void validateChainDataPruningParams() { + if (Boolean.TRUE.equals(unstableChainDataPruningOptions.getChainDataPruningEnabled()) + && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() + < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { + throw new ParameterException( + this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 1024"); + } + } + private GenesisConfigOptions readGenesisConfigOptions() { try { @@ -2149,7 +2163,10 @@ public BesuControllerBuilder getControllerBuilder() { .reorgLoggingThreshold(reorgLoggingThreshold) .evmConfiguration(unstableEvmOptions.toDomainObject()) .dataStorageConfiguration(dataStorageOptions.toDomainObject()) - .maxPeers(p2PDiscoveryOptionGroup.maxPeers); + .maxPeers(p2PDiscoveryOptionGroup.maxPeers) + .isChainDataPruningEnabled(unstableChainDataPruningOptions.getChainDataPruningEnabled()) + .chainDataPruningBlocksRetained( + unstableChainDataPruningOptions.getChainDataPruningBlocksRetained()); } private GraphQLConfiguration graphQLConfiguration() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java new file mode 100644 index 00000000000..6bdf2093cc8 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -0,0 +1,35 @@ +package org.hyperledger.besu.cli.options.unstable; + +import picocli.CommandLine; + +public class ChainDataPruningOptions { + + public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 1024; + + @CommandLine.Option( + hidden = true, + names = {"--Xchain-data-pruning-enabled"}, + description = + "Enable the chain pruner to actively prune old chain data (default: ${DEFAULT-VALUE})") + private final Boolean chainDataPruningEnabled = Boolean.FALSE; + + @CommandLine.Option( + hidden = true, + names = {"--Xchain-data-pruning-blocks-retained"}, + description = + "The number of recent blocks for which to keep the chain data. Must be >= 1024 (default: ${DEFAULT-VALUE})") + private final Long chainDataPruningBlocksRetained = + DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED; + + public static ChainDataPruningOptions create() { + return new ChainDataPruningOptions(); + } + + public Boolean getChainDataPruningEnabled() { + return chainDataPruningEnabled; + } + + public Long getChainDataPruningBlocksRetained() { + return chainDataPruningBlocksRetained; + } +} 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 c8017637d25..02411b2eaf3 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,7 @@ import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; +import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -139,6 +140,8 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides Collections.emptyList(); protected EvmConfiguration evmConfiguration; protected int maxPeers; + protected boolean isChainDataPruningEnabled; + protected long chainDataPruningBlocksRetained; public BesuControllerBuilder storageProvider(final StorageProvider storageProvider) { this.storageProvider = storageProvider; @@ -267,6 +270,17 @@ public BesuControllerBuilder maxPeers(final int maxPeers) { return this; } + public BesuControllerBuilder isChainDataPruningEnabled(final boolean isChainDataPruningEnabled) { + this.isChainDataPruningEnabled = isChainDataPruningEnabled; + return this; + } + + public BesuControllerBuilder chainDataPruningBlocksRetained( + final long chainDataPruningBlocksRetained) { + this.chainDataPruningBlocksRetained = chainDataPruningBlocksRetained; + return this; + } + public BesuController build() { checkNotNull(genesisConfig, "Missing genesis config"); checkNotNull(syncConfig, "Missing sync config"); @@ -300,6 +314,19 @@ public BesuController build() { reorgLoggingThreshold, dataDirectory.toString()); + if (isChainDataPruningEnabled) { + ChainDataPruner chainDataPruner = + new ChainDataPruner( + blockchainStorage, + storageProvider.getStorageBySegmentIdentifier( + KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE), + chainDataPruningBlocksRetained); + blockchain.observeBlockAdded(chainDataPruner); + LOG.info( + "Chain data pruning enabled with recent blocks retained to be: " + + chainDataPruningBlocksRetained); + } + final WorldStateArchive worldStateArchive = createWorldStateArchive(worldStateStorage, blockchain); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java similarity index 74% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 64bdfe2ac30..e96e9b46d29 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -1,24 +1,25 @@ package org.hyperledger.besu.ethereum.chain; -import com.google.common.collect.Lists; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Optional; -public class ChainPruner implements BlockAddedObserver { +import com.google.common.collect.Lists; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChainDataPruner implements BlockAddedObserver { - private static final Logger LOG = LoggerFactory.getLogger(ChainPruner.class); + private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); private static final Bytes PRUNING_MARK_KEY = Bytes.wrap("blockNumberTail".getBytes(StandardCharsets.UTF_8)); @@ -28,15 +29,15 @@ public class ChainPruner implements BlockAddedObserver { private final BlockchainStorage blockchainStorage; private final KeyValueStorage prunerStorage; - private final long blocksToKeep; + private final long blocksToRetain; - public ChainPruner( + public ChainDataPruner( final BlockchainStorage blockchainStorage, final KeyValueStorage prunerStorage, - final long blocksToKeep) { + final long blocksToRetain) { this.blockchainStorage = blockchainStorage; this.prunerStorage = prunerStorage; - this.blocksToKeep = blocksToKeep; + this.blocksToRetain = blocksToRetain; } @Override @@ -51,8 +52,15 @@ public void onBlockAdded(final BlockAddedEvent event) { } long pruningMark = maybePruningMark.get(); if (blockNumber < pruningMark) { - // Ignore and warn if block number < pruning mark, this normally indicates the blocksToKeep is too small. - LOG.warn("Block added event: " + event + " has a block number of " + blockNumber + " < pruning mark " + pruningMark); + // Ignore and warn if block number < pruning mark, this normally indicates the blocksToKeep is + // too small. + LOG.warn( + "Block added event: " + + event + + " has a block number of " + + blockNumber + + " < pruning mark " + + pruningMark); return; } // Append block into fork blocks. @@ -62,7 +70,7 @@ public void onBlockAdded(final BlockAddedEvent event) { setForkBlocks(tx, blockNumber, forkBlocks); // If a block is a new canonical head, start pruning. if (event.isNewCanonicalHead()) { - while (blockNumber - pruningMark >= blocksToKeep) { + while (blockNumber - pruningMark >= blocksToRetain) { LOG.debug("Pruning chain data at pruning mark: " + pruningMark); // Get a collection of old fork blocks that need to be pruned. Collection oldForkBlocks = getForkBlocks(pruningMark); @@ -77,7 +85,10 @@ public void onBlockAdded(final BlockAddedEvent event) { updater.removeBlockBody(toPrune); updater.removeTransactionReceipts(toPrune); updater.removeTotalDifficulty(toPrune); - maybeBody.get().getTransactions().forEach(t -> updater.removeTransactionLocation(t.getHash())); + maybeBody + .get() + .getTransactions() + .forEach(t -> updater.removeTransactionLocation(t.getHash())); } // Prune canonical chain mapping and commit. updater.removeBlockHash(pruningMark); @@ -102,15 +113,24 @@ private Collection getForkBlocks(final long blockNumber) { .orElse(Lists.newArrayList()); } - private void setPruningMark(final KeyValueStorageTransaction transaction, final long pruningMark) { + private void setPruningMark( + final KeyValueStorageTransaction transaction, final long pruningMark) { set(transaction, VARIABLES_PREFIX, PRUNING_MARK_KEY, UInt256.valueOf(pruningMark)); } - private void setForkBlocks(final KeyValueStorageTransaction transaction, final long blockNumber, final Collection forkBlocks) { - set(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber), RLP.encode(o -> o.writeList(forkBlocks, (val, out) -> out.writeBytes(val)))); + private void setForkBlocks( + final KeyValueStorageTransaction transaction, + final long blockNumber, + final Collection forkBlocks) { + set( + transaction, + FORK_BLOCKS_PREFIX, + UInt256.valueOf(blockNumber), + RLP.encode(o -> o.writeList(forkBlocks, (val, out) -> out.writeBytes(val)))); } - private void removeForkBlocks(final KeyValueStorageTransaction transaction, final long blockNumber) { + private void removeForkBlocks( + final KeyValueStorageTransaction transaction, final long blockNumber) { remove(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)); } @@ -118,11 +138,16 @@ private Optional get(final Bytes prefix, final Bytes key) { return prunerStorage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); } - private void set(final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key, final Bytes value) { + private void set( + final KeyValueStorageTransaction transaction, + final Bytes prefix, + final Bytes key, + final Bytes value) { transaction.put(Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); } - private void remove(final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key) { + private void remove( + final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key) { transaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java new file mode 100644 index 00000000000..cd2b96a46a6 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -0,0 +1,85 @@ +package org.hyperledger.besu.ethereum.chain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class ChainDataPrunerTest { + + @Test + public void singleChainPruning() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final BlockchainStorage blockchainStorage = + new KeyValueStoragePrefixedKeyBlockchainStorage( + new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + final ChainDataPruner chainDataPruner = + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512); + Block genesisBlock = gen.genesisBlock(); + final MutableBlockchain blockchain = + DefaultBlockchain.createMutable( + genesisBlock, blockchainStorage, new NoOpMetricsSystem(), 0); + blockchain.observeBlockAdded(chainDataPruner); + + // Generate & Import 1000 blocks + gen.blockSequence(genesisBlock, 1000) + .forEach( + blk -> { + blockchain.appendBlock(blk, gen.receipts(blk)); + long number = blk.getHeader().getNumber(); + if (number <= 512) { + // No prune happened + assertThat(blockchain.getBlockHeader(1)).isPresent(); + } else { + // Prune number - 512 only + assertThat(blockchain.getBlockHeader(number - 512)).isEmpty(); + assertThat(blockchain.getBlockHeader(number - 511)).isPresent(); + } + }); + } + + @Test + public void forkPruning() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final BlockchainStorage blockchainStorage = + new KeyValueStoragePrefixedKeyBlockchainStorage( + new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + final ChainDataPruner chainDataPruner = + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512); + Block genesisBlock = gen.genesisBlock(); + final MutableBlockchain blockchain = + DefaultBlockchain.createMutable( + genesisBlock, blockchainStorage, new NoOpMetricsSystem(), 0); + blockchain.observeBlockAdded(chainDataPruner); + + List canonicalChain = gen.blockSequence(genesisBlock, 1000); + List forkChain = gen.blockSequence(genesisBlock, 16); + for (Block blk : forkChain) { + blockchain.storeBlock(blk, gen.receipts(blk)); + } + for (int i = 0; i < 512; i++) { + Block blk = canonicalChain.get(i); + blockchain.appendBlock(blk, gen.receipts(blk)); + } + // No prune happened + assertThat(blockchain.getBlockByHash(canonicalChain.get(0).getHash())).isPresent(); + assertThat(blockchain.getBlockByHash(forkChain.get(0).getHash())).isPresent(); + for (int i = 512; i < 527; i++) { + Block blk = canonicalChain.get(i); + blockchain.appendBlock(blk, gen.receipts(blk)); + // Prune block on canonical chain and fork for i - 512 only + assertThat(blockchain.getBlockByHash(canonicalChain.get(i - 512).getHash())).isEmpty(); + assertThat(blockchain.getBlockByHash(canonicalChain.get(i - 511).getHash())).isPresent(); + assertThat(blockchain.getBlockByHash(forkChain.get(i - 512).getHash())).isEmpty(); + assertThat(blockchain.getBlockByHash(forkChain.get(i - 511).getHash())).isPresent(); + } + } +} From af595798970da355fda7124a2ccdce54f8454b16 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 16 Nov 2022 18:36:47 +1000 Subject: [PATCH 03/29] Add pruning frequency Signed-off-by: wcgcyx --- .../org/hyperledger/besu/cli/BesuCommand.java | 19 ++++++++----- .../unstable/ChainDataPruningOptions.java | 27 +++++++++++++++++++ .../controller/BesuControllerBuilder.java | 13 +++++++-- .../besu/ethereum/chain/ChainDataPruner.java | 24 +++++++++++++++-- .../ethereum/chain/ChainDataPrunerTest.java | 19 +++++++++++-- 5 files changed, 90 insertions(+), 12 deletions(-) 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 02477826390..abd5db5fba0 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1900,11 +1900,16 @@ public void validateRpcOptionsParams() { } public void validateChainDataPruningParams() { - if (Boolean.TRUE.equals(unstableChainDataPruningOptions.getChainDataPruningEnabled()) - && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() - < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { - throw new ParameterException( - this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 1024"); + if (Boolean.TRUE.equals(unstableChainDataPruningOptions.getChainDataPruningEnabled())) { + if (unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() + < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { + throw new ParameterException( + this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 1024"); + } + if (unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency() <= 0) { + throw new ParameterException( + this.commandLine, "--Xchain-data-pruning-frequency must be positive"); + } } } @@ -2166,7 +2171,9 @@ public BesuControllerBuilder getControllerBuilder() { .maxPeers(p2PDiscoveryOptionGroup.maxPeers) .isChainDataPruningEnabled(unstableChainDataPruningOptions.getChainDataPruningEnabled()) .chainDataPruningBlocksRetained( - unstableChainDataPruningOptions.getChainDataPruningBlocksRetained()); + unstableChainDataPruningOptions.getChainDataPruningBlocksRetained()) + .chainDataPruningFrequency( + unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency()); } private GraphQLConfiguration graphQLConfiguration() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index 6bdf2093cc8..f3d87fb976f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -1,3 +1,18 @@ +/* + * 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 picocli.CommandLine; @@ -5,6 +20,7 @@ public class ChainDataPruningOptions { public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 1024; + public static final long DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( hidden = true, @@ -21,6 +37,13 @@ public class ChainDataPruningOptions { private final Long chainDataPruningBlocksRetained = DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED; + @CommandLine.Option( + hidden = true, + names = {"--Xchain-data-pruning-frequency"}, + description = + "The number of blocks added to the chain between two pruning operations. Must be positive (default: ${DEFAULT-VALUE})") + private final Long chainDataPruningBlocksFrequency = DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY; + public static ChainDataPruningOptions create() { return new ChainDataPruningOptions(); } @@ -32,4 +55,8 @@ public Boolean getChainDataPruningEnabled() { public Long getChainDataPruningBlocksRetained() { return chainDataPruningBlocksRetained; } + + public Long getChainDataPruningBlocksFrequency() { + return chainDataPruningBlocksFrequency; + } } 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 02411b2eaf3..b8a273d30ab 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -142,6 +142,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides protected int maxPeers; protected boolean isChainDataPruningEnabled; protected long chainDataPruningBlocksRetained; + protected long chainDataPruningFrequency; public BesuControllerBuilder storageProvider(final StorageProvider storageProvider) { this.storageProvider = storageProvider; @@ -281,6 +282,11 @@ public BesuControllerBuilder chainDataPruningBlocksRetained( return this; } + public BesuControllerBuilder chainDataPruningFrequency(final long chainDataPruningFrequency) { + this.chainDataPruningFrequency = chainDataPruningFrequency; + return this; + } + public BesuController build() { checkNotNull(genesisConfig, "Missing genesis config"); checkNotNull(syncConfig, "Missing sync config"); @@ -320,11 +326,14 @@ public BesuController build() { blockchainStorage, storageProvider.getStorageBySegmentIdentifier( KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE), - chainDataPruningBlocksRetained); + chainDataPruningBlocksRetained, + chainDataPruningFrequency); blockchain.observeBlockAdded(chainDataPruner); LOG.info( "Chain data pruning enabled with recent blocks retained to be: " - + chainDataPruningBlocksRetained); + + chainDataPruningBlocksRetained + + " and frequency to be: " + + chainDataPruningFrequency); } final WorldStateArchive worldStateArchive = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index e96e9b46d29..5e87c001b8f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -1,3 +1,18 @@ +/* + * 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.chain; import org.hyperledger.besu.datatypes.Hash; @@ -31,13 +46,17 @@ public class ChainDataPruner implements BlockAddedObserver { private final KeyValueStorage prunerStorage; private final long blocksToRetain; + private final long pruningFrequency; + public ChainDataPruner( final BlockchainStorage blockchainStorage, final KeyValueStorage prunerStorage, - final long blocksToRetain) { + final long blocksToRetain, + final long pruningFrequency) { this.blockchainStorage = blockchainStorage; this.prunerStorage = prunerStorage; this.blocksToRetain = blocksToRetain; + this.pruningFrequency = pruningFrequency; } @Override @@ -69,7 +88,8 @@ public void onBlockAdded(final BlockAddedEvent event) { forkBlocks.add(event.getBlock().getHash()); setForkBlocks(tx, blockNumber, forkBlocks); // If a block is a new canonical head, start pruning. - if (event.isNewCanonicalHead()) { + if (event.isNewCanonicalHead() + && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { while (blockNumber - pruningMark >= blocksToRetain) { LOG.debug("Pruning chain data at pruning mark: " + pruningMark); // Get a collection of old fork blocks that need to be pruned. diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index cd2b96a46a6..2e60453e7df 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -1,3 +1,18 @@ +/* + * 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.chain; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +37,7 @@ public void singleChainPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512); + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 1); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -53,7 +68,7 @@ public void forkPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512); + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 1); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( From 16de0e99ca6ddb27fa9c7be00d32bdff35037668 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 17 Nov 2022 11:10:42 +1000 Subject: [PATCH 04/29] Fix unit tests Signed-off-by: wcgcyx --- .../java/org/hyperledger/besu/cli/CommandTestAbstract.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 e5b72f9b67d..9b9b62ab6ca 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -224,6 +224,12 @@ public void initMocks() throws Exception { when(mockControllerBuilder.dataStorageConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.evmConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.isChainDataPruningEnabled(anyBoolean())) + .thenReturn(mockControllerBuilder); + when(mockControllerBuilder.chainDataPruningBlocksRetained(anyLong())) + .thenReturn(mockControllerBuilder); + when(mockControllerBuilder.chainDataPruningFrequency(anyLong())) + .thenReturn(mockControllerBuilder); // doReturn used because of generic BesuController doReturn(mockController).when(mockControllerBuilder).build(); lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager); From 3e67042a2fd26ab24fcf05759f38513d800b6972 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 17 Nov 2022 11:56:32 +1000 Subject: [PATCH 05/29] Fix chain pruning tests Signed-off-by: wcgcyx --- .../hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index 2e60453e7df..f63041d3cc4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -37,7 +37,7 @@ public void singleChainPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 1); + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 0); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -68,7 +68,7 @@ public void forkPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 1); + new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 0); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( From 0d433a32e8c0a4916ed3e3c278de25928ca53773 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 18 Nov 2022 14:41:55 +1000 Subject: [PATCH 06/29] Increase minimum blocks to retain Signed-off-by: wcgcyx --- .../src/main/java/org/hyperledger/besu/cli/BesuCommand.java | 6 +++--- .../besu/cli/options/unstable/ChainDataPruningOptions.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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 abd5db5fba0..920028be9dc 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1904,11 +1904,11 @@ public void validateChainDataPruningParams() { if (unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( - this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 1024"); + this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 50400"); } - if (unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency() <= 0) { + if (unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency() < 0) { throw new ParameterException( - this.commandLine, "--Xchain-data-pruning-frequency must be positive"); + this.commandLine, "--Xchain-data-pruning-frequency must be non-negative"); } } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index f3d87fb976f..5709ac51139 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -19,7 +19,7 @@ public class ChainDataPruningOptions { - public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 1024; + public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 50400; public static final long DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( @@ -33,7 +33,7 @@ public class ChainDataPruningOptions { hidden = true, names = {"--Xchain-data-pruning-blocks-retained"}, description = - "The number of recent blocks for which to keep the chain data. Must be >= 1024 (default: ${DEFAULT-VALUE})") + "The number of recent blocks for which to keep the chain data. Must be >= 50400 (default: ${DEFAULT-VALUE})") private final Long chainDataPruningBlocksRetained = DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED; @@ -41,7 +41,7 @@ public class ChainDataPruningOptions { hidden = true, names = {"--Xchain-data-pruning-frequency"}, description = - "The number of blocks added to the chain between two pruning operations. Must be positive (default: ${DEFAULT-VALUE})") + "The number of blocks added to the chain between two pruning operations. Must be non-negative (default: ${DEFAULT-VALUE})") private final Long chainDataPruningBlocksFrequency = DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY; public static ChainDataPruningOptions create() { From 0e545c52aaf810a91b4c14e1b5a73320ba4c5880 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 21 Nov 2022 16:39:26 +1000 Subject: [PATCH 07/29] Skip ancestor check in pruning mode Signed-off-by: wcgcyx --- .../besu/controller/BesuControllerBuilder.java | 1 + .../internal/methods/engine/EngineForkchoiceUpdated.java | 4 +++- .../internal/methods/engine/EngineNewPayload.java | 4 +++- .../hyperledger/besu/ethereum/chain/ChainDataPruner.java | 9 +++++++++ 4 files changed, 16 insertions(+), 2 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 b8a273d30ab..e027ce3b8ac 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -321,6 +321,7 @@ public BesuController build() { dataDirectory.toString()); if (isChainDataPruningEnabled) { + ChainDataPruner.enablePruning(); ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java index bc88647ed40..6fd6b2e2fde 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; +import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.core.BlockHeader; import java.util.Optional; @@ -116,7 +117,8 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) } // TODO: post-merge cleanup, this should be unnecessary after merge - if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newHead.get())) { + if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newHead.get()) + && !ChainDataPruner.isPruningEnabled()) { logForkchoiceUpdatedCall(INVALID, forkChoice); return new JsonRpcSuccessResponse( requestId, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 7390b9d80f0..5ba6d17fa0d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult; +import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -199,7 +200,8 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) } // TODO: post-merge cleanup - if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader)) { + if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader) + && !ChainDataPruner.isPruningEnabled()) { mergeCoordinator.addBadBlock(block); return respondWithInvalid( reqId, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 5e87c001b8f..f9cb09d49a6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -42,6 +42,7 @@ public class ChainDataPruner implements BlockAddedObserver { private static final Bytes VARIABLES_PREFIX = Bytes.of(1); private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); + private static boolean pruningEnabled = false; private final BlockchainStorage blockchainStorage; private final KeyValueStorage prunerStorage; private final long blocksToRetain; @@ -59,6 +60,14 @@ public ChainDataPruner( this.pruningFrequency = pruningFrequency; } + public static void enablePruning() { + pruningEnabled = true; + } + + public static boolean isPruningEnabled() { + return pruningEnabled; + } + @Override public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); From 9313b026178bde3b5b0f6a194e206c8a77769df4 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 21 Nov 2022 17:03:55 +1000 Subject: [PATCH 08/29] Insert changelog entry & fix build issue Signed-off-by: wcgcyx --- CHANGELOG.md | 1 + .../internal/methods/engine/EngineNewPayload.java | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 135133f279c..940bc557830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Shanghai implementation of EIP-3855 Push0 [#4660](https://github.com/hyperledger/besu/pull/4660) - Shanghai implementation of EIP-3540 and EIP-3670 Ethereum Object Format and Code Validation [#4644](https://github.com/hyperledger/besu/pull/4644) - Remove some log statements that are keeping some objects live in heap for a long time, to reduce the amount of memory required during initial sync [#4705](https://github.com/hyperledger/besu/pull/4705) +- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-blocks-retained`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index a2e4d84f33c..7450a929983 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -182,6 +182,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final var block = new Block(newBlockHeader, new BlockBody(transactions, Collections.emptyList())); + final String warningMessage = "Sync to block " + block.toLogString() + " failed"; if (mergeContext.get().isSyncing() || parentHeader.isEmpty()) { LOG.debug( @@ -197,7 +198,13 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader) && !ChainDataPruner.isPruningEnabled()) { - mergeCoordinator.addBadBlock(block); + mergeCoordinator + .appendNewPayloadToSync(block) + .exceptionally( + exception -> { + LOG.warn(warningMessage, exception.getMessage()); + return null; + }); return respondWithInvalid( reqId, blockParam, From 3e3596bdd5e5e9d4a7917b34d6d6f3258400f490 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 21 Nov 2022 17:35:05 +1000 Subject: [PATCH 09/29] Fix unit test Signed-off-by: wcgcyx --- .../internal/methods/engine/EngineNewPayload.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 7450a929983..e9b9887f06f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -182,7 +182,6 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final var block = new Block(newBlockHeader, new BlockBody(transactions, Collections.emptyList())); - final String warningMessage = "Sync to block " + block.toLogString() + " failed"; if (mergeContext.get().isSyncing() || parentHeader.isEmpty()) { LOG.debug( @@ -198,13 +197,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader) && !ChainDataPruner.isPruningEnabled()) { - mergeCoordinator - .appendNewPayloadToSync(block) - .exceptionally( - exception -> { - LOG.warn(warningMessage, exception.getMessage()); - return null; - }); + mergeCoordinator.addBadBlock(block, Optional.empty()); return respondWithInvalid( reqId, blockParam, From 4c1d792914463c9c22cab470622794ead45dca8e Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 22 Nov 2022 11:50:45 +1000 Subject: [PATCH 10/29] Fix changelog & address reviews Signed-off-by: wcgcyx --- CHANGELOG.md | 2 +- besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java | 4 +++- .../besu/cli/options/unstable/ChainDataPruningOptions.java | 4 +++- .../hyperledger/besu/controller/BesuControllerBuilder.java | 2 +- .../org/hyperledger/besu/ethereum/chain/ChainDataPruner.java | 2 ++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23486bf7e69..3d78702b7b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ - Shanghai implementation of EIP-3540 and EIP-3670 Ethereum Object Format and Code Validation [#4644](https://github.com/hyperledger/besu/pull/4644) - Remove some log statements that are keeping some objects live in heap for a long time, to reduce the amount of memory required during initial sync [#4705](https://github.com/hyperledger/besu/pull/4705) - Add field `type` to Transaction receipt object (eth_getTransactionReceipt) [#4505](https://github.com/hyperledger/besu/issues/4505) -- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-blocks-retained`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) +- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-enabled`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes 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 8a8c5893a27..8728f3747e1 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1904,7 +1904,9 @@ public void validateChainDataPruningParams() { if (unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( - this.commandLine, "--Xchain-data-pruning-blocks-retained must be >= 50400"); + this.commandLine, + "--Xchain-data-pruning-blocks-retained must be >= " + + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); } if (unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency() < 0) { throw new ParameterException( diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index 5709ac51139..6576bfca2b1 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -33,7 +33,9 @@ public class ChainDataPruningOptions { hidden = true, names = {"--Xchain-data-pruning-blocks-retained"}, description = - "The number of recent blocks for which to keep the chain data. Must be >= 50400 (default: ${DEFAULT-VALUE})") + "The number of recent blocks for which to keep the chain data. Must be >= " + + DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED + + " (default: ${DEFAULT-VALUE})") private final Long chainDataPruningBlocksRetained = DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED; 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 9deb62e4137..33ad9296eab 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -322,7 +322,7 @@ public BesuController build() { if (isChainDataPruningEnabled) { ChainDataPruner.enablePruning(); - ChainDataPruner chainDataPruner = + final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, storageProvider.getStorageBySegmentIdentifier( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index f9cb09d49a6..10a63941927 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -42,6 +42,8 @@ public class ChainDataPruner implements BlockAddedObserver { private static final Bytes VARIABLES_PREFIX = Bytes.of(1); private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); + // TODO: cleanup - pruningEnabled will not be required after + // https://github.com/hyperledger/besu/pull/4703 is merged. private static boolean pruningEnabled = false; private final BlockchainStorage blockchainStorage; private final KeyValueStorage prunerStorage; From 95e86a58127d2b4b6c5e84aed9fd522acef5fe43 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 24 Nov 2022 13:03:17 +1000 Subject: [PATCH 11/29] Fix minor issues & code cleanup Signed-off-by: wcgcyx --- .../org/hyperledger/besu/cli/BesuCommand.java | 6 +- .../unstable/ChainDataPruningOptions.java | 9 ++- .../besu/ethereum/chain/ChainDataPruner.java | 69 +++++++++---------- 3 files changed, 40 insertions(+), 44 deletions(-) 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 8728f3747e1..f40ea02cbe4 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1900,7 +1900,7 @@ public void validateRpcOptionsParams() { } public void validateChainDataPruningParams() { - if (Boolean.TRUE.equals(unstableChainDataPruningOptions.getChainDataPruningEnabled())) { + if (unstableChainDataPruningOptions.getChainDataPruningEnabled()) { if (unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( @@ -1908,10 +1908,6 @@ public void validateChainDataPruningParams() { "--Xchain-data-pruning-blocks-retained must be >= " + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); } - if (unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency() < 0) { - throw new ParameterException( - this.commandLine, "--Xchain-data-pruning-frequency must be non-negative"); - } } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index 6576bfca2b1..e00e155c372 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -15,12 +15,14 @@ */ package org.hyperledger.besu.cli.options.unstable; +import org.hyperledger.besu.util.number.PositiveNumber; + import picocli.CommandLine; public class ChainDataPruningOptions { public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 50400; - public static final long DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; + public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( hidden = true, @@ -44,7 +46,8 @@ public class ChainDataPruningOptions { names = {"--Xchain-data-pruning-frequency"}, description = "The number of blocks added to the chain between two pruning operations. Must be non-negative (default: ${DEFAULT-VALUE})") - private final Long chainDataPruningBlocksFrequency = DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY; + private final PositiveNumber chainDataPruningBlocksFrequency = + PositiveNumber.fromInt(DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY); public static ChainDataPruningOptions create() { return new ChainDataPruningOptions(); @@ -59,6 +62,6 @@ public Long getChainDataPruningBlocksRetained() { } public Long getChainDataPruningBlocksFrequency() { - return chainDataPruningBlocksFrequency; + return (long) chainDataPruningBlocksFrequency.getValue(); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 10a63941927..46db357a15f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.chain; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; @@ -37,7 +36,7 @@ public class ChainDataPruner implements BlockAddedObserver { private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); private static final Bytes PRUNING_MARK_KEY = - Bytes.wrap("blockNumberTail".getBytes(StandardCharsets.UTF_8)); + Bytes.wrap("pruningMark".getBytes(StandardCharsets.UTF_8)); private static final Bytes VARIABLES_PREFIX = Bytes.of(1); private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); @@ -74,16 +73,11 @@ public static boolean isPruningEnabled() { public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); // Get pruning mark - long blockNumber = event.getBlock().getHeader().getNumber(); - Optional maybePruningMark = getPruningMark(); - if (maybePruningMark.isEmpty()) { - // Set initial pruning mark - maybePruningMark = Optional.of(blockNumber); - } - long pruningMark = maybePruningMark.get(); + final long blockNumber = event.getBlock().getHeader().getNumber(); + long pruningMark = getPruningMark().orElse(blockNumber); if (blockNumber < pruningMark) { - // Ignore and warn if block number < pruning mark, this normally indicates the blocksToKeep is - // too small. + // Ignore and warn if block number < pruning mark, this normally indicates the blocksToRetain + // is too small. LOG.warn( "Block added event: " + event @@ -94,8 +88,8 @@ public void onBlockAdded(final BlockAddedEvent event) { return; } // Append block into fork blocks. - KeyValueStorageTransaction tx = prunerStorage.startTransaction(); - Collection forkBlocks = getForkBlocks(blockNumber); + final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); + final Collection forkBlocks = getForkBlocks(blockNumber); forkBlocks.add(event.getBlock().getHash()); setForkBlocks(tx, blockNumber, forkBlocks); // If a block is a new canonical head, start pruning. @@ -103,29 +97,7 @@ public void onBlockAdded(final BlockAddedEvent event) { && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { while (blockNumber - pruningMark >= blocksToRetain) { LOG.debug("Pruning chain data at pruning mark: " + pruningMark); - // Get a collection of old fork blocks that need to be pruned. - Collection oldForkBlocks = getForkBlocks(pruningMark); - BlockchainStorage.Updater updater = blockchainStorage.updater(); - for (Hash toPrune : oldForkBlocks) { - Optional maybeBody = blockchainStorage.getBlockBody(toPrune); - if (maybeBody.isEmpty()) { - continue; - } - // Prune block header, body, receipts, total difficulty and transaction locations. - updater.removeBlockHeader(toPrune); - updater.removeBlockBody(toPrune); - updater.removeTransactionReceipts(toPrune); - updater.removeTotalDifficulty(toPrune); - maybeBody - .get() - .getTransactions() - .forEach(t -> updater.removeTransactionLocation(t.getHash())); - } - // Prune canonical chain mapping and commit. - updater.removeBlockHash(pruningMark); - updater.commit(); - // Remove old fork blocks. - removeForkBlocks(tx, pruningMark); + pruneChainDataAtBlock(tx, pruningMark); pruningMark++; } } @@ -134,6 +106,31 @@ public void onBlockAdded(final BlockAddedEvent event) { tx.commit(); } + private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { + // Get a collection of old fork blocks that need to be pruned. + final Collection oldForkBlocks = getForkBlocks(blockNumber); + final BlockchainStorage.Updater updater = blockchainStorage.updater(); + for (Hash toPrune : oldForkBlocks) { + // Prune block header, body, receipts, total difficulty and transaction locations. + updater.removeBlockHeader(toPrune); + updater.removeBlockBody(toPrune); + updater.removeTransactionReceipts(toPrune); + updater.removeTotalDifficulty(toPrune); + blockchainStorage + .getBlockBody(toPrune) + .ifPresent( + blockBody -> + blockBody + .getTransactions() + .forEach(t -> updater.removeTransactionLocation(t.getHash()))); + } + // Prune canonical chain mapping and commit. + updater.removeBlockHash(blockNumber); + updater.commit(); + // Remove old fork blocks. + removeForkBlocks(tx, blockNumber); + } + private Optional getPruningMark() { return get(VARIABLES_PREFIX, PRUNING_MARK_KEY).map(UInt256::fromBytes).map(UInt256::toLong); } From a63542a737c9d40eccc813b2ff483722f4106f32 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Thu, 24 Nov 2022 14:27:17 +1000 Subject: [PATCH 12/29] Separate class for pruning storage Signed-off-by: wcgcyx --- .../besu/ethereum/chain/ChainDataPruner.java | 84 ++-------------- .../chain/ChainDataPrunerStorage.java | 99 +++++++++++++++++++ 2 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 46db357a15f..8465eb6e796 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -16,47 +16,33 @@ package org.hyperledger.besu.ethereum.chain; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import java.nio.charset.StandardCharsets; import java.util.Collection; -import java.util.Optional; -import com.google.common.collect.Lists; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ChainDataPruner implements BlockAddedObserver { - private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); - private static final Bytes PRUNING_MARK_KEY = - Bytes.wrap("pruningMark".getBytes(StandardCharsets.UTF_8)); - - private static final Bytes VARIABLES_PREFIX = Bytes.of(1); - private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); - // TODO: cleanup - pruningEnabled will not be required after // https://github.com/hyperledger/besu/pull/4703 is merged. private static boolean pruningEnabled = false; private final BlockchainStorage blockchainStorage; - private final KeyValueStorage prunerStorage; + private final ChainDataPrunerStorage prunerStorage; private final long blocksToRetain; private final long pruningFrequency; public ChainDataPruner( final BlockchainStorage blockchainStorage, - final KeyValueStorage prunerStorage, + final KeyValueStorage storage, final long blocksToRetain, final long pruningFrequency) { this.blockchainStorage = blockchainStorage; - this.prunerStorage = prunerStorage; + this.prunerStorage = new ChainDataPrunerStorage(storage); this.blocksToRetain = blocksToRetain; this.pruningFrequency = pruningFrequency; } @@ -74,7 +60,7 @@ public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); // Get pruning mark final long blockNumber = event.getBlock().getHeader().getNumber(); - long pruningMark = getPruningMark().orElse(blockNumber); + long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); if (blockNumber < pruningMark) { // Ignore and warn if block number < pruning mark, this normally indicates the blocksToRetain // is too small. @@ -89,9 +75,9 @@ public void onBlockAdded(final BlockAddedEvent event) { } // Append block into fork blocks. final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); - final Collection forkBlocks = getForkBlocks(blockNumber); + final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); forkBlocks.add(event.getBlock().getHash()); - setForkBlocks(tx, blockNumber, forkBlocks); + prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); // If a block is a new canonical head, start pruning. if (event.isNewCanonicalHead() && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { @@ -102,13 +88,13 @@ public void onBlockAdded(final BlockAddedEvent event) { } } // Update pruning mark and commit - setPruningMark(tx, pruningMark); + prunerStorage.setPruningMark(tx, pruningMark); tx.commit(); } private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { // Get a collection of old fork blocks that need to be pruned. - final Collection oldForkBlocks = getForkBlocks(blockNumber); + final Collection oldForkBlocks = prunerStorage.getForkBlocks(blockNumber); final BlockchainStorage.Updater updater = blockchainStorage.updater(); for (Hash toPrune : oldForkBlocks) { // Prune block header, body, receipts, total difficulty and transaction locations. @@ -128,58 +114,6 @@ private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final lo updater.removeBlockHash(blockNumber); updater.commit(); // Remove old fork blocks. - removeForkBlocks(tx, blockNumber); - } - - private Optional getPruningMark() { - return get(VARIABLES_PREFIX, PRUNING_MARK_KEY).map(UInt256::fromBytes).map(UInt256::toLong); - } - - private Collection getForkBlocks(final long blockNumber) { - return get(FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)) - .map(bytes -> RLP.input(bytes).readList(in -> bytesToHash(in.readBytes32()))) - .orElse(Lists.newArrayList()); - } - - private void setPruningMark( - final KeyValueStorageTransaction transaction, final long pruningMark) { - set(transaction, VARIABLES_PREFIX, PRUNING_MARK_KEY, UInt256.valueOf(pruningMark)); - } - - private void setForkBlocks( - final KeyValueStorageTransaction transaction, - final long blockNumber, - final Collection forkBlocks) { - set( - transaction, - FORK_BLOCKS_PREFIX, - UInt256.valueOf(blockNumber), - RLP.encode(o -> o.writeList(forkBlocks, (val, out) -> out.writeBytes(val)))); - } - - private void removeForkBlocks( - final KeyValueStorageTransaction transaction, final long blockNumber) { - remove(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)); - } - - private Optional get(final Bytes prefix, final Bytes key) { - return prunerStorage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); - } - - private void set( - final KeyValueStorageTransaction transaction, - final Bytes prefix, - final Bytes key, - final Bytes value) { - transaction.put(Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); - } - - private void remove( - final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key) { - transaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); - } - - private Hash bytesToHash(final Bytes bytes) { - return Hash.wrap(Bytes32.wrap(bytes, 0)); + prunerStorage.removeForkBlocks(tx, blockNumber); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java new file mode 100644 index 00000000000..8b50ec83d52 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java @@ -0,0 +1,99 @@ +/* + * 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.chain; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Optional; + +import com.google.common.collect.Lists; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class ChainDataPrunerStorage { + private static final Bytes PRUNING_MARK_KEY = + Bytes.wrap("pruningMark".getBytes(StandardCharsets.UTF_8)); + + private static final Bytes VARIABLES_PREFIX = Bytes.of(1); + private static final Bytes FORK_BLOCKS_PREFIX = Bytes.of(2); + + private final KeyValueStorage storage; + + public ChainDataPrunerStorage(final KeyValueStorage storage) { + this.storage = storage; + } + + public KeyValueStorageTransaction startTransaction() { + return this.storage.startTransaction(); + } + + public Optional getPruningMark() { + return get(VARIABLES_PREFIX, PRUNING_MARK_KEY).map(UInt256::fromBytes).map(UInt256::toLong); + } + + public Collection getForkBlocks(final long blockNumber) { + return get(FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)) + .map(bytes -> RLP.input(bytes).readList(in -> bytesToHash(in.readBytes32()))) + .orElse(Lists.newArrayList()); + } + + public void setPruningMark(final KeyValueStorageTransaction transaction, final long pruningMark) { + set(transaction, VARIABLES_PREFIX, PRUNING_MARK_KEY, UInt256.valueOf(pruningMark)); + } + + public void setForkBlocks( + final KeyValueStorageTransaction transaction, + final long blockNumber, + final Collection forkBlocks) { + set( + transaction, + FORK_BLOCKS_PREFIX, + UInt256.valueOf(blockNumber), + RLP.encode(o -> o.writeList(forkBlocks, (val, out) -> out.writeBytes(val)))); + } + + public void removeForkBlocks( + final KeyValueStorageTransaction transaction, final long blockNumber) { + remove(transaction, FORK_BLOCKS_PREFIX, UInt256.valueOf(blockNumber)); + } + + private Optional get(final Bytes prefix, final Bytes key) { + return storage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); + } + + private void set( + final KeyValueStorageTransaction transaction, + final Bytes prefix, + final Bytes key, + final Bytes value) { + transaction.put(Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); + } + + private void remove( + final KeyValueStorageTransaction transaction, final Bytes prefix, final Bytes key) { + transaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); + } + + private Hash bytesToHash(final Bytes bytes) { + return Hash.wrap(Bytes32.wrap(bytes, 0)); + } +} From 328005b02da8fb6c3967323f28846315af89cffb Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 25 Nov 2022 15:48:27 +1000 Subject: [PATCH 13/29] Rename options Signed-off-by: wcgcyx --- .../src/main/java/org/hyperledger/besu/cli/BesuCommand.java | 2 +- .../besu/cli/options/unstable/ChainDataPruningOptions.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 22d8e261c30..fbb50806a16 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1923,7 +1923,7 @@ public void validateChainDataPruningParams() { < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( this.commandLine, - "--Xchain-data-pruning-blocks-retained must be >= " + "--Xchain-pruning-blocks-retained must be >= " + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index e00e155c372..178f7cdc14d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -26,14 +26,14 @@ public class ChainDataPruningOptions { @CommandLine.Option( hidden = true, - names = {"--Xchain-data-pruning-enabled"}, + names = {"--Xchain-pruning-enabled"}, description = "Enable the chain pruner to actively prune old chain data (default: ${DEFAULT-VALUE})") private final Boolean chainDataPruningEnabled = Boolean.FALSE; @CommandLine.Option( hidden = true, - names = {"--Xchain-data-pruning-blocks-retained"}, + names = {"--Xchain-pruning-blocks-retained"}, description = "The number of recent blocks for which to keep the chain data. Must be >= " + DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED @@ -43,7 +43,7 @@ public class ChainDataPruningOptions { @CommandLine.Option( hidden = true, - names = {"--Xchain-data-pruning-frequency"}, + names = {"--Xchain-pruning-frequency"}, description = "The number of blocks added to the chain between two pruning operations. Must be non-negative (default: ${DEFAULT-VALUE})") private final PositiveNumber chainDataPruningBlocksFrequency = From 64ba456ba6508ac66d922e64f4f951b69c641651 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 29 Nov 2022 14:25:09 +1000 Subject: [PATCH 14/29] Move pruning to separate thread Signed-off-by: wcgcyx --- ethereum/core/build.gradle | 1 + .../besu/ethereum/chain/ChainDataPruner.java | 71 ++++++++++--------- .../ethereum/chain/ChainDataPrunerTest.java | 21 +++++- 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index cc3521c10f9..3cd849dfc82 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation 'org.apache.tuweni:tuweni-rlp' implementation 'org.hyperledger.besu:bls12-381' implementation 'org.immutables:value-annotations' + implementation 'org.awaitility:awaitility' implementation 'org.xerial.snappy:snappy-java' diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 8465eb6e796..e1a87f9bf44 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -58,38 +58,45 @@ public static boolean isPruningEnabled() { @Override public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); - // Get pruning mark - final long blockNumber = event.getBlock().getHeader().getNumber(); - long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); - if (blockNumber < pruningMark) { - // Ignore and warn if block number < pruning mark, this normally indicates the blocksToRetain - // is too small. - LOG.warn( - "Block added event: " - + event - + " has a block number of " - + blockNumber - + " < pruning mark " - + pruningMark); - return; - } - // Append block into fork blocks. - final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); - final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); - forkBlocks.add(event.getBlock().getHash()); - prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); - // If a block is a new canonical head, start pruning. - if (event.isNewCanonicalHead() - && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { - while (blockNumber - pruningMark >= blocksToRetain) { - LOG.debug("Pruning chain data at pruning mark: " + pruningMark); - pruneChainDataAtBlock(tx, pruningMark); - pruningMark++; - } - } - // Update pruning mark and commit - prunerStorage.setPruningMark(tx, pruningMark); - tx.commit(); + new Thread( + () -> { + synchronized (prunerStorage) { + // Get pruning mark + final long blockNumber = event.getBlock().getHeader().getNumber(); + long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); + if (blockNumber < pruningMark) { + // Ignore and warn if block number < pruning mark, this normally indicates the + // blocksToRetain + // is too small. + LOG.warn( + "Block added event: " + + event + + " has a block number of " + + blockNumber + + " < pruning mark " + + pruningMark); + return; + } + // Append block into fork blocks. + final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); + final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); + forkBlocks.add(event.getBlock().getHash()); + prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); + // If a block is a new canonical head, start pruning. + if (event.isNewCanonicalHead() + && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { + while (blockNumber - pruningMark >= blocksToRetain) { + LOG.debug("Pruning chain data at pruning mark: " + pruningMark); + pruneChainDataAtBlock(tx, pruningMark); + pruningMark++; + } + } + // Update pruning mark and commit + prunerStorage.setPruningMark(tx, pruningMark); + tx.commit(); + } + }) + .start(); } private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index f63041d3cc4..56a9746a5e8 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -25,7 +25,9 @@ import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.List; +import java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; public class ChainDataPrunerTest { @@ -55,7 +57,10 @@ public void singleChainPruning() { assertThat(blockchain.getBlockHeader(1)).isPresent(); } else { // Prune number - 512 only - assertThat(blockchain.getBlockHeader(number - 512)).isEmpty(); + Awaitility.await() + .pollInterval(1, TimeUnit.MILLISECONDS) + .atMost(50, TimeUnit.MILLISECONDS) + .until(() -> blockchain.getBlockHeader(number - 512).isEmpty()); assertThat(blockchain.getBlockHeader(number - 511)).isPresent(); } }); @@ -88,12 +93,22 @@ public void forkPruning() { assertThat(blockchain.getBlockByHash(canonicalChain.get(0).getHash())).isPresent(); assertThat(blockchain.getBlockByHash(forkChain.get(0).getHash())).isPresent(); for (int i = 512; i < 527; i++) { + final int index = i; Block blk = canonicalChain.get(i); blockchain.appendBlock(blk, gen.receipts(blk)); // Prune block on canonical chain and fork for i - 512 only - assertThat(blockchain.getBlockByHash(canonicalChain.get(i - 512).getHash())).isEmpty(); + Awaitility.await() + .pollInterval(1, TimeUnit.MILLISECONDS) + .atMost(50, TimeUnit.MILLISECONDS) + .until( + () -> blockchain.getBlockByHash(canonicalChain.get(index - 512).getHash()).isEmpty()); assertThat(blockchain.getBlockByHash(canonicalChain.get(i - 511).getHash())).isPresent(); - assertThat(blockchain.getBlockByHash(forkChain.get(i - 512).getHash())).isEmpty(); + Awaitility.await() + .pollInterval(1, TimeUnit.MILLISECONDS) + .atMost(50, TimeUnit.MILLISECONDS) + .until( + () -> blockchain.getBlockByHash(canonicalChain.get(index - 512).getHash()).isEmpty()); + assertThat(blockchain.getBlockByHash(forkChain.get(i - 511).getHash())).isPresent(); } } From ac7961130f436cec08bb43512b50ec7e651b564f Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 2 Dec 2022 10:15:17 +1000 Subject: [PATCH 15/29] Update changelog Signed-off-by: wcgcyx --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e7d53b31a0..852b0ef4916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Breaking Changes ### Additions and Improvements +- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-enabled`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes @@ -34,7 +35,6 @@ - Do not send new payloads to backward sync if initial sync is in progress [#4720](https://github.com/hyperledger/besu/issues/4720) - Improve the way transaction fee cap validation is done on London fee market to not depend on transient network conditions [#4598](https://github.com/hyperledger/besu/pull/4598) - Preload and cache account and storage data from RocksDB to improve performance [#4737](https://github.com/hyperledger/besu/issues/4737) -- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-enabled`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes - Restore updating chain head and finalized block during backward sync [#4718](https://github.com/hyperledger/besu/pull/4718) From 51c6c6df9e3fb834731b68a908d2e6716f2a67b5 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 5 Dec 2022 14:51:14 +1000 Subject: [PATCH 16/29] Remove global variable & decrease minimum allowed retaining block Signed-off-by: wcgcyx --- .../unstable/ChainDataPruningOptions.java | 2 +- .../controller/BesuControllerBuilder.java | 30 +++++++++---------- .../engine/EngineForkchoiceUpdated.java | 3 +- .../methods/engine/EngineNewPayload.java | 3 +- .../besu/ethereum/ProtocolContext.java | 11 +++++++ .../besu/ethereum/chain/ChainDataPruner.java | 12 -------- 6 files changed, 29 insertions(+), 32 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index 178f7cdc14d..df7e071f5bf 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -21,7 +21,7 @@ public class ChainDataPruningOptions { - public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 50400; + public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 5040; public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( 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 555010dc757..61ecf679644 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -321,8 +321,22 @@ public BesuController build() { reorgLoggingThreshold, dataDirectory.toString()); + final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(metricsSystem); + + final WorldStateArchive worldStateArchive = + createWorldStateArchive(worldStateStorage, blockchain, cachedMerkleTrieLoader); + + if (blockchain.getChainHeadBlockNumber() < 1) { + genesisState.writeStateTo(worldStateArchive.getMutable()); + } + + final ProtocolContext protocolContext = + createProtocolContext( + blockchain, worldStateArchive, protocolSchedule, this::createConsensusContext); + validateContext(protocolContext); + if (isChainDataPruningEnabled) { - ChainDataPruner.enablePruning(); + protocolContext.setIsChainPruningEnabled(true); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, @@ -338,20 +352,6 @@ public BesuController build() { + chainDataPruningFrequency); } - final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(metricsSystem); - - final WorldStateArchive worldStateArchive = - createWorldStateArchive(worldStateStorage, blockchain, cachedMerkleTrieLoader); - - if (blockchain.getChainHeadBlockNumber() < 1) { - genesisState.writeStateTo(worldStateArchive.getMutable()); - } - - final ProtocolContext protocolContext = - createProtocolContext( - blockchain, worldStateArchive, protocolSchedule, this::createConsensusContext); - validateContext(protocolContext); - protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( protocolContext.getWorldStateArchive()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java index a7cd0573a3a..2aeee13001e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java @@ -35,7 +35,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; -import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.core.BlockHeader; import java.util.Optional; @@ -121,7 +120,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup, this should be unnecessary after merge if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newHead) - && !ChainDataPruner.isPruningEnabled()) { + && !protocolContext.isChainPruningEnabled()) { logForkchoiceUpdatedCall(INVALID, forkChoice); return new JsonRpcSuccessResponse( requestId, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 600a928246f..64e47ad6863 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -36,7 +36,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult; -import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -198,7 +197,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader) - && !ChainDataPruner.isPruningEnabled()) { + && !protocolContext.isChainPruningEnabled()) { mergeCoordinator.addBadBlock(block, Optional.empty()); return respondWithInvalid( reqId, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index 0b115937053..a942542b87f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -29,6 +29,7 @@ public class ProtocolContext { private final MutableBlockchain blockchain; private final WorldStateArchive worldStateArchive; private final ConsensusContext consensusContext; + private boolean isChainPruningEnabled = false; public ProtocolContext( final MutableBlockchain blockchain, @@ -67,4 +68,14 @@ public Optional safeConsensusContext(final Class .filter(c -> klass.isAssignableFrom(c.getClass())) .map(klass::cast); } + + // TODO: cleanup - isChainPruningEnabled will not be required after + // https://github.com/hyperledger/besu/pull/4703 is merged. + public void setIsChainPruningEnabled(final boolean isChainPruningEnabled) { + this.isChainPruningEnabled = isChainPruningEnabled; + } + + public boolean isChainPruningEnabled() { + return isChainPruningEnabled; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index e1a87f9bf44..26f9199a16b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -26,10 +26,6 @@ public class ChainDataPruner implements BlockAddedObserver { private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); - - // TODO: cleanup - pruningEnabled will not be required after - // https://github.com/hyperledger/besu/pull/4703 is merged. - private static boolean pruningEnabled = false; private final BlockchainStorage blockchainStorage; private final ChainDataPrunerStorage prunerStorage; private final long blocksToRetain; @@ -47,14 +43,6 @@ public ChainDataPruner( this.pruningFrequency = pruningFrequency; } - public static void enablePruning() { - pruningEnabled = true; - } - - public static boolean isPruningEnabled() { - return pruningEnabled; - } - @Override public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); From 96c29344b6fe9291bc038cac9a8bd7c103941808 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 5 Dec 2022 15:46:03 +1000 Subject: [PATCH 17/29] Limit total pruning threads Signed-off-by: wcgcyx --- .../unstable/ChainDataPruningOptions.java | 2 +- .../besu/ethereum/chain/ChainDataPruner.java | 90 ++++++++++--------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index df7e071f5bf..375c0337caf 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -21,7 +21,7 @@ public class ChainDataPruningOptions { - public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 5040; + public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 7200; public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 26f9199a16b..d9362d73c1b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -20,6 +20,10 @@ import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; import java.util.Collection; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,8 +33,9 @@ public class ChainDataPruner implements BlockAddedObserver { private final BlockchainStorage blockchainStorage; private final ChainDataPrunerStorage prunerStorage; private final long blocksToRetain; - private final long pruningFrequency; + private final ExecutorService pruningExecutor; + private static final int MAX_PRUNING_WORKER = 16; public ChainDataPruner( final BlockchainStorage blockchainStorage, @@ -41,50 +46,55 @@ public ChainDataPruner( this.prunerStorage = new ChainDataPrunerStorage(storage); this.blocksToRetain = blocksToRetain; this.pruningFrequency = pruningFrequency; + this.pruningExecutor = + new ThreadPoolExecutor( + 1, + 1, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), + new ThreadPoolExecutor.DiscardPolicy()); } @Override public void onBlockAdded(final BlockAddedEvent event) { LOG.debug("New block added event: " + event); - new Thread( - () -> { - synchronized (prunerStorage) { - // Get pruning mark - final long blockNumber = event.getBlock().getHeader().getNumber(); - long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); - if (blockNumber < pruningMark) { - // Ignore and warn if block number < pruning mark, this normally indicates the - // blocksToRetain - // is too small. - LOG.warn( - "Block added event: " - + event - + " has a block number of " - + blockNumber - + " < pruning mark " - + pruningMark); - return; - } - // Append block into fork blocks. - final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); - final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); - forkBlocks.add(event.getBlock().getHash()); - prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); - // If a block is a new canonical head, start pruning. - if (event.isNewCanonicalHead() - && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { - while (blockNumber - pruningMark >= blocksToRetain) { - LOG.debug("Pruning chain data at pruning mark: " + pruningMark); - pruneChainDataAtBlock(tx, pruningMark); - pruningMark++; - } - } - // Update pruning mark and commit - prunerStorage.setPruningMark(tx, pruningMark); - tx.commit(); - } - }) - .start(); + pruningExecutor.submit( + () -> { + // Get pruning mark + final long blockNumber = event.getBlock().getHeader().getNumber(); + long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); + if (blockNumber < pruningMark) { + // Ignore and warn if block number < pruning mark, this normally indicates the + // blocksToRetain + // is too small. + LOG.warn( + "Block added event: " + + event + + " has a block number of " + + blockNumber + + " < pruning mark " + + pruningMark); + return; + } + // Append block into fork blocks. + final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); + final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); + forkBlocks.add(event.getBlock().getHash()); + prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); + // If a block is a new canonical head, start pruning. + if (event.isNewCanonicalHead() + && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { + while (blockNumber - pruningMark >= blocksToRetain) { + LOG.debug("Pruning chain data at pruning mark: " + pruningMark); + pruneChainDataAtBlock(tx, pruningMark); + pruningMark++; + } + } + // Update pruning mark and commit + prunerStorage.setPruningMark(tx, pruningMark); + tx.commit(); + }); } private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { From cbc460b375e3b35eaab520ef2941d10ae3f96bea Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 5 Dec 2022 15:50:37 +1000 Subject: [PATCH 18/29] Update pruner constructor Signed-off-by: wcgcyx --- .../hyperledger/besu/controller/BesuControllerBuilder.java | 6 ++++-- .../hyperledger/besu/ethereum/chain/ChainDataPruner.java | 5 ++--- .../besu/ethereum/chain/ChainDataPrunerTest.java | 6 ++++-- 3 files changed, 10 insertions(+), 7 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 61ecf679644..ae373c2f464 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.ChainDataPruner; +import org.hyperledger.besu.ethereum.chain.ChainDataPrunerStorage; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -340,8 +341,9 @@ public BesuController build() { final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, - storageProvider.getStorageBySegmentIdentifier( - KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE), + new ChainDataPrunerStorage( + storageProvider.getStorageBySegmentIdentifier( + KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE)), chainDataPruningBlocksRetained, chainDataPruningFrequency); blockchain.observeBlockAdded(chainDataPruner); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index d9362d73c1b..19f9bcacf0d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -16,7 +16,6 @@ package org.hyperledger.besu.ethereum.chain; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; import java.util.Collection; @@ -39,11 +38,11 @@ public class ChainDataPruner implements BlockAddedObserver { public ChainDataPruner( final BlockchainStorage blockchainStorage, - final KeyValueStorage storage, + final ChainDataPrunerStorage prunerStorage, final long blocksToRetain, final long pruningFrequency) { this.blockchainStorage = blockchainStorage; - this.prunerStorage = new ChainDataPrunerStorage(storage); + this.prunerStorage = prunerStorage; this.blocksToRetain = blocksToRetain; this.pruningFrequency = pruningFrequency; this.pruningExecutor = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index 56a9746a5e8..5074fa4a99c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -39,7 +39,8 @@ public void singleChainPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 0); + new ChainDataPruner( + blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -73,7 +74,8 @@ public void forkPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = - new ChainDataPruner(blockchainStorage, new InMemoryKeyValueStorage(), 512, 0); + new ChainDataPruner( + blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( From cc4648eefd03ddd3f4ec6b3add57a9db975f8cd3 Mon Sep 17 00:00:00 2001 From: Zhenyang Shi Date: Tue, 6 Dec 2022 12:42:20 +1000 Subject: [PATCH 19/29] Update besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java Co-authored-by: Simon Dudley Signed-off-by: Zhenyang Shi --- besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 6bba4ffeebe..cc60fce27d9 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1924,8 +1924,7 @@ public void validateRpcOptionsParams() { } public void validateChainDataPruningParams() { - if (unstableChainDataPruningOptions.getChainDataPruningEnabled()) { - if (unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() + if (unstableChainDataPruningOptions.getChainDataPruningEnabled() && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( this.commandLine, From 831539315e3c5d56ee91edba8e691bdcfd392921 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 6 Dec 2022 12:55:50 +1000 Subject: [PATCH 20/29] Spotless & Remove unnecessary comments Signed-off-by: wcgcyx --- .../org/hyperledger/besu/cli/BesuCommand.java | 14 +++++++------- .../besu/ethereum/chain/ChainDataPruner.java | 15 ++------------- 2 files changed, 9 insertions(+), 20 deletions(-) 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 cc60fce27d9..08f2d51fba9 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -1924,13 +1924,13 @@ public void validateRpcOptionsParams() { } public void validateChainDataPruningParams() { - if (unstableChainDataPruningOptions.getChainDataPruningEnabled() && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() - < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { - throw new ParameterException( - this.commandLine, - "--Xchain-pruning-blocks-retained must be >= " - + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); - } + if (unstableChainDataPruningOptions.getChainDataPruningEnabled() + && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() + < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { + throw new ParameterException( + this.commandLine, + "--Xchain-pruning-blocks-retained must be >= " + + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 19f9bcacf0d..cd2e07fe04a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -57,31 +57,25 @@ public ChainDataPruner( @Override public void onBlockAdded(final BlockAddedEvent event) { - LOG.debug("New block added event: " + event); pruningExecutor.submit( () -> { - // Get pruning mark final long blockNumber = event.getBlock().getHeader().getNumber(); long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); if (blockNumber < pruningMark) { - // Ignore and warn if block number < pruning mark, this normally indicates the - // blocksToRetain - // is too small. LOG.warn( "Block added event: " + event + " has a block number of " + blockNumber + " < pruning mark " - + pruningMark); + + pruningMark + + " which normally indicates the blocksToRetain is too small"); return; } - // Append block into fork blocks. final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); forkBlocks.add(event.getBlock().getHash()); prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); - // If a block is a new canonical head, start pruning. if (event.isNewCanonicalHead() && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { while (blockNumber - pruningMark >= blocksToRetain) { @@ -90,18 +84,15 @@ public void onBlockAdded(final BlockAddedEvent event) { pruningMark++; } } - // Update pruning mark and commit prunerStorage.setPruningMark(tx, pruningMark); tx.commit(); }); } private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { - // Get a collection of old fork blocks that need to be pruned. final Collection oldForkBlocks = prunerStorage.getForkBlocks(blockNumber); final BlockchainStorage.Updater updater = blockchainStorage.updater(); for (Hash toPrune : oldForkBlocks) { - // Prune block header, body, receipts, total difficulty and transaction locations. updater.removeBlockHeader(toPrune); updater.removeBlockBody(toPrune); updater.removeTransactionReceipts(toPrune); @@ -114,10 +105,8 @@ private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final lo .getTransactions() .forEach(t -> updater.removeTransactionLocation(t.getHash()))); } - // Prune canonical chain mapping and commit. updater.removeBlockHash(blockNumber); updater.commit(); - // Remove old fork blocks. prunerStorage.removeForkBlocks(tx, blockNumber); } } From 5992b665b7aa3dad8f5b10285002a41d608f81d3 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 6 Dec 2022 13:25:39 +1000 Subject: [PATCH 21/29] More clear variable Signed-off-by: wcgcyx --- .../besu/ethereum/chain/ChainDataPruner.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index cd2e07fe04a..79e45b998b2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -60,15 +60,15 @@ public void onBlockAdded(final BlockAddedEvent event) { pruningExecutor.submit( () -> { final long blockNumber = event.getBlock().getHeader().getNumber(); - long pruningMark = prunerStorage.getPruningMark().orElse(blockNumber); - if (blockNumber < pruningMark) { + long currentPruningMark = prunerStorage.getPruningMark().orElse(blockNumber); + if (blockNumber < currentPruningMark) { LOG.warn( "Block added event: " + event + " has a block number of " + blockNumber + " < pruning mark " - + pruningMark + + currentPruningMark + " which normally indicates the blocksToRetain is too small"); return; } @@ -76,15 +76,18 @@ public void onBlockAdded(final BlockAddedEvent event) { final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); forkBlocks.add(event.getBlock().getHash()); prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); + final long newPruningMark = blockNumber - blocksToRetain; if (event.isNewCanonicalHead() - && blockNumber - blocksToRetain - pruningMark >= pruningFrequency) { - while (blockNumber - pruningMark >= blocksToRetain) { - LOG.debug("Pruning chain data at pruning mark: " + pruningMark); - pruneChainDataAtBlock(tx, pruningMark); - pruningMark++; + && newPruningMark - currentPruningMark >= pruningFrequency) { + long currentRetainedBlock = blockNumber - currentPruningMark; + while (currentRetainedBlock > blocksToRetain) { + LOG.debug("Pruning chain data at pruning mark: " + currentPruningMark); + pruneChainDataAtBlock(tx, currentPruningMark); + currentPruningMark++; + currentRetainedBlock = blockNumber - currentPruningMark; } } - prunerStorage.setPruningMark(tx, pruningMark); + prunerStorage.setPruningMark(tx, currentPruningMark); tx.commit(); }); } From 5d4da4e9c9b48c06e4937d5b7bd18c6cf2e103a9 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 6 Dec 2022 13:28:26 +1000 Subject: [PATCH 22/29] Clear logging Signed-off-by: wcgcyx --- .../org/hyperledger/besu/ethereum/chain/ChainDataPruner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 79e45b998b2..5f68a64806e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -81,7 +81,7 @@ public void onBlockAdded(final BlockAddedEvent event) { && newPruningMark - currentPruningMark >= pruningFrequency) { long currentRetainedBlock = blockNumber - currentPruningMark; while (currentRetainedBlock > blocksToRetain) { - LOG.debug("Pruning chain data at pruning mark: " + currentPruningMark); + LOG.debug("Pruning chain data with block height of " + currentPruningMark); pruneChainDataAtBlock(tx, currentPruningMark); currentPruningMark++; currentRetainedBlock = blockNumber - currentPruningMark; From 118178ec78159d316f70db1162d1e6a0c713284e Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 6 Dec 2022 14:18:31 +1000 Subject: [PATCH 23/29] Fix bug & use monitored executors Signed-off-by: wcgcyx --- .../controller/BesuControllerBuilder.java | 9 ++++++- .../besu/ethereum/chain/ChainDataPruner.java | 19 ++++--------- .../ethereum/chain/ChainDataPrunerTest.java | 27 +++++++++++++++++-- 3 files changed, 38 insertions(+), 17 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 ae373c2f464..01f0b23ad7b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -53,6 +53,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.MergePeerFilter; +import org.hyperledger.besu.ethereum.eth.manager.MonitoredExecutors; import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; import org.hyperledger.besu.ethereum.eth.peervalidation.CheckpointBlocksPeerValidator; import org.hyperledger.besu.ethereum.eth.peervalidation.ClassicForkPeerValidator; @@ -345,7 +346,13 @@ public BesuController build() { storageProvider.getStorageBySegmentIdentifier( KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE)), chainDataPruningBlocksRetained, - chainDataPruningFrequency); + chainDataPruningFrequency, + MonitoredExecutors.newBoundedThreadPool( + ChainDataPruner.class.getSimpleName(), + 1, + 1, + ChainDataPruner.MAX_PRUNING_WORKER, + metricsSystem)); blockchain.observeBlockAdded(chainDataPruner); LOG.info( "Chain data pruning enabled with recent blocks retained to be: " diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 5f68a64806e..ebbb296afef 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -19,40 +19,31 @@ import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; import java.util.Collection; -import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ChainDataPruner implements BlockAddedObserver { + public static final int MAX_PRUNING_WORKER = 16; private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); private final BlockchainStorage blockchainStorage; private final ChainDataPrunerStorage prunerStorage; private final long blocksToRetain; private final long pruningFrequency; private final ExecutorService pruningExecutor; - private static final int MAX_PRUNING_WORKER = 16; public ChainDataPruner( final BlockchainStorage blockchainStorage, final ChainDataPrunerStorage prunerStorage, final long blocksToRetain, - final long pruningFrequency) { + final long pruningFrequency, + final ExecutorService pruningExecutor) { this.blockchainStorage = blockchainStorage; this.prunerStorage = prunerStorage; this.blocksToRetain = blocksToRetain; this.pruningFrequency = pruningFrequency; - this.pruningExecutor = - new ThreadPoolExecutor( - 1, - 1, - 60L, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), - new ThreadPoolExecutor.DiscardPolicy()); + this.pruningExecutor = pruningExecutor; } @Override @@ -79,7 +70,7 @@ public void onBlockAdded(final BlockAddedEvent event) { final long newPruningMark = blockNumber - blocksToRetain; if (event.isNewCanonicalHead() && newPruningMark - currentPruningMark >= pruningFrequency) { - long currentRetainedBlock = blockNumber - currentPruningMark; + long currentRetainedBlock = blockNumber - currentPruningMark + 1; while (currentRetainedBlock > blocksToRetain) { LOG.debug("Pruning chain data with block height of " + currentPruningMark); pruneChainDataAtBlock(tx, currentPruningMark); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index 5074fa4a99c..cbafb31b48b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.chain; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.chain.ChainDataPruner.MAX_PRUNING_WORKER; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; @@ -25,6 +26,8 @@ import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; @@ -40,7 +43,17 @@ public void singleChainPruning() { new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = new ChainDataPruner( - blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0); + blockchainStorage, + new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), + 512, + 0, + new ThreadPoolExecutor( + 1, + 1, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), + new ThreadPoolExecutor.DiscardPolicy())); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -75,7 +88,17 @@ public void forkPruning() { new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = new ChainDataPruner( - blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0); + blockchainStorage, + new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), + 512, + 0, + new ThreadPoolExecutor( + 1, + 1, + 60L, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), + new ThreadPoolExecutor.DiscardPolicy())); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( From 91102a7dc3fbc46ab58dfe5edb4fd92f7760ffe8 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 7 Dec 2022 21:57:21 +1000 Subject: [PATCH 24/29] New domain object Signed-off-by: wcgcyx --- .../org/hyperledger/besu/cli/BesuCommand.java | 6 +-- .../unstable/ChainDataPruningOptions.java | 37 +++++++++++++--- .../controller/BesuControllerBuilder.java | 31 +++++-------- .../chain/ChainPrunerConfiguration.java | 43 +++++++++++++++++++ 4 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.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 08f2d51fba9..ac8c9e00675 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -2192,11 +2192,7 @@ public BesuControllerBuilder getControllerBuilder() { .evmConfiguration(unstableEvmOptions.toDomainObject()) .dataStorageConfiguration(dataStorageOptions.toDomainObject()) .maxPeers(p2PDiscoveryOptionGroup.maxPeers) - .isChainDataPruningEnabled(unstableChainDataPruningOptions.getChainDataPruningEnabled()) - .chainDataPruningBlocksRetained( - unstableChainDataPruningOptions.getChainDataPruningBlocksRetained()) - .chainDataPruningFrequency( - unstableChainDataPruningOptions.getChainDataPruningBlocksFrequency()); + .chainPruningConfiguration(unstableChainDataPruningOptions.toDomainObject()); } private GraphQLConfiguration graphQLConfiguration() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java index 375c0337caf..47cdc501631 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java @@ -15,25 +15,33 @@ */ package org.hyperledger.besu.cli.options.unstable; +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.ethereum.chain.ChainPrunerConfiguration; import org.hyperledger.besu.util.number.PositiveNumber; -import picocli.CommandLine; +import java.util.Arrays; +import java.util.List; -public class ChainDataPruningOptions { +import picocli.CommandLine; +public class ChainDataPruningOptions implements CLIOptions { + private static final String CHAIN_PRUNING_ENABLED_FLAG = "--Xchain-pruning-enabled"; + private static final String CHAIN_PRUNING_BLOCKS_RETAINED_FLAG = + "--Xchain-pruning-blocks-retained"; + private static final String CHAIN_PRUNING_FREQUENCY_FLAG = "--Xchain-pruning-frequency"; public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 7200; public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @CommandLine.Option( hidden = true, - names = {"--Xchain-pruning-enabled"}, + names = {CHAIN_PRUNING_ENABLED_FLAG}, description = "Enable the chain pruner to actively prune old chain data (default: ${DEFAULT-VALUE})") private final Boolean chainDataPruningEnabled = Boolean.FALSE; @CommandLine.Option( hidden = true, - names = {"--Xchain-pruning-blocks-retained"}, + names = {CHAIN_PRUNING_BLOCKS_RETAINED_FLAG}, description = "The number of recent blocks for which to keep the chain data. Must be >= " + DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED @@ -43,7 +51,7 @@ public class ChainDataPruningOptions { @CommandLine.Option( hidden = true, - names = {"--Xchain-pruning-frequency"}, + names = {CHAIN_PRUNING_FREQUENCY_FLAG}, description = "The number of blocks added to the chain between two pruning operations. Must be non-negative (default: ${DEFAULT-VALUE})") private final PositiveNumber chainDataPruningBlocksFrequency = @@ -61,7 +69,22 @@ public Long getChainDataPruningBlocksRetained() { return chainDataPruningBlocksRetained; } - public Long getChainDataPruningBlocksFrequency() { - return (long) chainDataPruningBlocksFrequency.getValue(); + @Override + public ChainPrunerConfiguration toDomainObject() { + return new ChainPrunerConfiguration( + chainDataPruningEnabled, + chainDataPruningBlocksRetained, + chainDataPruningBlocksFrequency.getValue()); + } + + @Override + public List getCLIOptions() { + return Arrays.asList( + CHAIN_PRUNING_ENABLED_FLAG, + chainDataPruningEnabled.toString(), + CHAIN_PRUNING_BLOCKS_RETAINED_FLAG, + chainDataPruningBlocksRetained.toString(), + CHAIN_PRUNING_FREQUENCY_FLAG, + chainDataPruningBlocksFrequency.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 01f0b23ad7b..3546a45990b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.ChainDataPruner; import org.hyperledger.besu.ethereum.chain.ChainDataPrunerStorage; +import org.hyperledger.besu.ethereum.chain.ChainPrunerConfiguration; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -143,9 +144,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides Collections.emptyList(); protected EvmConfiguration evmConfiguration; protected int maxPeers; - protected boolean isChainDataPruningEnabled; - protected long chainDataPruningBlocksRetained; - protected long chainDataPruningFrequency; + protected ChainPrunerConfiguration chainPrunerConfiguration; public BesuControllerBuilder storageProvider(final StorageProvider storageProvider) { this.storageProvider = storageProvider; @@ -274,19 +273,9 @@ public BesuControllerBuilder maxPeers(final int maxPeers) { return this; } - public BesuControllerBuilder isChainDataPruningEnabled(final boolean isChainDataPruningEnabled) { - this.isChainDataPruningEnabled = isChainDataPruningEnabled; - return this; - } - - public BesuControllerBuilder chainDataPruningBlocksRetained( - final long chainDataPruningBlocksRetained) { - this.chainDataPruningBlocksRetained = chainDataPruningBlocksRetained; - return this; - } - - public BesuControllerBuilder chainDataPruningFrequency(final long chainDataPruningFrequency) { - this.chainDataPruningFrequency = chainDataPruningFrequency; + public BesuControllerBuilder chainPruningConfiguration( + final ChainPrunerConfiguration chainPrunerConfiguration) { + this.chainPrunerConfiguration = chainPrunerConfiguration; return this; } @@ -337,7 +326,7 @@ public BesuController build() { blockchain, worldStateArchive, protocolSchedule, this::createConsensusContext); validateContext(protocolContext); - if (isChainDataPruningEnabled) { + if (chainPrunerConfiguration.getChainPruningEnabled()) { protocolContext.setIsChainPruningEnabled(true); final ChainDataPruner chainDataPruner = new ChainDataPruner( @@ -345,8 +334,8 @@ public BesuController build() { new ChainDataPrunerStorage( storageProvider.getStorageBySegmentIdentifier( KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE)), - chainDataPruningBlocksRetained, - chainDataPruningFrequency, + chainPrunerConfiguration.getChainPruningBlocksRetained(), + chainPrunerConfiguration.getChainPruningBlocksFrequency(), MonitoredExecutors.newBoundedThreadPool( ChainDataPruner.class.getSimpleName(), 1, @@ -356,9 +345,9 @@ public BesuController build() { blockchain.observeBlockAdded(chainDataPruner); LOG.info( "Chain data pruning enabled with recent blocks retained to be: " - + chainDataPruningBlocksRetained + + chainPrunerConfiguration.getChainPruningBlocksRetained() + " and frequency to be: " - + chainDataPruningFrequency); + + chainPrunerConfiguration.getChainPruningBlocksFrequency()); } protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java new file mode 100644 index 00000000000..e327a74a382 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java @@ -0,0 +1,43 @@ +/* + * 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.chain; + +public class ChainPrunerConfiguration { + private final boolean chainPruningEnabled; + private final long chainPruningBlocksRetained; + private final long chainPruningBlocksFrequency; + + public ChainPrunerConfiguration( + final boolean chainPruningEnabled, + final long chainPruningBlocksRetained, + final long chainPruningBlocksFrequency) { + this.chainPruningEnabled = chainPruningEnabled; + this.chainPruningBlocksRetained = chainPruningBlocksRetained; + this.chainPruningBlocksFrequency = chainPruningBlocksFrequency; + } + + public long getChainPruningBlocksRetained() { + return chainPruningBlocksRetained; + } + + public boolean getChainPruningEnabled() { + return chainPruningEnabled; + } + + public long getChainPruningBlocksFrequency() { + return chainPruningBlocksFrequency; + } +} From 962adb6c7ad57d9ac0c457f8c35109209a9f78db Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 7 Dec 2022 22:41:53 +1000 Subject: [PATCH 25/29] Fix tests Signed-off-by: wcgcyx --- .../controller/BesuControllerBuilder.java | 2 +- .../besu/cli/CommandTestAbstract.java | 7 +-- .../besu/ethereum/chain/ChainDataPruner.java | 44 ++++++++++--------- .../chain/ChainPrunerConfiguration.java | 2 + 4 files changed, 28 insertions(+), 27 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 3546a45990b..7164fd797b0 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -144,7 +144,7 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides Collections.emptyList(); protected EvmConfiguration evmConfiguration; protected int maxPeers; - protected ChainPrunerConfiguration chainPrunerConfiguration; + protected ChainPrunerConfiguration chainPrunerConfiguration = ChainPrunerConfiguration.DEFAULT; public BesuControllerBuilder storageProvider(final StorageProvider storageProvider) { this.storageProvider = storageProvider; 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 d8363fb494f..d415f99cea8 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -226,12 +226,7 @@ public void initMocks() throws Exception { when(mockControllerBuilder.dataStorageConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.evmConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.isChainDataPruningEnabled(anyBoolean())) - .thenReturn(mockControllerBuilder); - when(mockControllerBuilder.chainDataPruningBlocksRetained(anyLong())) - .thenReturn(mockControllerBuilder); - when(mockControllerBuilder.chainDataPruningFrequency(anyLong())) - .thenReturn(mockControllerBuilder); + when(mockControllerBuilder.chainPruningConfiguration(any())).thenReturn(mockControllerBuilder); // doReturn used because of generic BesuController doReturn(mockController).when(mockControllerBuilder).build(); lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index ebbb296afef..267b87eb1c6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -48,38 +48,42 @@ public ChainDataPruner( @Override public void onBlockAdded(final BlockAddedEvent event) { + final long blockNumber = event.getBlock().getHeader().getNumber(); + final long storedPruningMark = prunerStorage.getPruningMark().orElse(blockNumber); + if (blockNumber < storedPruningMark) { + LOG.warn( + "Block added event: " + + event + + " has a block number of " + + blockNumber + + " < pruning mark " + + storedPruningMark + + " which normally indicates the blocksToRetain is too small"); + return; + } + final KeyValueStorageTransaction tx1 = prunerStorage.startTransaction(); + final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); + forkBlocks.add(event.getBlock().getHash()); + prunerStorage.setForkBlocks(tx1, blockNumber, forkBlocks); + tx1.commit(); + pruningExecutor.submit( () -> { - final long blockNumber = event.getBlock().getHeader().getNumber(); - long currentPruningMark = prunerStorage.getPruningMark().orElse(blockNumber); - if (blockNumber < currentPruningMark) { - LOG.warn( - "Block added event: " - + event - + " has a block number of " - + blockNumber - + " < pruning mark " - + currentPruningMark - + " which normally indicates the blocksToRetain is too small"); - return; - } - final KeyValueStorageTransaction tx = prunerStorage.startTransaction(); - final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); - forkBlocks.add(event.getBlock().getHash()); - prunerStorage.setForkBlocks(tx, blockNumber, forkBlocks); + final KeyValueStorageTransaction tx2 = prunerStorage.startTransaction(); + long currentPruningMark = storedPruningMark; final long newPruningMark = blockNumber - blocksToRetain; if (event.isNewCanonicalHead() && newPruningMark - currentPruningMark >= pruningFrequency) { long currentRetainedBlock = blockNumber - currentPruningMark + 1; while (currentRetainedBlock > blocksToRetain) { LOG.debug("Pruning chain data with block height of " + currentPruningMark); - pruneChainDataAtBlock(tx, currentPruningMark); + pruneChainDataAtBlock(tx2, currentPruningMark); currentPruningMark++; currentRetainedBlock = blockNumber - currentPruningMark; } } - prunerStorage.setPruningMark(tx, currentPruningMark); - tx.commit(); + prunerStorage.setPruningMark(tx2, currentPruningMark); + tx2.commit(); }); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java index e327a74a382..5d59704eaa2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java @@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum.chain; public class ChainPrunerConfiguration { + public static final ChainPrunerConfiguration DEFAULT = + new ChainPrunerConfiguration(false, 7200, 256); private final boolean chainPruningEnabled; private final long chainPruningBlocksRetained; private final long chainPruningBlocksFrequency; From 1368c55b52637431c3f16f4a237ab165413b8484 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 7 Dec 2022 23:09:03 +1000 Subject: [PATCH 26/29] Move flag to merge context Signed-off-by: wcgcyx --- .../besu/controller/BesuControllerBuilder.java | 2 +- .../besu/consensus/merge/MergeContext.java | 6 ++++++ .../besu/consensus/merge/PostMergeContext.java | 14 ++++++++++++++ .../methods/engine/EngineForkchoiceUpdated.java | 2 +- .../internal/methods/engine/EngineNewPayload.java | 2 +- .../hyperledger/besu/ethereum/ProtocolContext.java | 11 ----------- 6 files changed, 23 insertions(+), 14 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 7164fd797b0..337d238f7ce 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -327,7 +327,7 @@ public BesuController build() { validateContext(protocolContext); if (chainPrunerConfiguration.getChainPruningEnabled()) { - protocolContext.setIsChainPruningEnabled(true); + protocolContext.getConsensusContext(MergeContext.class).setIsChainPruningEnabled(true); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java index eb06c53fe9d..c1c217788fb 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java @@ -65,4 +65,10 @@ void fireNewUnverifiedForkchoiceEvent( void putPayloadById(final PayloadIdentifier payloadId, final Block block); Optional retrieveBlockById(final PayloadIdentifier payloadId); + + default void setIsChainPruningEnabled(final boolean isChainPruningEnabled) {} + + default boolean isChainPruningEnabled() { + return false; + } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java index 2ce00d83cb2..c1f87fe846f 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java @@ -64,6 +64,10 @@ public class PostMergeContext implements MergeContext { private final AtomicReference> terminalPoWBlock = new AtomicReference<>(Optional.empty()); + // TODO: cleanup - isChainPruningEnabled will not be required after + // https://github.com/hyperledger/besu/pull/4703 is merged. + private boolean isChainPruningEnabled = false; + @VisibleForTesting PostMergeContext() { this(Difficulty.ZERO); @@ -265,4 +269,14 @@ private static class PayloadTuple { this.block = block; } } + + @Override + public void setIsChainPruningEnabled(final boolean isChainPruningEnabled) { + this.isChainPruningEnabled = isChainPruningEnabled; + } + + @Override + public boolean isChainPruningEnabled() { + return isChainPruningEnabled; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java index 2aeee13001e..787c3509071 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdated.java @@ -120,7 +120,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup, this should be unnecessary after merge if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newHead) - && !protocolContext.isChainPruningEnabled()) { + && !mergeContext.get().isChainPruningEnabled()) { logForkchoiceUpdatedCall(INVALID, forkChoice); return new JsonRpcSuccessResponse( requestId, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java index 64e47ad6863..ff3c8eab554 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayload.java @@ -197,7 +197,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) // TODO: post-merge cleanup if (!mergeCoordinator.latestValidAncestorDescendsFromTerminal(newBlockHeader) - && !protocolContext.isChainPruningEnabled()) { + && !mergeContext.get().isChainPruningEnabled()) { mergeCoordinator.addBadBlock(block, Optional.empty()); return respondWithInvalid( reqId, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index a942542b87f..0b115937053 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -29,7 +29,6 @@ public class ProtocolContext { private final MutableBlockchain blockchain; private final WorldStateArchive worldStateArchive; private final ConsensusContext consensusContext; - private boolean isChainPruningEnabled = false; public ProtocolContext( final MutableBlockchain blockchain, @@ -68,14 +67,4 @@ public Optional safeConsensusContext(final Class .filter(c -> klass.isAssignableFrom(c.getClass())) .map(klass::cast); } - - // TODO: cleanup - isChainPruningEnabled will not be required after - // https://github.com/hyperledger/besu/pull/4703 is merged. - public void setIsChainPruningEnabled(final boolean isChainPruningEnabled) { - this.isChainPruningEnabled = isChainPruningEnabled; - } - - public boolean isChainPruningEnabled() { - return isChainPruningEnabled; - } } From 2a439f152cdbd7b0ecd29252fa4e123bd9895eca Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 9 Dec 2022 09:52:31 +1000 Subject: [PATCH 27/29] Renaming Signed-off-by: wcgcyx --- CHANGELOG.md | 2 -- .../hyperledger/besu/controller/BesuControllerBuilder.java | 2 +- .../hyperledger/besu/ethereum/chain/ChainDataPruner.java | 4 ++-- .../besu/ethereum/chain/ChainDataPrunerTest.java | 6 +++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67350d45cb3..a1dec47dffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,6 @@ ### Additions and Improvements - Implement Eth/68 sub-protocol [#4715](https://github.com/hyperledger/besu/issues/4715) - Increase the speed of modexp gas execution and execution. [#4780](https://github.com/hyperledger/besu/pull/4780) - -### Additions and Improvements - Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-enabled`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes 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 337d238f7ce..a21895ff97f 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -340,7 +340,7 @@ public BesuController build() { ChainDataPruner.class.getSimpleName(), 1, 1, - ChainDataPruner.MAX_PRUNING_WORKER, + ChainDataPruner.MAX_PRUNING_THREAD_QUEUE_SIZE, metricsSystem)); blockchain.observeBlockAdded(chainDataPruner); LOG.info( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 267b87eb1c6..edc8ce14514 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory; public class ChainDataPruner implements BlockAddedObserver { - public static final int MAX_PRUNING_WORKER = 16; + public static final int MAX_PRUNING_THREAD_QUEUE_SIZE = 16; private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class); private final BlockchainStorage blockchainStorage; private final ChainDataPrunerStorage prunerStorage; @@ -58,7 +58,7 @@ public void onBlockAdded(final BlockAddedEvent event) { + blockNumber + " < pruning mark " + storedPruningMark - + " which normally indicates the blocksToRetain is too small"); + + " which normally indicates chain-pruning-blocks-retained is too small"); return; } final KeyValueStorageTransaction tx1 = prunerStorage.startTransaction(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index cbafb31b48b..93e4005cb9b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.chain; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.chain.ChainDataPruner.MAX_PRUNING_WORKER; +import static org.hyperledger.besu.ethereum.chain.ChainDataPruner.MAX_PRUNING_THREAD_QUEUE_SIZE; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; @@ -52,7 +52,7 @@ public void singleChainPruning() { 1, 60L, TimeUnit.SECONDS, - new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), + new ArrayBlockingQueue<>(MAX_PRUNING_THREAD_QUEUE_SIZE), new ThreadPoolExecutor.DiscardPolicy())); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = @@ -97,7 +97,7 @@ public void forkPruning() { 1, 60L, TimeUnit.SECONDS, - new ArrayBlockingQueue<>(MAX_PRUNING_WORKER), + new ArrayBlockingQueue<>(MAX_PRUNING_THREAD_QUEUE_SIZE), new ThreadPoolExecutor.DiscardPolicy())); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = From d29a28649d52cc40a798f1f0e7202adf1f26ed9a Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 9 Dec 2022 15:37:00 +1000 Subject: [PATCH 28/29] Rename tx Signed-off-by: wcgcyx --- .../besu/ethereum/chain/ChainDataPruner.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index edc8ce14514..87605e3ff66 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -61,15 +61,16 @@ public void onBlockAdded(final BlockAddedEvent event) { + " which normally indicates chain-pruning-blocks-retained is too small"); return; } - final KeyValueStorageTransaction tx1 = prunerStorage.startTransaction(); + final KeyValueStorageTransaction recordBlockHashesTransaction = + prunerStorage.startTransaction(); final Collection forkBlocks = prunerStorage.getForkBlocks(blockNumber); forkBlocks.add(event.getBlock().getHash()); - prunerStorage.setForkBlocks(tx1, blockNumber, forkBlocks); - tx1.commit(); + prunerStorage.setForkBlocks(recordBlockHashesTransaction, blockNumber, forkBlocks); + recordBlockHashesTransaction.commit(); pruningExecutor.submit( () -> { - final KeyValueStorageTransaction tx2 = prunerStorage.startTransaction(); + final KeyValueStorageTransaction pruningTransaction = prunerStorage.startTransaction(); long currentPruningMark = storedPruningMark; final long newPruningMark = blockNumber - blocksToRetain; if (event.isNewCanonicalHead() @@ -77,13 +78,13 @@ public void onBlockAdded(final BlockAddedEvent event) { long currentRetainedBlock = blockNumber - currentPruningMark + 1; while (currentRetainedBlock > blocksToRetain) { LOG.debug("Pruning chain data with block height of " + currentPruningMark); - pruneChainDataAtBlock(tx2, currentPruningMark); + pruneChainDataAtBlock(pruningTransaction, currentPruningMark); currentPruningMark++; currentRetainedBlock = blockNumber - currentPruningMark; } } - prunerStorage.setPruningMark(tx2, currentPruningMark); - tx2.commit(); + prunerStorage.setPruningMark(pruningTransaction, currentPruningMark); + pruningTransaction.commit(); }); } From 25d514dbc5429031edd56086a7600ca8334aac9d Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 9 Dec 2022 17:41:00 +1000 Subject: [PATCH 29/29] Fix issues Signed-off-by: wcgcyx --- CHANGELOG.md | 2 +- .../org/hyperledger/besu/cli/BesuCommand.java | 17 ++++----- ...gOptions.java => ChainPruningOptions.java} | 6 +-- .../controller/BesuControllerBuilder.java | 38 +++++++++++-------- ethereum/core/build.gradle | 2 +- .../besu/ethereum/chain/ChainDataPruner.java | 6 +-- .../chain/ChainPrunerConfiguration.java | 22 +++++------ 7 files changed, 49 insertions(+), 44 deletions(-) rename besu/src/main/java/org/hyperledger/besu/cli/options/unstable/{ChainDataPruningOptions.java => ChainPruningOptions.java} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde1e4c8531..6acc854eedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Increase the speed of modexp gas execution and execution. [#4780](https://github.com/hyperledger/besu/pull/4780) - Added experimental CLI options `--Xeth-capability-max` and `--Xeth-capability-min` to specify a range of capabilities to be supported by the Eth protocol. [#4752](https://github.com/hyperledger/besu/pull/4752) - Set the default curve in the EVMTool, like is done in production operations [#4790](https://github.com/hyperledger/besu/pull/4790) -- Add chain data pruning feature with three experimental CLI options: `--Xchain-data-pruning-enabled`, `--Xchain-data-pruning-blocks-retained` and `--Xchain-data-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) +- Add chain data pruning feature with three experimental CLI options: `--Xchain-pruning-enabled`, `--Xchain-pruning-blocks-retained` and `--Xchain-pruning-frequency` [#4686](https://github.com/hyperledger/besu/pull/4686) ### Bug Fixes 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 840d3fa96ee..0521e147eab 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -56,7 +56,7 @@ import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; -import org.hyperledger.besu.cli.options.unstable.ChainDataPruningOptions; +import org.hyperledger.besu.cli.options.unstable.ChainPruningOptions; import org.hyperledger.besu.cli.options.unstable.DnsOptions; import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; import org.hyperledger.besu.cli.options.unstable.EvmOptions; @@ -290,8 +290,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final PrivacyPluginOptions unstablePrivacyPluginOptions = PrivacyPluginOptions.create(); private final EvmOptions unstableEvmOptions = EvmOptions.create(); private final IpcOptions unstableIpcOptions = IpcOptions.create(); - private final ChainDataPruningOptions unstableChainDataPruningOptions = - ChainDataPruningOptions.create(); + private final ChainPruningOptions unstableChainPruningOptions = ChainPruningOptions.create(); // stable CLI options private final DataStorageOptions dataStorageOptions = DataStorageOptions.create(); @@ -1542,7 +1541,7 @@ private void handleUnstableOptions() { .put("Launcher", unstableLauncherOptions) .put("EVM Options", unstableEvmOptions) .put("IPC Options", unstableIpcOptions) - .put("Chain Data Pruning Options", unstableChainDataPruningOptions) + .put("Chain Data Pruning Options", unstableChainPruningOptions) .build(); UnstableOptionsSubCommand.createUnstableOptions(commandLine, unstableOptions); @@ -1933,13 +1932,13 @@ public void validateRpcOptionsParams() { } public void validateChainDataPruningParams() { - if (unstableChainDataPruningOptions.getChainDataPruningEnabled() - && unstableChainDataPruningOptions.getChainDataPruningBlocksRetained() - < ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { + if (unstableChainPruningOptions.getChainDataPruningEnabled() + && unstableChainPruningOptions.getChainDataPruningBlocksRetained() + < ChainPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { throw new ParameterException( this.commandLine, "--Xchain-pruning-blocks-retained must be >= " - + ChainDataPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); + + ChainPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); } } @@ -2201,7 +2200,7 @@ public BesuControllerBuilder getControllerBuilder() { .evmConfiguration(unstableEvmOptions.toDomainObject()) .dataStorageConfiguration(dataStorageOptions.toDomainObject()) .maxPeers(p2PDiscoveryOptionGroup.maxPeers) - .chainPruningConfiguration(unstableChainDataPruningOptions.toDomainObject()); + .chainPruningConfiguration(unstableChainPruningOptions.toDomainObject()); } private GraphQLConfiguration graphQLConfiguration() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java similarity index 94% rename from besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java rename to besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java index 47cdc501631..5b90e9281d6 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainDataPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java @@ -24,7 +24,7 @@ import picocli.CommandLine; -public class ChainDataPruningOptions implements CLIOptions { +public class ChainPruningOptions implements CLIOptions { private static final String CHAIN_PRUNING_ENABLED_FLAG = "--Xchain-pruning-enabled"; private static final String CHAIN_PRUNING_BLOCKS_RETAINED_FLAG = "--Xchain-pruning-blocks-retained"; @@ -57,8 +57,8 @@ public class ChainDataPruningOptions implements CLIOptions { + mergeContext.setIsChainPruningEnabled(true); + }); + final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage); blockchain.observeBlockAdded(chainDataPruner); LOG.info( "Chain data pruning enabled with recent blocks retained to be: " @@ -686,6 +678,22 @@ private WorldStateArchive createWorldStateArchive( } } + private ChainDataPruner createChainPruner(final BlockchainStorage blockchainStorage) { + return new ChainDataPruner( + blockchainStorage, + new ChainDataPrunerStorage( + storageProvider.getStorageBySegmentIdentifier( + KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE)), + chainPrunerConfiguration.getChainPruningBlocksRetained(), + chainPrunerConfiguration.getChainPruningBlocksFrequency(), + MonitoredExecutors.newBoundedThreadPool( + ChainDataPruner.class.getSimpleName(), + 1, + 1, + ChainDataPruner.MAX_PRUNING_THREAD_QUEUE_SIZE, + metricsSystem)); + } + protected List createPeerValidators(final ProtocolSchedule protocolSchedule) { final List validators = new ArrayList<>(); diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index a019a1a1729..e835c828d66 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -56,7 +56,6 @@ dependencies { implementation 'org.apache.tuweni:tuweni-rlp' implementation 'org.hyperledger.besu:bls12-381' implementation 'org.immutables:value-annotations' - implementation 'org.awaitility:awaitility' implementation 'io.prometheus:simpleclient_guava' @@ -82,6 +81,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-core' + testImplementation 'org.awaitility:awaitility' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 87605e3ff66..7807786948d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -73,8 +73,8 @@ public void onBlockAdded(final BlockAddedEvent event) { final KeyValueStorageTransaction pruningTransaction = prunerStorage.startTransaction(); long currentPruningMark = storedPruningMark; final long newPruningMark = blockNumber - blocksToRetain; - if (event.isNewCanonicalHead() - && newPruningMark - currentPruningMark >= pruningFrequency) { + final long blocksToBePruned = newPruningMark - currentPruningMark; + if (event.isNewCanonicalHead() && blocksToBePruned >= pruningFrequency) { long currentRetainedBlock = blockNumber - currentPruningMark + 1; while (currentRetainedBlock > blocksToRetain) { LOG.debug("Pruning chain data with block height of " + currentPruningMark); @@ -91,7 +91,7 @@ public void onBlockAdded(final BlockAddedEvent event) { private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) { final Collection oldForkBlocks = prunerStorage.getForkBlocks(blockNumber); final BlockchainStorage.Updater updater = blockchainStorage.updater(); - for (Hash toPrune : oldForkBlocks) { + for (final Hash toPrune : oldForkBlocks) { updater.removeBlockHeader(toPrune); updater.removeBlockBody(toPrune); updater.removeTransactionReceipts(toPrune); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java index 5d59704eaa2..99ac9bc486d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java @@ -18,28 +18,26 @@ public class ChainPrunerConfiguration { public static final ChainPrunerConfiguration DEFAULT = new ChainPrunerConfiguration(false, 7200, 256); - private final boolean chainPruningEnabled; - private final long chainPruningBlocksRetained; - private final long chainPruningBlocksFrequency; + private final boolean enabled; + private final long blocksRetained; + private final long blocksFrequency; public ChainPrunerConfiguration( - final boolean chainPruningEnabled, - final long chainPruningBlocksRetained, - final long chainPruningBlocksFrequency) { - this.chainPruningEnabled = chainPruningEnabled; - this.chainPruningBlocksRetained = chainPruningBlocksRetained; - this.chainPruningBlocksFrequency = chainPruningBlocksFrequency; + final boolean enabled, final long blocksRetained, final long blocksFrequency) { + this.enabled = enabled; + this.blocksRetained = blocksRetained; + this.blocksFrequency = blocksFrequency; } public long getChainPruningBlocksRetained() { - return chainPruningBlocksRetained; + return blocksRetained; } public boolean getChainPruningEnabled() { - return chainPruningEnabled; + return enabled; } public long getChainPruningBlocksFrequency() { - return chainPruningBlocksFrequency; + return blocksFrequency; } }