From 2550254549fe6146512b9bdd7999ab60f0e770b4 Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Tue, 22 Oct 2024 10:11:57 +1000 Subject: [PATCH] Save earliest slot block in a DB variable (#8722) Adding a variable in the database to store the earliest available block slot which is used when peers request blocks by range. This is a query that can vary in some case and I've seen it causing disconnection in some cases due to multiple requests getting timed out. Changing it to be stored in a variable brings the query to 1 - 2 ms response time. It does add a bit of logic to handle the sync of historical batches and prune which are currently the workflow in the code base that affect that column family. Signed-off-by: Gabriel Fukushima --------- Signed-off-by: Gabriel Fukushima --- .../storage/server/kvstore/DatabaseTest.java | 80 ++++++++++++++++++- .../pegasys/teku/storage/server/Database.java | 4 +- .../server/kvstore/KvStoreDatabase.java | 53 ++++++++++-- .../dataaccess/CombinedKvStoreDao.java | 15 +++- .../dataaccess/KvStoreCombinedDao.java | 4 + .../dataaccess/KvStoreCombinedDaoAdapter.java | 10 +++ .../dataaccess/V4FinalizedKvStoreDao.java | 15 +++- .../server/kvstore/schema/SchemaCombined.java | 2 + .../SchemaFinalizedSnapshotStateAdapter.java | 8 +- .../kvstore/schema/V6SchemaCombined.java | 8 ++ .../schema/V6SchemaCombinedTreeState.java | 1 + .../storage/server/noop/NoOpDatabase.java | 3 +- .../storage/server/pruner/BlockPruner.java | 4 +- .../server/pruner/BlockPrunerTest.java | 19 +++-- .../cli/subcommand/debug/DebugDbCommand.java | 28 ++++++- 15 files changed, 228 insertions(+), 26 deletions(-) diff --git a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java index 89a7e8c40c9..313ffc52052 100644 --- a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java +++ b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java @@ -2207,7 +2207,8 @@ public void pruneFinalizedBlocks_shouldRemoveFinalizedBlocks(final DatabaseConte spec.computeEpochAtSlot(finalizedBlock.getSlot()).plus(1), finalizedBlock); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(6))).isPresent(); - final UInt64 lastPrunedSlot1 = database.pruneFinalizedBlocks(UInt64.valueOf(3), 100); + final UInt64 lastPrunedSlot1 = + database.pruneFinalizedBlocks(UInt64.valueOf(3), 100, UInt64.valueOf(10)); assertThat(lastPrunedSlot1).isEqualTo(UInt64.valueOf(3)); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(0))).isEmpty(); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(1))).isEmpty(); @@ -2217,16 +2218,89 @@ public void pruneFinalizedBlocks_shouldRemoveFinalizedBlocks(final DatabaseConte assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(5))).isPresent(); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(6))).isPresent(); - final UInt64 lastPrunedSlot2 = database.pruneFinalizedBlocks(UInt64.valueOf(5), 1); + final UInt64 lastPrunedSlot2 = + database.pruneFinalizedBlocks(UInt64.valueOf(5), 1, UInt64.valueOf(10)); assertThat(lastPrunedSlot2).isEqualTo(UInt64.valueOf(4)); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(4))).isEmpty(); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(5))).isPresent(); assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(6))).isPresent(); - final UInt64 lastPrunedSlot3 = database.pruneFinalizedBlocks(UInt64.valueOf(4), 1); + final UInt64 lastPrunedSlot3 = + database.pruneFinalizedBlocks(UInt64.valueOf(4), 1, UInt64.valueOf(10)); assertThat(lastPrunedSlot3).isEqualTo(UInt64.valueOf(4)); } + @TestTemplate + public void pruneFinalizedBlocks_UpdatesEarliestAvailableBlockSlot(final DatabaseContext context) + throws Exception { + initialize(context, StateStorageMode.ARCHIVE); + final List blockAndStates = chainBuilder.generateBlocksUpToSlot(5); + addBlocks(blockAndStates); + // Block 7 skipped simulating it was an empty block + final SignedBlockAndState finalizedBlock = chainBuilder.generateBlockAtSlot(7); + addBlocks(finalizedBlock); + justifyAndFinalizeEpoch( + spec.computeEpochAtSlot(finalizedBlock.getSlot()).plus(1), finalizedBlock); + assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(6))).isEmpty(); + + final UInt64 lastPrunedSlot1 = + database.pruneFinalizedBlocks(UInt64.valueOf(3), 100, UInt64.valueOf(10)); + assertThat(lastPrunedSlot1).isEqualTo(UInt64.valueOf(3)); + assertThat(database.getEarliestAvailableBlockSlot()).isEqualTo(Optional.of(UInt64.valueOf(4))); + + final UInt64 lastPrunedSlot2 = + database.pruneFinalizedBlocks(UInt64.valueOf(5), 10, UInt64.valueOf(10)); + assertThat(lastPrunedSlot2).isEqualTo(UInt64.valueOf(5)); + // there's no slot 6 because that was purposely skipped so we expect the earliest available + // block slot to be 7 + assertThat(database.getEarliestAvailableBlockSlot()).isEqualTo(Optional.of(UInt64.valueOf(7))); + } + + @TestTemplate + public void pruneFinalizedBlocks_UpdatesEarliestAvailableBlockSlotWhenLimited( + final DatabaseContext context) throws Exception { + initialize(context, StateStorageMode.ARCHIVE); + final List blockAndStates = chainBuilder.generateBlocksUpToSlot(5); + addBlocks(blockAndStates); + // Block 7 skipped simulating it was an empty block + final SignedBlockAndState finalizedBlock = chainBuilder.generateBlockAtSlot(7); + addBlocks(finalizedBlock); + justifyAndFinalizeEpoch( + spec.computeEpochAtSlot(finalizedBlock.getSlot()).plus(1), finalizedBlock); + assertThat(database.getFinalizedBlockAtSlot(UInt64.valueOf(6))).isEmpty(); + + final UInt64 lastPrunedSlot1 = + database.pruneFinalizedBlocks(UInt64.valueOf(3), 2, UInt64.valueOf(10)); + assertThat(lastPrunedSlot1).isEqualTo(UInt64.valueOf(1)); + assertThat(database.getEarliestAvailableBlockSlot()).isEqualTo(Optional.of(UInt64.valueOf(2))); + + final UInt64 lastPrunedSlot2 = + database.pruneFinalizedBlocks(UInt64.valueOf(5), 10, UInt64.valueOf(10)); + assertThat(lastPrunedSlot2).isEqualTo(UInt64.valueOf(5)); + // there's no slot 6 because that was purposely skipped so we expect the earliest available + // block slot to be 7 + assertThat(database.getEarliestAvailableBlockSlot()).isEqualTo(Optional.of(UInt64.valueOf(7))); + } + + @TestTemplate + public void + pruneFinalizedBlocks_ClearEarliestAvailableBlockSlotVariableWhenNoBlocksLeftAfterPrune( + final DatabaseContext context) throws Exception { + initialize(context, StateStorageMode.ARCHIVE); + final List blockAndStates = chainBuilder.generateBlocksUpToSlot(5); + addBlocks(blockAndStates); + // Block 7 skipped simulating it was an empty block + final SignedBlockAndState finalizedBlock = chainBuilder.generateBlockAtSlot(7); + addBlocks(finalizedBlock); + justifyAndFinalizeEpoch( + spec.computeEpochAtSlot(finalizedBlock.getSlot()).plus(1), finalizedBlock); + + final UInt64 lastPrunedSlot1 = + database.pruneFinalizedBlocks(UInt64.valueOf(7), 10, UInt64.valueOf(10)); + assertThat(lastPrunedSlot1).isEqualTo(UInt64.valueOf(7)); + assertThat(database.getEarliestAvailableBlockSlot()).isEqualTo(Optional.empty()); + } + private List> getFinalizedStateRootsList() { try (final Stream> roots = database.getFinalizedStateRoots()) { return roots.map(entry -> Map.entry(entry.getKey(), entry.getValue())).collect(toList()); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java index b4a06ee6004..4f8751cc44b 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java @@ -237,9 +237,11 @@ default Stream streamBlobSidecarKeys(final UInt64 * * @param lastSlotToPrune inclusive, not reached if limit happens first * @param pruneLimit slots limit + * @param checkpointInitialSlot * @return actual last pruned slot */ - UInt64 pruneFinalizedBlocks(UInt64 lastSlotToPrune, int pruneLimit); + UInt64 pruneFinalizedBlocks( + UInt64 lastSlotToPrune, int pruneLimit, final UInt64 checkpointInitialSlot); Optional pruneFinalizedStates( Optional lastPrunedSlot, UInt64 lastSlotToPruneStateFor, long pruneLimit); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index 9f3fdf05da0..c7d5ead766b 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -323,12 +323,34 @@ protected void storeFinalizedBlocksToDao( .forEach(updater::addBlobSidecar); }); + needToUpdateEarliestBlockSlot(blocks.stream().findFirst()) + .ifPresent(updater::setEarliestBlockSlot); needToUpdateEarliestBlobSidecarSlot(maybeEarliestBlobSidecar) .ifPresent(updater::setEarliestBlobSidecarSlot); updater.commit(); } } + private Optional needToUpdateEarliestBlockSlot( + final Optional maybeNewEarliestBlockSlot) { + // New value is absent - not updating + if (maybeNewEarliestBlockSlot.isEmpty()) { + return Optional.empty(); + } + // New value is present, value from DB is absent - updating + final Optional maybeEarliestFinalizedBlockSlotDb = dao.getEarliestFinalizedBlockSlot(); + if (maybeEarliestFinalizedBlockSlotDb.isEmpty()) { + return maybeNewEarliestBlockSlot.map(SignedBeaconBlock::getSlot); + } + // New value is smaller than value from DB - updating + final UInt64 newEarliestBlockSlot = maybeNewEarliestBlockSlot.get().getSlot(); + if (newEarliestBlockSlot.isLessThan(maybeEarliestFinalizedBlockSlotDb.get())) { + return maybeNewEarliestBlockSlot.map(SignedBeaconBlock::getSlot); + } else { + return Optional.empty(); + } + } + private Optional needToUpdateEarliestBlobSidecarSlot( final Optional maybeNewEarliestBlobSidecarSlot) { // New value is absent - not updating @@ -377,7 +399,8 @@ public void deleteHotBlocks(final Set blockRootsToDelete) { } @Override - public UInt64 pruneFinalizedBlocks(final UInt64 lastSlotToPrune, final int pruneLimit) { + public UInt64 pruneFinalizedBlocks( + final UInt64 lastSlotToPrune, final int pruneLimit, final UInt64 checkpointInitialSlot) { final Optional earliestBlockSlot = dao.getEarliestFinalizedBlock().map(SignedBeaconBlock::getSlot); LOG.debug( @@ -386,14 +409,18 @@ public UInt64 pruneFinalizedBlocks(final UInt64 lastSlotToPrune, final int prune if (earliestBlockSlot.isEmpty()) { return lastSlotToPrune; } - return pruneToBlock(lastSlotToPrune, pruneLimit); + return pruneToBlock(lastSlotToPrune, pruneLimit, checkpointInitialSlot); } - private UInt64 pruneToBlock(final UInt64 lastSlotToPrune, final int pruneLimit) { + private UInt64 pruneToBlock( + final UInt64 lastSlotToPrune, final int pruneLimit, final UInt64 checkpointInitialSlot) { final List> blocksToPrune; + final Optional earliestSlotAvailableAfterPrune; LOG.debug("Pruning finalized blocks to slot {} (included)", lastSlotToPrune); try (final Stream stream = dao.streamFinalizedBlocks(UInt64.ZERO, lastSlotToPrune)) { + // get an extra block to set earliest finalized block slot available after pruning runs + // ensuring it is an existing block in the DB blocksToPrune = stream.limit(pruneLimit).map(block -> Pair.of(block.getSlot(), block.getRoot())).toList(); } @@ -402,17 +429,30 @@ private UInt64 pruneToBlock(final UInt64 lastSlotToPrune, final int pruneLimit) LOG.debug("No finalized blocks to prune up to {} slot", lastSlotToPrune); return lastSlotToPrune; } + + try (final Stream stream = + dao.streamFinalizedBlocks(UInt64.ZERO, checkpointInitialSlot)) { + + earliestSlotAvailableAfterPrune = + stream + .map(SignedBeaconBlock::getSlot) + .filter(slot -> slot.isGreaterThan(blocksToPrune.getLast().getLeft())) + .findFirst(); + } + final UInt64 lastPrunedBlockSlot = blocksToPrune.getLast().getKey(); LOG.debug( "Pruning {} finalized blocks, last block slot is {}", blocksToPrune.size(), lastPrunedBlockSlot); - deleteFinalizedBlocks(blocksToPrune); + deleteFinalizedBlocks(blocksToPrune, earliestSlotAvailableAfterPrune); return blocksToPrune.size() < pruneLimit ? lastSlotToPrune : lastPrunedBlockSlot; } - private void deleteFinalizedBlocks(final List> blocksToPrune) { + private void deleteFinalizedBlocks( + final List> blocksToPrune, + final Optional earliestSlotAvailableAfterPrune) { if (blocksToPrune.size() > 0) { if (blocksToPrune.size() < 20) { LOG.debug( @@ -425,6 +465,8 @@ private void deleteFinalizedBlocks(final List> blocksToPru try (final FinalizedUpdater updater = finalizedUpdater()) { blocksToPrune.forEach( pair -> updater.deleteFinalizedBlock(pair.getLeft(), pair.getRight())); + earliestSlotAvailableAfterPrune.ifPresentOrElse( + updater::setEarliestBlockSlot, updater::deleteEarliestBlockSlot); updater.commit(); } } @@ -578,6 +620,7 @@ public void storeInitialAnchor(final AnchorPoint initialAnchor) { && spec.atSlot(initialAnchor.getSlot()) .getMilestone() .isGreaterThanOrEqualTo(SpecMilestone.DENEB)) { + updater.setEarliestBlockSlot(initialAnchor.getSlot()); updater.setEarliestBlobSidecarSlot(initialAnchor.getSlot()); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java index ed62d602f13..83bda83c9a8 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java @@ -267,7 +267,10 @@ public Optional getFinalizedBlockAtSlot(final UInt64 slot) { @Override public Optional getEarliestFinalizedBlockSlot() { - return db.getFirstEntry(schema.getColumnFinalizedBlocksBySlot()).map(ColumnEntry::getKey); + return db.get(schema.getVariableEarliestBlockSlot()) + .or( + () -> + db.getFirstEntry(schema.getColumnFinalizedBlocksBySlot()).map(ColumnEntry::getKey)); } @Override @@ -649,6 +652,16 @@ public void setEarliestBlobSidecarSlot(final UInt64 slot) { transaction.put(schema.getVariableEarliestBlobSidecarSlot(), slot); } + @Override + public void setEarliestBlockSlot(final UInt64 slot) { + transaction.put(schema.getVariableEarliestBlockSlot(), slot); + } + + @Override + public void deleteEarliestBlockSlot() { + transaction.delete(schema.getVariableEarliestBlockSlot()); + } + @Override public void commit() { // Commit db updates diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java index 96e99714be5..2dba5e05e70 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java @@ -260,6 +260,10 @@ interface FinalizedUpdater extends AutoCloseable { void setEarliestBlobSidecarSlot(UInt64 slot); + void setEarliestBlockSlot(UInt64 slot); + + void deleteEarliestBlockSlot(); + void commit(); void cancel(); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java index 3f48f45bd2d..b9d918f98dc 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java @@ -588,6 +588,16 @@ public void setEarliestBlobSidecarSlot(final UInt64 slot) { finalizedUpdater.setEarliestBlobSidecarSlot(slot); } + @Override + public void setEarliestBlockSlot(final UInt64 slot) { + finalizedUpdater.setEarliestBlockSlot(slot); + } + + @Override + public void deleteEarliestBlockSlot() { + finalizedUpdater.deleteEarliestBlockSlot(); + } + @Override public void addMinGenesisTimeBlock(final MinGenesisTimeBlockEvent event) { hotUpdater.addMinGenesisTimeBlock(event); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java index 786ffb996fe..e4eae58e694 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java @@ -65,7 +65,10 @@ public Optional getFinalizedBlockAtSlot(final UInt64 slot) { } public Optional getEarliestFinalizedBlockSlot() { - return db.getFirstEntry(schema.getColumnFinalizedBlocksBySlot()).map(ColumnEntry::getKey); + return db.get(schema.getVariableEarliestBlockSlot()) + .or( + () -> + db.getFirstEntry(schema.getColumnFinalizedBlocksBySlot()).map(ColumnEntry::getKey)); } public Optional getEarliestFinalizedStateSlot() { @@ -412,6 +415,16 @@ public void setEarliestBlobSidecarSlot(final UInt64 slot) { transaction.put(schema.getVariableEarliestBlobSidecarSlot(), slot); } + @Override + public void setEarliestBlockSlot(final UInt64 slot) { + transaction.put(schema.getVariableEarliestBlockSlot(), slot); + } + + @Override + public void deleteEarliestBlockSlot() { + transaction.delete(schema.getVariableEarliestBlockSlot()); + } + @Override public void commit() { // Commit db updates diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java index c284f6aba5a..c74190ae832 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java @@ -83,6 +83,8 @@ public interface SchemaCombined extends Schema { KvStoreVariable getVariableEarliestBlobSidecarSlot(); + KvStoreVariable getVariableEarliestBlockSlot(); + KvStoreVariable getVariableFinalizedDepositSnapshot(); Map> getColumnMap(); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java index 7120bf77f44..a98a89dcbc6 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java @@ -106,11 +106,17 @@ public KvStoreVariable getVariableEarliestBlobSidecarSlot() { return delegate.getVariableEarliestBlobSidecarSlot(); } + public KvStoreVariable getVariableEarliestBlockSlot() { + return delegate.getVariableEarliestBlockSlot(); + } + public Map> getVariableMap() { return Map.of( "OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot(), "EARLIEST_BLOB_SIDECAR_SLOT", - getVariableEarliestBlobSidecarSlot()); + getVariableEarliestBlobSidecarSlot(), + "EARLIEST_BLOCK_SLOT_AVAILABLE", + getVariableEarliestBlockSlot()); } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java index fa833459c3c..15630607eb5 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java @@ -84,6 +84,7 @@ public abstract class V6SchemaCombined implements SchemaCombined { private final KvStoreVariable optimisticTransitionBlockSlot; private final KvStoreVariable earliestBlobSidecarSlot; + private final KvStoreVariable earliestBlockSlot; protected V6SchemaCombined(final Spec spec, final int finalizedOffset) { this.finalizedOffset = finalizedOffset; @@ -100,6 +101,7 @@ protected V6SchemaCombined(final Spec spec, final int finalizedOffset) { optimisticTransitionBlockSlot = KvStoreVariable.create(finalizedOffset + 1, UINT64_SERIALIZER); earliestBlobSidecarSlot = KvStoreVariable.create(finalizedOffset + 2, UINT64_SERIALIZER); + earliestBlockSlot = KvStoreVariable.create(finalizedOffset + 3, UINT64_SERIALIZER); } @Override @@ -192,6 +194,11 @@ public KvStoreVariable getVariableEarliestBlobSidecarSlot() { return earliestBlobSidecarSlot; } + @Override + public KvStoreVariable getVariableEarliestBlockSlot() { + return earliestBlockSlot; + } + @Override public Map> getColumnMap() { return ImmutableMap.>builder() @@ -227,6 +234,7 @@ public Map> getVariableMap() { .put("OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot()) .put("FINALIZED_DEPOSIT_SNAPSHOT", getVariableFinalizedDepositSnapshot()) .put("EARLIEST_BLOB_SIDECAR_SLOT", getVariableEarliestBlobSidecarSlot()) + .put("EARLIEST_BLOCK_SLOT_AVAILABLE", getVariableEarliestBlockSlot()) .build(); } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java index 5375e869c88..eed62f47bdd 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java @@ -163,6 +163,7 @@ public Map> getVariableMap() { .put("OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot()) .put("FINALIZED_DEPOSIT_SNAPSHOT", getVariableFinalizedDepositSnapshot()) .put("EARLIEST_BLOB_SIDECAR_SLOT", getVariableEarliestBlobSidecarSlot()) + .put("EARLIEST_BLOCK_SLOT", getVariableEarliestBlockSlot()) .build(); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java index abbf50e67d4..af522b0c619 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java @@ -226,7 +226,8 @@ public Optional getFinalizedDepositSnapshot() { public void setFinalizedDepositSnapshot(final DepositTreeSnapshot finalizedDepositSnapshot) {} @Override - public UInt64 pruneFinalizedBlocks(final UInt64 lastSlotToPrune, final int pruneLimit) { + public UInt64 pruneFinalizedBlocks( + final UInt64 lastSlotToPrune, final int pruneLimit, final UInt64 checkpointInitialSlot) { return lastSlotToPrune; } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/pruner/BlockPruner.java b/storage/src/main/java/tech/pegasys/teku/storage/server/pruner/BlockPruner.java index cfcc19f4626..3635f6c24a5 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/pruner/BlockPruner.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/pruner/BlockPruner.java @@ -97,6 +97,7 @@ private void pruneBlocks() { final UInt64 earliestEpochToKeep = finalizedEpoch.minusMinZero(spec.getNetworkingConfig().getMinEpochsForBlockRequests()); final UInt64 earliestSlotToKeep = spec.computeStartSlotAtEpoch(earliestEpochToKeep); + final UInt64 checkpointEarliestSlot = spec.computeStartSlotAtEpoch(finalizedEpoch); if (earliestSlotToKeep.isZero()) { LOG.debug("Pruning is not performed as the epochs to retain include the genesis epoch."); return; @@ -104,7 +105,8 @@ private void pruneBlocks() { LOG.debug("Initiating pruning of finalized blocks prior to slot {}.", earliestSlotToKeep); try { final UInt64 lastPrunedSlot = - database.pruneFinalizedBlocks(earliestSlotToKeep.decrement(), pruneLimit); + database.pruneFinalizedBlocks( + earliestSlotToKeep.decrement(), pruneLimit, checkpointEarliestSlot); LOG.debug( "Pruned {} finalized blocks prior to slot {}, last pruned slot was {}.", pruneLimit, diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/pruner/BlockPrunerTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/pruner/BlockPrunerTest.java index a9a2eb4137f..7af5b301857 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/server/pruner/BlockPrunerTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/pruner/BlockPrunerTest.java @@ -72,7 +72,7 @@ class BlockPrunerTest { void setUp() { epochsToKeep = spec.getNetworkingConfig().getMinEpochsForBlockRequests(); assertThat(pruner.start()).isCompleted(); - when(database.pruneFinalizedBlocks(any(), anyInt())).thenReturn(UInt64.ZERO); + when(database.pruneFinalizedBlocks(any(), anyInt(), any())).thenReturn(UInt64.ZERO); } @Test @@ -80,7 +80,7 @@ void shouldPruneWhenFirstStarted() { when(database.getFinalizedCheckpoint()) .thenReturn(Optional.of(dataStructureUtil.randomCheckpoint(UInt64.valueOf(50)))); asyncRunner.executeDueActions(); - verify(database).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS)); + verify(database).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS), any()); verify(pruningActiveLabelledGauge).set(eq(0.), any()); } @@ -88,12 +88,12 @@ void shouldPruneWhenFirstStarted() { void shouldPruneAfterInterval() { when(database.getFinalizedCheckpoint()).thenReturn(Optional.empty()); asyncRunner.executeDueActions(); - verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS)); + verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS), any()); when(database.getFinalizedCheckpoint()) .thenReturn(Optional.of(dataStructureUtil.randomCheckpoint(UInt64.valueOf(52)))); triggerNextPruning(); - verify(database).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS)); + verify(database).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS), any()); verify(pruningActiveLabelledGauge, times(2)).set(eq(0.), any()); } @@ -101,7 +101,7 @@ void shouldPruneAfterInterval() { void shouldNotPruneWhenFinalizedCheckpointNotSet() { when(database.getFinalizedCheckpoint()).thenReturn(Optional.empty()); triggerNextPruning(); - verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS)); + verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS), any()); verify(pruningActiveLabelledGauge).set(eq(0.), any()); } @@ -110,32 +110,35 @@ void shouldNotPruneWhenFinalizedCheckpointBelowEpochsToKeep() { when(database.getFinalizedCheckpoint()) .thenReturn(Optional.of(dataStructureUtil.randomCheckpoint(epochsToKeep))); triggerNextPruning(); - verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS)); + verify(database, never()).pruneFinalizedBlocks(any(), eq(PRUNE_SLOTS), any()); verify(pruningActiveLabelledGauge).set(eq(0.), any()); } @Test void shouldPruneBlocksMoreThanEpochsToKeepBeforeFinalizedCheckpoint() { final UInt64 finalizedEpoch = UInt64.valueOf(50); + final UInt64 checkpointEarliestSlot = spec.computeStartSlotAtEpoch(finalizedEpoch); when(database.getFinalizedCheckpoint()) .thenReturn(Optional.of(dataStructureUtil.randomCheckpoint(finalizedEpoch))); triggerNextPruning(); // SlotToKeep = FinalizedEpoch (50) * SlotsPerEpoch(10) - EpochsToKeep(5) * SlotsPerEpoch(10) // = 500 - 50 = 450, last slot to prune = 450 - 1 = 449. final UInt64 lastSlotToPrune = UInt64.valueOf(449); - verify(database).pruneFinalizedBlocks(lastSlotToPrune, PRUNE_SLOTS); + verify(database).pruneFinalizedBlocks(lastSlotToPrune, PRUNE_SLOTS, checkpointEarliestSlot); verify(pruningActiveLabelledGauge).set(eq(0.), any()); } @Test void shouldPruneBlocksWhenFirstEpochIsPrunable() { final int finalizedEpoch = epochsToKeep + 1; + final UInt64 checkpointEarliestSlot = + spec.computeStartSlotAtEpoch(UInt64.valueOf(finalizedEpoch)); when(database.getFinalizedCheckpoint()) .thenReturn(Optional.of(dataStructureUtil.randomCheckpoint(finalizedEpoch))); triggerNextPruning(); // Should prune all blocks in the first epoch (ie blocks 0 - 9) final UInt64 lastSlotToPrune = UInt64.valueOf(SLOTS_PER_EPOCH - 1); - verify(database).pruneFinalizedBlocks(lastSlotToPrune, PRUNE_SLOTS); + verify(database).pruneFinalizedBlocks(lastSlotToPrune, PRUNE_SLOTS, checkpointEarliestSlot); verify(pruningActiveLabelledGauge).set(eq(0.), any()); } diff --git a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java index 381cd27cb20..0c5a0e5e39e 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/DebugDbCommand.java @@ -156,13 +156,33 @@ public int getFinalizedState( footer = "Teku is licensed under the Apache License 2.0") public int getEarliestAvailableBlockSlot( @Mixin final BeaconNodeDataOptions beaconNodeDataOptions, - @Mixin final Eth2NetworkOptions eth2NetworkOptions) + @Mixin final Eth2NetworkOptions eth2NetworkOptions, + @Option( + names = {"--timed", "-t"}, + description = "Prints the time taken to retrieve the earliest available block slot", + defaultValue = "true", + fallbackValue = "true", + showDefaultValue = Visibility.ALWAYS) + final boolean verbose) throws Exception { try (final Database database = createDatabase(beaconNodeDataOptions, eth2NetworkOptions)) { - Optional earliestAvailableBlockSlot = database.getEarliestAvailableBlockSlot(); - earliestAvailableBlockSlot.ifPresent(System.out::println); + if (verbose) { + final long startTime = System.currentTimeMillis(); + final Optional earliestAvailableBlockSlot = + database.getEarliestAvailableBlockSlot(); + final long endTime = System.currentTimeMillis(); + earliestAvailableBlockSlot.ifPresent(System.out::println); + System.out.println( + "Time taken to retrieve the earliest available block slot: " + + (endTime - startTime) + + "ms"); + } else { + final Optional earliestAvailableBlockSlot = + database.getEarliestAvailableBlockSlot(); + earliestAvailableBlockSlot.ifPresent(System.out::println); + } + return 0; } - return 0; } @Command(