Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
daecbb7
Add pre-merge pruning options to ChainPruningOptions
Matilda-Clerke Apr 1, 2025
749fe64
Implement pre-merge block pruning in ChainDataPruner
Matilda-Clerke Apr 2, 2025
388f631
Add an info log to preMergePruningAction
Matilda-Clerke Apr 2, 2025
eed10b3
Add logging
Matilda-Clerke Apr 3, 2025
5d893b5
Fix database setup
Matilda-Clerke Apr 4, 2025
5b12159
Unsubscribe after finishing pruning
Matilda-Clerke Apr 8, 2025
1931d3f
Enable garbage collection of blobs in static data segments
Matilda-Clerke Apr 8, 2025
17c8c16
Change start of pre-merge block tuning from 0 to 1 to preserve the ge…
Matilda-Clerke Apr 14, 2025
9063878
Suggest garbage collection to try to avoid build up
Matilda-Clerke Apr 15, 2025
a0c3ce9
Fix missing space in option description in ChainPruningOptions.java
Matilda-Clerke Apr 28, 2025
e9e5cb0
Fix missing space in option description in ChainPruningOptions.java
Matilda-Clerke Apr 28, 2025
823c930
Rework ChainDataPruner changes
Matilda-Clerke Apr 28, 2025
d87b84a
Remove System.gc call
Matilda-Clerke Apr 30, 2025
24a954e
Move merge block number into NetworkName
Matilda-Clerke May 5, 2025
7ec3015
Throttle pre-merge pruning progress logs to one per 5 minutes
Matilda-Clerke May 5, 2025
faa9a0f
Allow different static data to have different garbage collection enab…
Matilda-Clerke May 5, 2025
cb73c1a
Update ChainDataPruner threadpool to match with metrics naming schemes
Matilda-Clerke May 5, 2025
ec9ba28
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 5, 2025
51173f6
Add 1 second sleep at start of pre-merge pruning action
Matilda-Clerke May 7, 2025
b7611b7
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 8, 2025
458ceea
Keep transaction difficulty to be consistent with updated sync
Matilda-Clerke May 8, 2025
db8e25c
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 9, 2025
0b7a74a
Adjust logging of a finish notification
Matilda-Clerke May 9, 2025
53580c9
Turn off staticDataGarbageCollection for BLOCKCHAIN
Matilda-Clerke May 9, 2025
7148efe
Rename constant to include time unit
Matilda-Clerke May 9, 2025
9c9bb01
spotless
Matilda-Clerke May 9, 2025
210b06c
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 19, 2025
e0c5554
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 19, 2025
2b8cf7e
Add debug log for pre-merge pruning progress
Matilda-Clerke May 20, 2025
7182cf8
Update NetworkName.mergeBlockNumber to firstPosBlockNumber
Matilda-Clerke May 23, 2025
02390e5
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 23, 2025
84eaf2c
Change pre-merge block pruning enablement to use data flag
Matilda-Clerke May 26, 2025
c197b1e
Add stack trace to start of pruning debug log to debug source of mult…
Matilda-Clerke May 26, 2025
f5c3c65
Move setIgnorableStorageSegments to after configure call
Matilda-Clerke May 26, 2025
5769dea
Revert "Add stack trace to start of pruning debug log to debug source…
Matilda-Clerke May 26, 2025
d7f062f
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 26, 2025
883ac25
Only execute preMergePruningAction for new canonical head
Matilda-Clerke May 27, 2025
a106767
spotless
Matilda-Clerke May 27, 2025
be85913
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 27, 2025
c74540c
Use checkpoint in genesis file and remove first PoS block from Networ…
Matilda-Clerke May 27, 2025
229dfdb
Merge remote-tracking branch 'origin/8337-modify-chain-data-pruner' i…
Matilda-Clerke May 27, 2025
18479be
Add unit test for new ChainDataPruner functionality
Matilda-Clerke May 29, 2025
3dcfc69
Merge branch 'main' into 8337-modify-chain-data-pruner
Matilda-Clerke May 29, 2025
67f7978
Set default ChainDataPruner pre merge batch size to 100
Matilda-Clerke May 29, 2025
b03d76d
Merge remote-tracking branch 'origin/8337-modify-chain-data-pruner' i…
Matilda-Clerke May 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -2662,7 +2662,8 @@ private void setMergeConfigOptions() {

/** Set ignorable segments in RocksDB Storage Provider plugin. */
public void setIgnorableStorageSegments() {
if (!unstableChainPruningOptions.getChainDataPruningEnabled()) {
if (!unstableChainPruningOptions.getChainDataPruningEnabled()
&& !unstableChainPruningOptions.getPreMergePruningEnabled()) {
rocksDBPlugin.addIgnorableSegmentIdentifier(KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
/** The Chain pruning CLI options. */
public class ChainPruningOptions implements CLIOptions<ChainPrunerConfiguration> {
private static final String CHAIN_PRUNING_ENABLED_FLAG = "--Xchain-pruning-enabled";
private static final String PRE_MERGE_PRUNING_ENABLED_FLAG = "--Xpre-merge-pruning-enabled";
private static final String CHAIN_PRUNING_BLOCKS_RETAINED_FLAG =
"--Xchain-pruning-blocks-retained";
private static final String CHAIN_PRUNING_BLOCKS_RETAINED_LIMIT_FLAG =
"--Xchain-pruning-blocks-retained-limit";
private static final String CHAIN_PRUNING_FREQUENCY_FLAG = "--Xchain-pruning-frequency";
private static final String PRE_MERGE_PRUNING_QUANTITY_FLAG = "--Xpre-merge-pruning-quantity";

/**
* The "CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT" field sets the minimum limit for the
Expand All @@ -42,20 +44,32 @@ public class ChainPruningOptions implements CLIOptions<ChainPrunerConfiguration>
/** The constant DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY. */
public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256;

/** The constant DEFAULT_PRE_MERGE_PRUNING_QUANTITY. */
public static final int DEFAULT_PRE_MERGE_PRUNING_QUANTITY = 256;

@CommandLine.Option(
hidden = true,
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 = {PRE_MERGE_PRUNING_ENABLED_FLAG},
description =
"Enable the chain pruner to actively prune pre-merge blocks, but not headers (default: ${DEFAULT-VALUE})")
private final Boolean preMergePruningEnabled = Boolean.FALSE;
Copy link
Contributor

@garyschulte garyschulte Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to see chain pruning be contingent on the sync mode in the same way trielog pruning is. Like a typical node would want to prune pre-merge history, but a full-sync node would not.

The network pre-merge cut-off could be an empty Optional for all networks except those that have at release time entered the proto-4444 window. This way chain pruning could always be enabled, but the presence or absence of a pruning cutoff is what causes the behavior change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it would be nice to enable pruning by default, I think users might be upset by their stored history being deleted unexpectedly.


@CommandLine.Option(
hidden = true,
names = {CHAIN_PRUNING_BLOCKS_RETAINED_FLAG},
description =
"The number of recent blocks for which to keep the chain data. Should be >= "
+ CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT
+ " (default: ${DEFAULT-VALUE})")
+ " (default: ${DEFAULT-VALUE}). Unused if "
+ PRE_MERGE_PRUNING_ENABLED_FLAG
+ " is enabled")
private final Long chainDataPruningBlocksRetained = CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT;

@CommandLine.Option(
Expand All @@ -65,7 +79,9 @@ public class ChainPruningOptions implements CLIOptions<ChainPrunerConfiguration>
"Allows setting the limit below which no more blocks can be pruned. This prevents setting a value lower than this for "
+ CHAIN_PRUNING_BLOCKS_RETAINED_FLAG
+ ". This flag should be used with caution as reducing the limit may have unintended side effects."
+ " (default: ${DEFAULT-VALUE})")
+ " (default: ${DEFAULT-VALUE}). Unused if "
+ PRE_MERGE_PRUNING_ENABLED_FLAG
+ " is enabled")
private final Long chainDataPruningBlocksRetainedLimit =
CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT;

Expand All @@ -77,6 +93,14 @@ public class ChainPruningOptions implements CLIOptions<ChainPrunerConfiguration>
private final PositiveNumber chainDataPruningBlocksFrequency =
PositiveNumber.fromInt(DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY);

@CommandLine.Option(
hidden = true,
names = {PRE_MERGE_PRUNING_QUANTITY_FLAG},
description =
"The number of pre-merge blocks to prune per pruning operation. Must be non-negative (default: ${DEFAULT-VALUE})")
private final PositiveNumber preMergePruningBlocksQuantity =
PositiveNumber.fromInt(DEFAULT_PRE_MERGE_PRUNING_QUANTITY);

/** Default Constructor. */
ChainPruningOptions() {}

Expand All @@ -98,6 +122,15 @@ public Boolean getChainDataPruningEnabled() {
return chainDataPruningEnabled;
}

/**
* Gets pre-merge pruning enabled
*
* @return the pre-merge pruning enabled
*/
public Boolean getPreMergePruningEnabled() {
return preMergePruningEnabled;
}

/**
* Gets chain data pruning blocks retained.
*
Expand All @@ -120,21 +153,27 @@ public Long getChainDataPruningBlocksRetainedLimit() {
public ChainPrunerConfiguration toDomainObject() {
return new ChainPrunerConfiguration(
chainDataPruningEnabled,
preMergePruningEnabled,
chainDataPruningBlocksRetained,
chainDataPruningBlocksRetainedLimit,
chainDataPruningBlocksFrequency.getValue());
chainDataPruningBlocksFrequency.getValue(),
preMergePruningBlocksQuantity.getValue());
}

@Override
public List<String> getCLIOptions() {
return Arrays.asList(
CHAIN_PRUNING_ENABLED_FLAG,
chainDataPruningEnabled.toString(),
PRE_MERGE_PRUNING_ENABLED_FLAG,
preMergePruningEnabled.toString(),
CHAIN_PRUNING_BLOCKS_RETAINED_FLAG,
chainDataPruningBlocksRetained.toString(),
CHAIN_PRUNING_BLOCKS_RETAINED_LIMIT_FLAG,
chainDataPruningBlocksRetainedLimit.toString(),
CHAIN_PRUNING_FREQUENCY_FLAG,
chainDataPruningBlocksFrequency.toString());
chainDataPruningBlocksFrequency.toString(),
PRE_MERGE_PRUNING_QUANTITY_FLAG,
preMergePruningBlocksQuantity.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static com.google.common.base.Preconditions.checkNotNull;

import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.components.BesuComponent;
import org.hyperledger.besu.config.CheckpointConfigOptions;
import org.hyperledger.besu.config.GenesisConfig;
Expand Down Expand Up @@ -115,8 +116,10 @@
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -125,6 +128,9 @@
public abstract class BesuControllerBuilder implements MiningParameterOverrides {
private static final Logger LOG = LoggerFactory.getLogger(BesuControllerBuilder.class);

private static final long MAINNET_MERGE_BLOCK_NUMBER = 15_537_393;
private static final long SEPOLIA_MERGE_BLOCK_NUMBER = 1_735_371;

/** The genesis file */
protected GenesisConfig genesisConfig;

Expand Down Expand Up @@ -695,14 +701,27 @@ public BesuController build() {
final boolean fullSyncDisabled = !SyncMode.isFullSync(syncConfig.getSyncMode());
final SyncState syncState = new SyncState(blockchain, ethPeers, fullSyncDisabled, checkpoint);

if (chainPrunerConfiguration.getChainPruningEnabled()) {
final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage);
blockchain.observeBlockAdded(chainDataPruner);
LOG.info(
"Chain data pruning enabled with recent blocks retained to be: "
+ chainPrunerConfiguration.getChainPruningBlocksRetained()
+ " and frequency to be: "
+ chainPrunerConfiguration.getChainPruningBlocksFrequency());
if (chainPrunerConfiguration.chainPruningEnabled()
|| chainPrunerConfiguration.preMergePruningEnabled()) {
LOG.info("Adding ChainDataPruner to observe block added events");
final AtomicLong chainDataPrunerObserverId = new AtomicLong();
final ChainDataPruner chainDataPruner =
createChainPruner(
blockchainStorage, () -> blockchain.removeObserver(chainDataPrunerObserverId.get()));
chainDataPrunerObserverId.set(blockchain.observeBlockAdded(chainDataPruner));
if (chainPrunerConfiguration.chainPruningEnabled()) {
LOG.info(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good user feedback.

"Chain data pruning enabled with recent blocks retained to be: "
+ chainPrunerConfiguration.chainPruningBlocksRetained()
+ " and frequency to be: "
+ chainPrunerConfiguration.blocksFrequency());
} else if (chainPrunerConfiguration.preMergePruningEnabled()) {
LOG.info(
"Pre-merge block pruning enabled with frequency: "
+ chainPrunerConfiguration.blocksFrequency()
+ " and quantity: "
+ chainPrunerConfiguration.preMergePruningBlocksQuantity());
}
}

final TransactionPool transactionPool =
Expand Down Expand Up @@ -1159,14 +1178,32 @@ yield new ForestWorldStateArchive(
};
}

private ChainDataPruner createChainPruner(final BlockchainStorage blockchainStorage) {
private ChainDataPruner createChainPruner(
final BlockchainStorage blockchainStorage, final Runnable unsubscribeRunnable) {
NetworkName network =
Stream.of(NetworkName.values())
.filter((n) -> n.getNetworkId().equals(networkId))
.findAny()
.orElseThrow(() -> new RuntimeException("Unrecognised network"));
return new ChainDataPruner(
blockchainStorage,
unsubscribeRunnable,
new ChainDataPrunerStorage(
storageProvider.getStorageBySegmentIdentifier(
KeyValueSegmentIdentifier.CHAIN_PRUNER_STATE)),
chainPrunerConfiguration.getChainPruningBlocksRetained(),
chainPrunerConfiguration.getChainPruningBlocksFrequency(),
switch (network) {
case MAINNET -> MAINNET_MERGE_BLOCK_NUMBER;
case SEPOLIA -> SEPOLIA_MERGE_BLOCK_NUMBER;
default -> 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a network is cancun at genesis, like hoodi is, then we don't do any PRE_MERGE_PRUNING, which makes sense. We would start CHAIN_PRUNING once we got to block 256. However, 6110 doesn't activate till pectra does. Might we have an inconsistency wrt deposits in the interim between them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also prefer using the checkpoint at Genesis for pruning configuration, as it aligns with the approach taken in #8582

},
chainPrunerConfiguration.chainPruningEnabled()
? ChainDataPruner.Mode.CHAIN_PRUNING
: (chainPrunerConfiguration.preMergePruningEnabled()
? ChainDataPruner.Mode.PRE_MERGE_PRUNING
: null),
chainPrunerConfiguration.chainPruningBlocksRetained(),
chainPrunerConfiguration.blocksFrequency(),
chainPrunerConfiguration.preMergePruningBlocksQuantity(),
MonitoredExecutors.newBoundedThreadPool(
ChainDataPruner.class.getSimpleName(),
1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,50 @@
import org.slf4j.LoggerFactory;

public class ChainDataPruner implements BlockAddedObserver {
public static final int MAX_PRUNING_THREAD_QUEUE_SIZE = 16;
private static final Logger LOG = LoggerFactory.getLogger(ChainDataPruner.class);

public static final int MAX_PRUNING_THREAD_QUEUE_SIZE = 16;

private final BlockchainStorage blockchainStorage;
private final Runnable unsubscribeRunnable;
private final ChainDataPrunerStorage prunerStorage;
private final long mergeBlock;
private final Mode mode;
private final long blocksToRetain;
private final long pruningFrequency;
private final long pruningQuantity;
private final ExecutorService pruningExecutor;

public ChainDataPruner(
final BlockchainStorage blockchainStorage,
final Runnable unsubscribeRunnable,
final ChainDataPrunerStorage prunerStorage,
final long mergeBlock,
final Mode mode,
final long blocksToRetain,
final long pruningFrequency,
final long pruningQuantity,
final ExecutorService pruningExecutor) {
this.blockchainStorage = blockchainStorage;
this.unsubscribeRunnable = unsubscribeRunnable;
this.prunerStorage = prunerStorage;
this.mergeBlock = mergeBlock;
this.mode = mode;
this.blocksToRetain = blocksToRetain;
this.pruningFrequency = pruningFrequency;
this.pruningExecutor = pruningExecutor;
this.pruningQuantity = pruningQuantity;
}

@Override
public void onBlockAdded(final BlockAddedEvent event) {
switch (mode) {
case CHAIN_PRUNING -> chainPrunerAction(event);
case PRE_MERGE_PRUNING -> preMergePruningAction();
}
}

private void chainPrunerAction(final BlockAddedEvent event) {
final long blockNumber = event.getBlock().getHeader().getNumber();
final long storedPruningMark = prunerStorage.getPruningMark().orElse(blockNumber);
if (blockNumber < storedPruningMark) {
Expand Down Expand Up @@ -87,6 +108,46 @@ public void onBlockAdded(final BlockAddedEvent event) {
});
}

private void preMergePruningAction() {
pruningExecutor.submit(
() -> {
final long storedPruningMark = prunerStorage.getPruningMark().orElse(1L);
final long expectedNewPruningMark =
Math.min(storedPruningMark + pruningQuantity, mergeBlock);
final KeyValueStorageTransaction pruningTransaction = prunerStorage.startTransaction();
final BlockchainStorage.Updater updater = blockchainStorage.updater();
for (long blockNumber = storedPruningMark;
blockNumber < expectedNewPruningMark;
blockNumber++) {
blockchainStorage
.getBlockHash(blockNumber)
.ifPresent(
(blockHash) -> {
updater.removeBlockBody(blockHash);
updater.removeTransactionReceipts(blockHash);
updater.removeTotalDifficulty(blockHash);
blockchainStorage
.getBlockBody(blockHash)
.ifPresent(
blockBody ->
blockBody
.getTransactions()
.forEach(
t -> updater.removeTransactionLocation(t.getHash())));
});
}
updater.commit();
prunerStorage.setPruningMark(pruningTransaction, expectedNewPruningMark);
pruningTransaction.commit();
LOG.info("Pruned blocks {} to {}", storedPruningMark, expectedNewPruningMark);
if (expectedNewPruningMark == mergeBlock) {
LOG.info(
"Done pruning pre-merge blocks. Unsubscribing from block added event observation");
unsubscribeRunnable.run();
}
});
}

private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final long blockNumber) {
final Collection<Hash> oldForkBlocks = prunerStorage.getForkBlocks(blockNumber);
final BlockchainStorage.Updater updater = blockchainStorage.updater();
Expand All @@ -107,4 +168,9 @@ private void pruneChainDataAtBlock(final KeyValueStorageTransaction tx, final lo
updater.commit();
prunerStorage.removeForkBlocks(tx, blockNumber);
}

public enum Mode {
CHAIN_PRUNING,
PRE_MERGE_PRUNING
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,13 @@
*/
package org.hyperledger.besu.ethereum.chain;

public class ChainPrunerConfiguration {
public record ChainPrunerConfiguration(
boolean chainPruningEnabled,
boolean preMergePruningEnabled,
long chainPruningBlocksRetained,
long blocksFrequency,
long chainPruningBlocksRetainedLimit,
int preMergePruningBlocksQuantity) {
public static final ChainPrunerConfiguration DEFAULT =
new ChainPrunerConfiguration(false, 7200, 7200, 256);
private final boolean enabled;
private final long blocksRetained;
private final long blocksFrequency;
private final long blocksRetainedLimit;

public ChainPrunerConfiguration(
final boolean enabled,
final long blocksRetained,
final long blocksRetainedLimit,
final long blocksFrequency) {
this.enabled = enabled;
this.blocksRetained = blocksRetained;
this.blocksRetainedLimit = blocksRetainedLimit;
this.blocksFrequency = blocksFrequency;
}

public long getChainPruningBlocksRetained() {
return blocksRetained;
}

public long getBlocksRetainedLimit() {
return blocksRetainedLimit;
}

public boolean getChainPruningEnabled() {
return enabled;
}

public long getChainPruningBlocksFrequency() {
return blocksFrequency;
}
new ChainPrunerConfiguration(false, false, 7200, 7200, 256, 1000);
}
Loading