From 62100c1f98770e3aa060b896b955672a9a597065 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 24 Sep 2024 14:12:45 +0100 Subject: [PATCH 01/16] Add check for Ephemery and reset db Signed-off-by: gconnect --- .../server/VersionedDatabaseFactory.java | 81 ++++++++++++++++++- .../server/network/DatabaseNetwork.java | 16 ++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java index 4505e1c3cd5..6864956ce64 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java @@ -13,6 +13,10 @@ package tech.pegasys.teku.storage.server; +import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; @@ -20,6 +24,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.Locale; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -46,7 +51,8 @@ public class VersionedDatabaseFactory implements DatabaseFactory { @VisibleForTesting static final String STORAGE_MODE_PATH = "data-storage-mode.txt"; @VisibleForTesting static final String METADATA_FILENAME = "metadata.yml"; @VisibleForTesting static final String NETWORK_FILENAME = "network.yml"; - + private static final String EPHEMERY_DEPOSIT_CONTRACT_ADDRESS = + "0x4242424242424242424242424242424242424242"; private final MetricsSystem metricsSystem; private final File dataDirectory; private final int maxKnownNodeCacheSize; @@ -88,6 +94,28 @@ public VersionedDatabaseFactory( public Database createDatabase() { LOG.info("Beacon data directory set to: {}", dataDirectory.getAbsolutePath()); validateDataPaths(); + // try { + // Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); + // File networkFile = dataDirectory.toPath().resolve(NETWORK_FILENAME).toFile(); + // final String depositContractString = + // eth1Address.toHexString().toLowerCase(Locale.ROOT); + // String networkContent = Files.readString(networkFile.toPath()); + // final ObjectMapper objectMapper = new ObjectMapper(new + // YAMLFactory().disable(WRITE_DOC_START_MARKER)); + // + // final DatabaseNetwork readDatabaseNetwork = + // objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); + // if(networkFile.exists() && + // depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) && + // networkContent.contains("deposit_chain_id") + // && readDatabaseNetwork.depositChainId.equals(depositChainId)) { + // LOG.info("Ready for reset"); + //// resetDatabaseDirectories(); + // } + // } catch (IOException e) { + // throw new RuntimeException(e); + // } + resetDatabaseDirectories(); final DatabaseVersion dbVersion = getDatabaseVersion(); createDirectories(dbVersion); saveDatabaseVersion(dbVersion); @@ -384,4 +412,55 @@ private void saveStorageMode(final StateStorageMode storageMode) { e); } } + + private void resetDatabaseDirectories() { + File networkFile = getNetworkFile(); + try { + if (dbDirectory.exists()) { + deleteDirectoryRecursively(dbDirectory.toPath()); + } + if (v5ArchiveDirectory.exists()) { + deleteDirectoryRecursively(v5ArchiveDirectory.toPath()); + } + if (networkFile.exists()) { + Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); + final String depositContractString = eth1Address.toHexString().toLowerCase(Locale.ROOT); + String networkContent = Files.readString(networkFile.toPath()); + + final ObjectMapper objectMapper = + new ObjectMapper(new YAMLFactory().disable(WRITE_DOC_START_MARKER)); + final DatabaseNetwork readDatabaseNetwork = + objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); + + if (depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) + && networkContent.contains("deposit_chain_id") + && !readDatabaseNetwork.getDepositChainId().equals(depositChainId)) { + Files.delete(networkFile.toPath()); + } + } + } catch (IOException e) { + throw DatabaseStorageException.unrecoverable( + String.format( + "Failed to reset database directories or network file at %s", + networkFile.getAbsolutePath()), + e); + } + } + + private void deleteDirectoryRecursively(final Path path) throws IOException { + if (Files.isDirectory(path)) { + try (var stream = Files.walk(path)) { + stream + .sorted((o1, o2) -> o2.compareTo(o1)) + .forEach( + p -> { + try { + Files.delete(p); + } catch (IOException e) { + throw new RuntimeException("Failed to delete file: " + p, e); + } + }); + } + } + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java index ea35a55855e..b1236ce74f2 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java @@ -45,6 +45,9 @@ public class DatabaseNetwork { @VisibleForTesting final Long depositChainId; + private static final String EPHEMERY_DEPOSIT_CONTRACT_ADDRESS = + "0x4242424242424242424242424242424242424242"; + @JsonCreator DatabaseNetwork( @JsonProperty(value = "fork_version") final String forkVersion, @@ -84,6 +87,15 @@ public static DatabaseNetwork init( formatMessage( "deposit contract", depositContractString, databaseNetwork.depositContract)); } + if (!depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) + && databaseNetwork.depositChainId != null + && !databaseNetwork.depositChainId.equals(depositChainId)) { + throw DatabaseStorageException.unrecoverable( + formatMessage( + "deposit chain id", + String.valueOf(depositChainId), + String.valueOf(databaseNetwork.depositChainId))); + } return databaseNetwork; } else { DatabaseNetwork databaseNetwork = @@ -93,6 +105,10 @@ public static DatabaseNetwork init( } } + public Long getDepositChainId() { + return depositChainId; + } + private static String formatMessage( final String fieldName, final String expected, final String actual) { return String.format( From d5dc6c4e0b8f6bf1ae0ef4b8c97cc091c14135e7 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 24 Sep 2024 14:31:21 +0100 Subject: [PATCH 02/16] Remove unused comment Signed-off-by: gconnect --- .../server/VersionedDatabaseFactory.java | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java index 6864956ce64..4d1b7dddc6f 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java @@ -94,27 +94,6 @@ public VersionedDatabaseFactory( public Database createDatabase() { LOG.info("Beacon data directory set to: {}", dataDirectory.getAbsolutePath()); validateDataPaths(); - // try { - // Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); - // File networkFile = dataDirectory.toPath().resolve(NETWORK_FILENAME).toFile(); - // final String depositContractString = - // eth1Address.toHexString().toLowerCase(Locale.ROOT); - // String networkContent = Files.readString(networkFile.toPath()); - // final ObjectMapper objectMapper = new ObjectMapper(new - // YAMLFactory().disable(WRITE_DOC_START_MARKER)); - // - // final DatabaseNetwork readDatabaseNetwork = - // objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); - // if(networkFile.exists() && - // depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) && - // networkContent.contains("deposit_chain_id") - // && readDatabaseNetwork.depositChainId.equals(depositChainId)) { - // LOG.info("Ready for reset"); - //// resetDatabaseDirectories(); - // } - // } catch (IOException e) { - // throw new RuntimeException(e); - // } resetDatabaseDirectories(); final DatabaseVersion dbVersion = getDatabaseVersion(); createDirectories(dbVersion); @@ -413,6 +392,7 @@ private void saveStorageMode(final StateStorageMode storageMode) { } } + @VisibleForTesting private void resetDatabaseDirectories() { File networkFile = getNetworkFile(); try { From a8f6afa07306a32ecb05615ad1c8dec9bbb3d04e Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 24 Sep 2024 22:18:16 +0100 Subject: [PATCH 03/16] Add a shouldResetForEphemery method and test Signed-off-by: gconnect --- .../server/VersionedDatabaseFactory.java | 44 ++++++++++++----- .../server/VersionedDatabaseFactoryTest.java | 47 +++++++++++++++++++ 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java index 4d1b7dddc6f..dbd3699e619 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java @@ -393,7 +393,36 @@ private void saveStorageMode(final StateStorageMode storageMode) { } @VisibleForTesting - private void resetDatabaseDirectories() { + public boolean shouldResetForEphemery() { + File networkFile = getNetworkFile(); + if (!networkFile.exists()) { + return false; + } + try { + Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); + String depositContractString = eth1Address.toHexString().toLowerCase(Locale.ROOT); + String networkContent = Files.readString(networkFile.toPath()); + + ObjectMapper objectMapper = + new ObjectMapper(new YAMLFactory().disable(WRITE_DOC_START_MARKER)); + DatabaseNetwork readDatabaseNetwork = + objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); + + return depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) + && networkContent.contains("deposit_chain_id") + && !readDatabaseNetwork.getDepositChainId().equals(depositChainId); + + } catch (IOException e) { + throw new RuntimeException( + String.format( + "Failed to read network file at %s during reset check", + networkFile.getAbsolutePath()), + e); + } + } + + @VisibleForTesting + public void resetDatabaseDirectories() { File networkFile = getNetworkFile(); try { if (dbDirectory.exists()) { @@ -403,18 +432,7 @@ private void resetDatabaseDirectories() { deleteDirectoryRecursively(v5ArchiveDirectory.toPath()); } if (networkFile.exists()) { - Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); - final String depositContractString = eth1Address.toHexString().toLowerCase(Locale.ROOT); - String networkContent = Files.readString(networkFile.toPath()); - - final ObjectMapper objectMapper = - new ObjectMapper(new YAMLFactory().disable(WRITE_DOC_START_MARKER)); - final DatabaseNetwork readDatabaseNetwork = - objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); - - if (depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) - && networkContent.contains("deposit_chain_id") - && !readDatabaseNetwork.getDepositChainId().equals(depositChainId)) { + if (shouldResetForEphemery()) { Files.delete(networkFile.toPath()); } } diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java index fd256b787b8..b44a63224b5 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java @@ -15,6 +15,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static tech.pegasys.teku.storage.server.StateStorageMode.PRUNE; import java.io.File; @@ -139,6 +144,48 @@ public void createDatabase_shouldAllowAllSupportedDatabases(final DatabaseVersio assertThat(dbFactory.getDatabaseVersion()).isEqualTo(version); } + @Test + void shouldOnlyResetWhenOnEphemeryAndDueForReset() throws Exception { + VersionedDatabaseFactory dbFactorySpy = + spy( + new VersionedDatabaseFactory( + new StubMetricsSystem(), + dataDir, + StorageConfiguration.builder() + .specProvider(spec) + .eth1DepositContract(eth1Address) + .build())); + + doReturn(true).when(dbFactorySpy).shouldResetForEphemery(); + dbFactorySpy.resetDatabaseDirectories(); + verify(dbFactorySpy, times(1)).resetDatabaseDirectories(); + + try (final Database db = dbFactorySpy.createDatabase()) { + assertThat(db).isNotNull(); + verify(dbFactorySpy, times(1)).createDatabase(); + } + } + + @Test + void shouldNotResetWhenOnEphemeryButNotDueForRestAndWhenNotOnEphemery() throws Exception { + VersionedDatabaseFactory dbFactorySpy = + spy( + new VersionedDatabaseFactory( + new StubMetricsSystem(), + dataDir, + StorageConfiguration.builder() + .specProvider(spec) + .eth1DepositContract(eth1Address) + .build())); + + doReturn(false).when(dbFactorySpy).shouldResetForEphemery(); + verifyNoInteractions(dbFactorySpy); + try (final Database db = dbFactorySpy.createDatabase()) { + assertThat(db).isNotNull(); + verify(dbFactorySpy, times(1)).createDatabase(); + } + } + private void createDbDirectory(final Path dataPath) { final File dbDirectory = Paths.get(dataPath.toAbsolutePath().toString(), VersionedDatabaseFactory.DB_PATH).toFile(); From 34ecc2d04cdab7d989d5d117c7f304451d0eb8e7 Mon Sep 17 00:00:00 2001 From: gconnect Date: Wed, 25 Sep 2024 03:19:19 +0100 Subject: [PATCH 04/16] update null check Signed-off-by: gconnect --- .../pegasys/teku/storage/server/network/DatabaseNetwork.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java index b1236ce74f2..0b46fdd3ab0 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java @@ -87,8 +87,8 @@ public static DatabaseNetwork init( formatMessage( "deposit contract", depositContractString, databaseNetwork.depositContract)); } - if (!depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) - && databaseNetwork.depositChainId != null + if (databaseNetwork.depositChainId != null + && !depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) && !databaseNetwork.depositChainId.equals(depositChainId)) { throw DatabaseStorageException.unrecoverable( formatMessage( From 1bbc3c4452eda3952d4b60cf25e69d56ac2483c0 Mon Sep 17 00:00:00 2001 From: gconnect Date: Mon, 30 Sep 2024 04:29:32 +0100 Subject: [PATCH 05/16] optimized the reset code and add a custom EphemeryException Signed-off-by: gconnect --- .../services/chainstorage/StorageService.java | 46 ++++++++++- .../server/VersionedDatabaseFactory.java | 78 ------------------- .../server/network/DatabaseNetwork.java | 5 ++ .../server/network/EphemeryException.java | 16 ++++ .../server/VersionedDatabaseFactoryTest.java | 47 ----------- 5 files changed, 64 insertions(+), 128 deletions(-) create mode 100644 storage/src/main/java/tech/pegasys/teku/storage/server/network/EphemeryException.java diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index 6a1e4e9bb39..5a191887b7c 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -16,6 +16,9 @@ import static tech.pegasys.teku.infrastructure.async.AsyncRunnerFactory.DEFAULT_MAX_QUEUE_SIZE; import static tech.pegasys.teku.spec.config.Constants.STORAGE_QUERY_CHANNEL_PARALLELISM; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -42,6 +45,7 @@ import tech.pegasys.teku.storage.server.RetryingStorageUpdateChannel; import tech.pegasys.teku.storage.server.StorageConfiguration; import tech.pegasys.teku.storage.server.VersionedDatabaseFactory; +import tech.pegasys.teku.storage.server.network.EphemeryException; import tech.pegasys.teku.storage.server.pruner.BlobSidecarPruner; import tech.pegasys.teku.storage.server.pruner.BlockPruner; import tech.pegasys.teku.storage.server.pruner.StatePruner; @@ -80,14 +84,24 @@ protected SafeFuture doStart() { 1, DEFAULT_MAX_QUEUE_SIZE, Thread.NORM_PRIORITY - 1); - final VersionedDatabaseFactory dbFactory = + VersionedDatabaseFactory dbFactory = new VersionedDatabaseFactory( serviceConfig.getMetricsSystem(), serviceConfig.getDataDirLayout().getBeaconDataDirectory(), config); - database = dbFactory.createDatabase(); - database.migrate(); + try { + database = dbFactory.createDatabase(); + database.migrate(); + } catch (EphemeryException e) { + try { + resetDatabaseDirectories(serviceConfig); + database = dbFactory.createDatabase(); + database.migrate(); + } catch (Exception ex) { + throw new RuntimeException("Failed to reset and recreate the database.", ex); + } + } final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( @@ -222,4 +236,30 @@ protected SafeFuture doStop() { public ChainStorage getChainStorage() { return chainStorage; } + + /** This method is called only on Ephemery network when reset is due. */ + void resetDatabaseDirectories(final ServiceConfig serviceConfig) throws IOException { + final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); + final Path slashProtectionDir = + serviceConfig.getDataDirLayout().getValidatorDataDirectory().resolve("slashprotection"); + deleteDirectoryRecursively(beaconDataDir); + deleteDirectoryRecursively(slashProtectionDir); + } + + private void deleteDirectoryRecursively(final Path path) throws IOException { + if (Files.isDirectory(path)) { + try (var stream = Files.walk(path)) { + stream + .sorted((o1, o2) -> o2.compareTo(o1)) + .forEach( + p -> { + try { + Files.delete(p); + } catch (IOException e) { + throw new RuntimeException("Failed to delete file: " + p, e); + } + }); + } + } + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java index dbd3699e619..a722e51b323 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactory.java @@ -13,10 +13,6 @@ package tech.pegasys.teku.storage.server; -import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; @@ -24,7 +20,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Locale; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -51,8 +46,6 @@ public class VersionedDatabaseFactory implements DatabaseFactory { @VisibleForTesting static final String STORAGE_MODE_PATH = "data-storage-mode.txt"; @VisibleForTesting static final String METADATA_FILENAME = "metadata.yml"; @VisibleForTesting static final String NETWORK_FILENAME = "network.yml"; - private static final String EPHEMERY_DEPOSIT_CONTRACT_ADDRESS = - "0x4242424242424242424242424242424242424242"; private final MetricsSystem metricsSystem; private final File dataDirectory; private final int maxKnownNodeCacheSize; @@ -94,7 +87,6 @@ public VersionedDatabaseFactory( public Database createDatabase() { LOG.info("Beacon data directory set to: {}", dataDirectory.getAbsolutePath()); validateDataPaths(); - resetDatabaseDirectories(); final DatabaseVersion dbVersion = getDatabaseVersion(); createDirectories(dbVersion); saveDatabaseVersion(dbVersion); @@ -391,74 +383,4 @@ private void saveStorageMode(final StateStorageMode storageMode) { e); } } - - @VisibleForTesting - public boolean shouldResetForEphemery() { - File networkFile = getNetworkFile(); - if (!networkFile.exists()) { - return false; - } - try { - Long depositChainId = spec.getGenesisSpecConfig().getDepositChainId(); - String depositContractString = eth1Address.toHexString().toLowerCase(Locale.ROOT); - String networkContent = Files.readString(networkFile.toPath()); - - ObjectMapper objectMapper = - new ObjectMapper(new YAMLFactory().disable(WRITE_DOC_START_MARKER)); - DatabaseNetwork readDatabaseNetwork = - objectMapper.readerFor(DatabaseNetwork.class).readValue(networkFile); - - return depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) - && networkContent.contains("deposit_chain_id") - && !readDatabaseNetwork.getDepositChainId().equals(depositChainId); - - } catch (IOException e) { - throw new RuntimeException( - String.format( - "Failed to read network file at %s during reset check", - networkFile.getAbsolutePath()), - e); - } - } - - @VisibleForTesting - public void resetDatabaseDirectories() { - File networkFile = getNetworkFile(); - try { - if (dbDirectory.exists()) { - deleteDirectoryRecursively(dbDirectory.toPath()); - } - if (v5ArchiveDirectory.exists()) { - deleteDirectoryRecursively(v5ArchiveDirectory.toPath()); - } - if (networkFile.exists()) { - if (shouldResetForEphemery()) { - Files.delete(networkFile.toPath()); - } - } - } catch (IOException e) { - throw DatabaseStorageException.unrecoverable( - String.format( - "Failed to reset database directories or network file at %s", - networkFile.getAbsolutePath()), - e); - } - } - - private void deleteDirectoryRecursively(final Path path) throws IOException { - if (Files.isDirectory(path)) { - try (var stream = Files.walk(path)) { - stream - .sorted((o1, o2) -> o2.compareTo(o1)) - .forEach( - p -> { - try { - Files.delete(p); - } catch (IOException e) { - throw new RuntimeException("Failed to delete file: " + p, e); - } - }); - } - } - } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java index 0b46fdd3ab0..c4aa809d782 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/network/DatabaseNetwork.java @@ -96,6 +96,11 @@ public static DatabaseNetwork init( String.valueOf(depositChainId), String.valueOf(databaseNetwork.depositChainId))); } + if (databaseNetwork.depositChainId != null + && depositContractString.equals(EPHEMERY_DEPOSIT_CONTRACT_ADDRESS) + && !databaseNetwork.depositChainId.equals(depositChainId)) { + throw new EphemeryException(); + } return databaseNetwork; } else { DatabaseNetwork databaseNetwork = diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/network/EphemeryException.java b/storage/src/main/java/tech/pegasys/teku/storage/server/network/EphemeryException.java new file mode 100644 index 00000000000..394aad3a064 --- /dev/null +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/network/EphemeryException.java @@ -0,0 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * 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. + */ + +package tech.pegasys.teku.storage.server.network; + +public class EphemeryException extends RuntimeException {} diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java index b44a63224b5..fd256b787b8 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/VersionedDatabaseFactoryTest.java @@ -15,11 +15,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static tech.pegasys.teku.storage.server.StateStorageMode.PRUNE; import java.io.File; @@ -144,48 +139,6 @@ public void createDatabase_shouldAllowAllSupportedDatabases(final DatabaseVersio assertThat(dbFactory.getDatabaseVersion()).isEqualTo(version); } - @Test - void shouldOnlyResetWhenOnEphemeryAndDueForReset() throws Exception { - VersionedDatabaseFactory dbFactorySpy = - spy( - new VersionedDatabaseFactory( - new StubMetricsSystem(), - dataDir, - StorageConfiguration.builder() - .specProvider(spec) - .eth1DepositContract(eth1Address) - .build())); - - doReturn(true).when(dbFactorySpy).shouldResetForEphemery(); - dbFactorySpy.resetDatabaseDirectories(); - verify(dbFactorySpy, times(1)).resetDatabaseDirectories(); - - try (final Database db = dbFactorySpy.createDatabase()) { - assertThat(db).isNotNull(); - verify(dbFactorySpy, times(1)).createDatabase(); - } - } - - @Test - void shouldNotResetWhenOnEphemeryButNotDueForRestAndWhenNotOnEphemery() throws Exception { - VersionedDatabaseFactory dbFactorySpy = - spy( - new VersionedDatabaseFactory( - new StubMetricsSystem(), - dataDir, - StorageConfiguration.builder() - .specProvider(spec) - .eth1DepositContract(eth1Address) - .build())); - - doReturn(false).when(dbFactorySpy).shouldResetForEphemery(); - verifyNoInteractions(dbFactorySpy); - try (final Database db = dbFactorySpy.createDatabase()) { - assertThat(db).isNotNull(); - verify(dbFactorySpy, times(1)).createDatabase(); - } - } - private void createDbDirectory(final Path dataPath) { final File dbDirectory = Paths.get(dataPath.toAbsolutePath().toString(), VersionedDatabaseFactory.DB_PATH).toFile(); From d23b3c45601866fd7ee7184f314ab6c6aa48928e Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 00:08:03 +0100 Subject: [PATCH 06/16] Update the reset method and remove migrate Signed-off-by: gconnect --- .../services/chainstorage/StorageService.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index 5a191887b7c..d2fdd85dea3 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -16,6 +16,7 @@ import static tech.pegasys.teku.infrastructure.async.AsyncRunnerFactory.DEFAULT_MAX_QUEUE_SIZE; import static tech.pegasys.teku.spec.config.Constants.STORAGE_QUERY_CHANNEL_PARALLELISM; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -84,7 +85,7 @@ protected SafeFuture doStart() { 1, DEFAULT_MAX_QUEUE_SIZE, Thread.NORM_PRIORITY - 1); - VersionedDatabaseFactory dbFactory = + final VersionedDatabaseFactory dbFactory = new VersionedDatabaseFactory( serviceConfig.getMetricsSystem(), serviceConfig.getDataDirLayout().getBeaconDataDirectory(), @@ -92,17 +93,10 @@ protected SafeFuture doStart() { try { database = dbFactory.createDatabase(); - database.migrate(); } catch (EphemeryException e) { - try { - resetDatabaseDirectories(serviceConfig); - database = dbFactory.createDatabase(); - database.migrate(); - } catch (Exception ex) { - throw new RuntimeException("Failed to reset and recreate the database.", ex); - } + database = resetDatabaseAndCreate(serviceConfig, dbFactory); + ; } - final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( serviceConfig.getMetricsSystem(), @@ -238,12 +232,21 @@ public ChainStorage getChainStorage() { } /** This method is called only on Ephemery network when reset is due. */ - void resetDatabaseDirectories(final ServiceConfig serviceConfig) throws IOException { - final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); - final Path slashProtectionDir = - serviceConfig.getDataDirLayout().getValidatorDataDirectory().resolve("slashprotection"); - deleteDirectoryRecursively(beaconDataDir); - deleteDirectoryRecursively(slashProtectionDir); + @VisibleForTesting + private Database resetDatabaseAndCreate( + final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { + try { + final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); + final Path slashProtectionDir = + serviceConfig.getDataDirLayout().getValidatorDataDirectory().resolve("slashprotection"); + deleteDirectoryRecursively(beaconDataDir); + deleteDirectoryRecursively(slashProtectionDir); + + return dbFactory.createDatabase(); + } catch (final Exception ex) { + throw new InvalidConfigurationException( + "The existing ephemery database was old, and was unable to reset it.", ex); + } } private void deleteDirectoryRecursively(final Path path) throws IOException { From 6a5d9fc0e4039eb44d742934c0144cdd805ffdf8 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 00:40:56 +0100 Subject: [PATCH 07/16] Remove private modifier Signed-off-by: gconnect --- .../pegasys/teku/services/chainstorage/StorageService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index d2fdd85dea3..151535d112b 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -95,7 +95,6 @@ protected SafeFuture doStart() { database = dbFactory.createDatabase(); } catch (EphemeryException e) { database = resetDatabaseAndCreate(serviceConfig, dbFactory); - ; } final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( @@ -233,7 +232,7 @@ public ChainStorage getChainStorage() { /** This method is called only on Ephemery network when reset is due. */ @VisibleForTesting - private Database resetDatabaseAndCreate( + Database resetDatabaseAndCreate( final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { try { final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); From c2aced4201eeb620c655b436b26a906c10f53e08 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 00:43:41 +0100 Subject: [PATCH 08/16] Apply spotlessApply Signed-off-by: gconnect --- .../tech/pegasys/teku/services/chainstorage/StorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index 151535d112b..5a6bc95a3f7 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -232,7 +232,7 @@ public ChainStorage getChainStorage() { /** This method is called only on Ephemery network when reset is due. */ @VisibleForTesting - Database resetDatabaseAndCreate( + Database resetDatabaseAndCreate( final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { try { final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); From 6d2cf4351acf516db735c972153851e774a2102b Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 22:25:28 +0100 Subject: [PATCH 09/16] Apply spotlessApply Signed-off-by: gconnect --- .../chainstorage/EphemeryDatabaseReset.java | 2 + .../EphemeryDatabaseResetTest.java | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java create mode 100644 services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java new file mode 100644 index 00000000000..a37c8691e50 --- /dev/null +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -0,0 +1,2 @@ +package tech.pegasys.teku.services.chainstorage;public class EphemeryDatabaseReset { +} diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java new file mode 100644 index 00000000000..3d27325ce1c --- /dev/null +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -0,0 +1,111 @@ +package tech.pegasys.teku.services.chainstorage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; +import tech.pegasys.teku.service.serviceutils.ServiceConfig; +import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout; +import tech.pegasys.teku.storage.server.VersionedDatabaseFactory; +import tech.pegasys.teku.storage.server.Database; + +import java.io.IOException; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class StorageServiceTest { + + + private ServiceConfig serviceConfig; + + @Mock + private VersionedDatabaseFactory dbFactory; + + @Mock + private Path beaconDataDir; + + @Mock + private Path slashProtectionDir; + + @Mock + private FileUtils fileUtils; + + @Mock + private EphemeryDatabaseReset ephemeryDatabaseReset; + + @Mock + private Database database; + +// private StorageService storageService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + serviceConfig = mock(ServiceConfig.class); + DataDirLayout dataDirLayout = mock(DataDirLayout.class); + when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); + when(serviceConfig.getDataDirLayout().getBeaconDataDirectory()).thenReturn(beaconDataDir); + when(serviceConfig.getDataDirLayout().getValidatorDataDirectory()).thenReturn(slashProtectionDir); + } + + @Test + void shouldResetDirectoriesAndCreateDatabase() throws IOException { + // Mock database creation + when(dbFactory.createDatabase()).thenReturn(database); + + // Call the method + Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + + // Verify that directories were deleted + verify(fileUtils).deleteDirectoryRecursively(beaconDataDir); + verify(fileUtils).deleteDirectoryRecursively(slashProtectionDir.resolve("slashprotection")); + + // Verify that the database was created + verify(dbFactory).createDatabase(); + + // Assert that the result is the mock database + assertEquals(database, result); + } + + @Test + void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws IOException { + // Simulate an exception during directory deletion + doThrow(new IOException("Failed to delete directory")).when(fileUtils).deleteDirectoryRecursively(beaconDataDir); + + // Expect InvalidConfigurationException to be thrown + assertThrows(InvalidConfigurationException.class, () -> { + ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + }); + + // Verify that database creation was not attempted + verify(dbFactory, never()).createDatabase(); + } + + @Test + void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws IOException { + // Simulate successful directory deletion but failure in database creation + when(dbFactory.createDatabase()).thenThrow(new RuntimeException("Database creation failed")); + + // Expect InvalidConfigurationException to be thrown + assertThrows(InvalidConfigurationException.class, () -> { + ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + }); + + // Verify that directories were deleted + verify(fileUtils).deleteDirectoryRecursively(beaconDataDir); + verify(fileUtils).deleteDirectoryRecursively(slashProtectionDir.resolve("slashprotection")); + + // Verify that database creation was attempted + verify(dbFactory).createDatabase(); + } +} + From 7b2871ceda2e45204951ed7cf7fac19402c363f0 Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 22:50:38 +0100 Subject: [PATCH 10/16] Create custom Ephemery reset class and test Signed-off-by: gconnect --- .../chainstorage/EphemeryDatabaseReset.java | 63 +++++- .../services/chainstorage/StorageService.java | 43 +--- .../EphemeryDatabaseResetTest.java | 204 +++++++++--------- 3 files changed, 171 insertions(+), 139 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java index a37c8691e50..ea8fe45cc0b 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -1,2 +1,63 @@ -package tech.pegasys.teku.services.chainstorage;public class EphemeryDatabaseReset { +/* + * Copyright Consensys Software Inc., 2024 + * + * 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. + */ + +package tech.pegasys.teku.services.chainstorage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; +import tech.pegasys.teku.service.serviceutils.ServiceConfig; +import tech.pegasys.teku.storage.server.Database; +import tech.pegasys.teku.storage.server.VersionedDatabaseFactory; + +public class EphemeryDatabaseReset { + + /** This method is called only on Ephemery network when reset is due. */ + Database resetDatabaseAndCreate( + final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { + try { + final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); + final Path validatorDataDir = serviceConfig.getDataDirLayout().getValidatorDataDirectory(); + final Path slashProtectionDir; + if (validatorDataDir.endsWith("slashprotection")) { + slashProtectionDir = validatorDataDir; + } else { + slashProtectionDir = validatorDataDir.resolve("slashprotection"); + } + deleteDirectoryRecursively(beaconDataDir); + deleteDirectoryRecursively(slashProtectionDir); + return dbFactory.createDatabase(); + } catch (final Exception ex) { + throw new InvalidConfigurationException( + "The existing ephemery database was old, and was unable to reset it.", ex); + } + } + + void deleteDirectoryRecursively(final Path path) throws IOException { + if (Files.isDirectory(path)) { + try (var stream = Files.walk(path)) { + stream + .sorted((o1, o2) -> o2.compareTo(o1)) + .forEach( + p -> { + try { + Files.delete(p); + } catch (IOException e) { + throw new RuntimeException("Failed to delete file: " + p, e); + } + }); + } + } + } } diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index 5a6bc95a3f7..12631b89626 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -16,10 +16,6 @@ import static tech.pegasys.teku.infrastructure.async.AsyncRunnerFactory.DEFAULT_MAX_QUEUE_SIZE; import static tech.pegasys.teku.spec.config.Constants.STORAGE_QUERY_CHANNEL_PARALLELISM; -import com.google.common.annotations.VisibleForTesting; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -63,6 +59,7 @@ public class StorageService extends Service implements StorageServiceFacade { private final boolean depositSnapshotStorageEnabled; private final boolean blobSidecarsStorageCountersEnabled; private static final Logger LOG = LogManager.getLogger(); + private final EphemeryDatabaseReset ephemeryDatabaseReset; public StorageService( final ServiceConfig serviceConfig, @@ -73,6 +70,7 @@ public StorageService( this.config = storageConfiguration; this.depositSnapshotStorageEnabled = depositSnapshotStorageEnabled; this.blobSidecarsStorageCountersEnabled = blobSidecarsStorageCountersEnabled; + this.ephemeryDatabaseReset = new EphemeryDatabaseReset(); } @Override @@ -94,7 +92,7 @@ protected SafeFuture doStart() { try { database = dbFactory.createDatabase(); } catch (EphemeryException e) { - database = resetDatabaseAndCreate(serviceConfig, dbFactory); + database = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); } final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( @@ -229,39 +227,4 @@ protected SafeFuture doStop() { public ChainStorage getChainStorage() { return chainStorage; } - - /** This method is called only on Ephemery network when reset is due. */ - @VisibleForTesting - Database resetDatabaseAndCreate( - final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { - try { - final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); - final Path slashProtectionDir = - serviceConfig.getDataDirLayout().getValidatorDataDirectory().resolve("slashprotection"); - deleteDirectoryRecursively(beaconDataDir); - deleteDirectoryRecursively(slashProtectionDir); - - return dbFactory.createDatabase(); - } catch (final Exception ex) { - throw new InvalidConfigurationException( - "The existing ephemery database was old, and was unable to reset it.", ex); - } - } - - private void deleteDirectoryRecursively(final Path path) throws IOException { - if (Files.isDirectory(path)) { - try (var stream = Files.walk(path)) { - stream - .sorted((o1, o2) -> o2.compareTo(o1)) - .forEach( - p -> { - try { - Files.delete(p); - } catch (IOException e) { - throw new RuntimeException("Failed to delete file: " + p, e); - } - }); - } - } - } } diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 3d27325ce1c..198761f0622 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -1,5 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * 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. + */ + package tech.pegasys.teku.services.chainstorage; +import static java.nio.file.Files.createTempDirectory; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -8,104 +33,87 @@ import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; import tech.pegasys.teku.service.serviceutils.ServiceConfig; import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout; -import tech.pegasys.teku.storage.server.VersionedDatabaseFactory; import tech.pegasys.teku.storage.server.Database; +import tech.pegasys.teku.storage.server.VersionedDatabaseFactory; -import java.io.IOException; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class StorageServiceTest { - - - private ServiceConfig serviceConfig; - - @Mock - private VersionedDatabaseFactory dbFactory; - - @Mock - private Path beaconDataDir; - - @Mock - private Path slashProtectionDir; - - @Mock - private FileUtils fileUtils; - - @Mock - private EphemeryDatabaseReset ephemeryDatabaseReset; - - @Mock - private Database database; - -// private StorageService storageService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - serviceConfig = mock(ServiceConfig.class); - DataDirLayout dataDirLayout = mock(DataDirLayout.class); - when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); - when(serviceConfig.getDataDirLayout().getBeaconDataDirectory()).thenReturn(beaconDataDir); - when(serviceConfig.getDataDirLayout().getValidatorDataDirectory()).thenReturn(slashProtectionDir); - } - - @Test - void shouldResetDirectoriesAndCreateDatabase() throws IOException { - // Mock database creation - when(dbFactory.createDatabase()).thenReturn(database); - - // Call the method - Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); - - // Verify that directories were deleted - verify(fileUtils).deleteDirectoryRecursively(beaconDataDir); - verify(fileUtils).deleteDirectoryRecursively(slashProtectionDir.resolve("slashprotection")); - - // Verify that the database was created - verify(dbFactory).createDatabase(); - - // Assert that the result is the mock database - assertEquals(database, result); - } - - @Test - void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws IOException { - // Simulate an exception during directory deletion - doThrow(new IOException("Failed to delete directory")).when(fileUtils).deleteDirectoryRecursively(beaconDataDir); - - // Expect InvalidConfigurationException to be thrown - assertThrows(InvalidConfigurationException.class, () -> { - ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); - }); - - // Verify that database creation was not attempted - verify(dbFactory, never()).createDatabase(); - } - - @Test - void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws IOException { - // Simulate successful directory deletion but failure in database creation - when(dbFactory.createDatabase()).thenThrow(new RuntimeException("Database creation failed")); - - // Expect InvalidConfigurationException to be thrown - assertThrows(InvalidConfigurationException.class, () -> { - ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); - }); - - // Verify that directories were deleted - verify(fileUtils).deleteDirectoryRecursively(beaconDataDir); - verify(fileUtils).deleteDirectoryRecursively(slashProtectionDir.resolve("slashprotection")); - - // Verify that database creation was attempted - verify(dbFactory).createDatabase(); - } +class EphemeryDatabaseResetTest { + + @Mock private ServiceConfig serviceConfig; + + @Mock private VersionedDatabaseFactory dbFactory; + + @Mock private DataDirLayout dataDirLayout; + private Path beaconDataDir; + private Path validatorDataDir; + private Path resolvedSlashProtectionDir; + + @Mock private Database database; + + @InjectMocks private EphemeryDatabaseReset ephemeryDatabaseReset; + + @BeforeEach + void setUp() throws IOException { + MockitoAnnotations.openMocks(this); + ephemeryDatabaseReset = spy(new EphemeryDatabaseReset()); + beaconDataDir = createTempDirectory("beaconDataDir"); + validatorDataDir = createTempDirectory("validatorDataDir"); + resolvedSlashProtectionDir = validatorDataDir.resolve("slashprotection"); + when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); + when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); + when(dataDirLayout.getValidatorDataDirectory()).thenReturn(validatorDataDir); + when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) + .thenReturn(resolvedSlashProtectionDir); + } + + @Test + void shouldResetDirectoriesAndCreateDatabase() throws IOException { + + when(dbFactory.createDatabase()).thenReturn(database); + when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); + when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) + .thenReturn(resolvedSlashProtectionDir); + + Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); + verify(dbFactory).createDatabase(); + assertEquals(database, result); + } + + @Test + void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws IOException { + doThrow(new IOException("Failed to delete directory")) + .when(ephemeryDatabaseReset) + .deleteDirectoryRecursively(beaconDataDir); + final InvalidConfigurationException exception = + assertThrows( + InvalidConfigurationException.class, + () -> { + ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + }); + assertEquals( + "The existing ephemery database was old, and was unable to reset it.", + exception.getMessage()); + verify(dbFactory, never()).createDatabase(); + verify(ephemeryDatabaseReset, never()).deleteDirectoryRecursively(resolvedSlashProtectionDir); + } + + @Test + void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws IOException { + doNothing().when(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + doNothing().when(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); + when(dbFactory.createDatabase()).thenThrow(new RuntimeException("Database creation failed")); + final InvalidConfigurationException exception = + assertThrows( + InvalidConfigurationException.class, + () -> { + ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + }); + assertEquals( + "The existing ephemery database was old, and was unable to reset it.", + exception.getMessage()); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); + verify(dbFactory).createDatabase(); + } } - From d9d1951b7b6c215b7fb390e5ec2999c468bbf36e Mon Sep 17 00:00:00 2001 From: gconnect Date: Tue, 1 Oct 2024 22:56:26 +0100 Subject: [PATCH 11/16] Add fnal and move global variable to local variable Signed-off-by: gconnect --- .../services/chainstorage/EphemeryDatabaseResetTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 198761f0622..91d4485d3ae 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -44,7 +44,6 @@ class EphemeryDatabaseResetTest { @Mock private DataDirLayout dataDirLayout; private Path beaconDataDir; - private Path validatorDataDir; private Path resolvedSlashProtectionDir; @Mock private Database database; @@ -54,9 +53,10 @@ class EphemeryDatabaseResetTest { @BeforeEach void setUp() throws IOException { MockitoAnnotations.openMocks(this); + ephemeryDatabaseReset = spy(new EphemeryDatabaseReset()); beaconDataDir = createTempDirectory("beaconDataDir"); - validatorDataDir = createTempDirectory("validatorDataDir"); + final Path validatorDataDir = createTempDirectory("validatorDataDir"); resolvedSlashProtectionDir = validatorDataDir.resolve("slashprotection"); when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); @@ -73,7 +73,7 @@ void shouldResetDirectoriesAndCreateDatabase() throws IOException { when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) .thenReturn(resolvedSlashProtectionDir); - Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + final Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); verify(dbFactory).createDatabase(); From 0d479595131caf7ff4fb1a2fc635231aac0e9325 Mon Sep 17 00:00:00 2001 From: gconnect Date: Wed, 2 Oct 2024 00:31:07 +0100 Subject: [PATCH 12/16] Remove repeated code Signed-off-by: gconnect --- .../services/chainstorage/EphemeryDatabaseResetTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 91d4485d3ae..1e10288d3fd 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -53,7 +53,6 @@ class EphemeryDatabaseResetTest { @BeforeEach void setUp() throws IOException { MockitoAnnotations.openMocks(this); - ephemeryDatabaseReset = spy(new EphemeryDatabaseReset()); beaconDataDir = createTempDirectory("beaconDataDir"); final Path validatorDataDir = createTempDirectory("validatorDataDir"); @@ -67,12 +66,7 @@ void setUp() throws IOException { @Test void shouldResetDirectoriesAndCreateDatabase() throws IOException { - when(dbFactory.createDatabase()).thenReturn(database); - when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); - when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) - .thenReturn(resolvedSlashProtectionDir); - final Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); From f73e49119b0734a9fec11c9eb25cb544db76189e Mon Sep 17 00:00:00 2001 From: gconnect Date: Wed, 2 Oct 2024 05:48:44 +0100 Subject: [PATCH 13/16] Ensure reset method delete specific files and update test and change global to local variable Signed-off-by: gconnect --- .../chainstorage/EphemeryDatabaseReset.java | 16 ++++++++-- .../services/chainstorage/StorageService.java | 5 +-- .../EphemeryDatabaseResetTest.java | 32 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java index ea8fe45cc0b..64180b819e4 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -27,7 +27,12 @@ public class EphemeryDatabaseReset { Database resetDatabaseAndCreate( final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { try { - final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); + final Path beaconDataDir = + serviceConfig.getDataDirLayout().getBeaconDataDirectory().getParent(); + final Path dbDataDir = beaconDataDir.resolve("db"); + final Path networkFile = beaconDataDir.resolve("network.yml"); + System.out.println("dbDataDir" + dbDataDir); + System.out.println("networkFile" + networkFile); final Path validatorDataDir = serviceConfig.getDataDirLayout().getValidatorDataDirectory(); final Path slashProtectionDir; if (validatorDataDir.endsWith("slashprotection")) { @@ -35,7 +40,8 @@ Database resetDatabaseAndCreate( } else { slashProtectionDir = validatorDataDir.resolve("slashprotection"); } - deleteDirectoryRecursively(beaconDataDir); + deleteDirectoryRecursively(dbDataDir); + deleteFile(networkFile); deleteDirectoryRecursively(slashProtectionDir); return dbFactory.createDatabase(); } catch (final Exception ex) { @@ -44,6 +50,12 @@ Database resetDatabaseAndCreate( } } + void deleteFile(final Path path) throws IOException { + if (Files.exists(path)) { + Files.delete(path); + } + } + void deleteDirectoryRecursively(final Path path) throws IOException { if (Files.isDirectory(path)) { try (var stream = Files.walk(path)) { diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index 12631b89626..b4b46894df0 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -59,7 +59,6 @@ public class StorageService extends Service implements StorageServiceFacade { private final boolean depositSnapshotStorageEnabled; private final boolean blobSidecarsStorageCountersEnabled; private static final Logger LOG = LogManager.getLogger(); - private final EphemeryDatabaseReset ephemeryDatabaseReset; public StorageService( final ServiceConfig serviceConfig, @@ -70,7 +69,6 @@ public StorageService( this.config = storageConfiguration; this.depositSnapshotStorageEnabled = depositSnapshotStorageEnabled; this.blobSidecarsStorageCountersEnabled = blobSidecarsStorageCountersEnabled; - this.ephemeryDatabaseReset = new EphemeryDatabaseReset(); } @Override @@ -92,7 +90,10 @@ protected SafeFuture doStart() { try { database = dbFactory.createDatabase(); } catch (EphemeryException e) { + final EphemeryDatabaseReset ephemeryDatabaseReset = new EphemeryDatabaseReset(); database = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); + LOG.warn( + "Ephemery network deposit contract id has updated, resetting the stored database and slashing protection data."); } final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 1e10288d3fd..18fd019ce1d 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -16,6 +16,7 @@ import static java.nio.file.Files.createTempDirectory; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -23,7 +24,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,8 +47,9 @@ class EphemeryDatabaseResetTest { @Mock private DataDirLayout dataDirLayout; private Path beaconDataDir; + private Path dbDataDir; private Path resolvedSlashProtectionDir; - + private File networkFile; @Mock private Database database; @InjectMocks private EphemeryDatabaseReset ephemeryDatabaseReset; @@ -55,22 +59,36 @@ void setUp() throws IOException { MockitoAnnotations.openMocks(this); ephemeryDatabaseReset = spy(new EphemeryDatabaseReset()); beaconDataDir = createTempDirectory("beaconDataDir"); + dbDataDir = beaconDataDir.resolve("db"); + networkFile = new File(beaconDataDir.toFile(), "network.yml"); final Path validatorDataDir = createTempDirectory("validatorDataDir"); resolvedSlashProtectionDir = validatorDataDir.resolve("slashprotection"); + when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); + when(dataDirLayout.getBeaconDataDirectory().resolve("db")).thenReturn(dbDataDir); + when(dataDirLayout.getBeaconDataDirectory().resolve("network.yml")) + .thenReturn(networkFile.toPath()); when(dataDirLayout.getValidatorDataDirectory()).thenReturn(validatorDataDir); when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) .thenReturn(resolvedSlashProtectionDir); } @Test - void shouldResetDirectoriesAndCreateDatabase() throws IOException { + void shouldResetSpecificDirectoriesAndCreateDatabase() throws IOException { + Path kvStoreDir = beaconDataDir.resolve("kvstore"); + Files.createDirectory(kvStoreDir); + Path dbVersion = beaconDataDir.resolve("db.version"); + Files.createFile(dbVersion); + when(dbFactory.createDatabase()).thenReturn(database); + final Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); - verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(dbDataDir); + verify(ephemeryDatabaseReset).deleteFile(networkFile.toPath()); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); - verify(dbFactory).createDatabase(); + assertTrue(Files.exists(kvStoreDir)); + assertTrue(Files.exists(dbVersion)); assertEquals(database, result); } @@ -78,7 +96,7 @@ void shouldResetDirectoriesAndCreateDatabase() throws IOException { void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws IOException { doThrow(new IOException("Failed to delete directory")) .when(ephemeryDatabaseReset) - .deleteDirectoryRecursively(beaconDataDir); + .deleteDirectoryRecursively(dbDataDir); final InvalidConfigurationException exception = assertThrows( InvalidConfigurationException.class, @@ -94,7 +112,7 @@ void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws @Test void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws IOException { - doNothing().when(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + doNothing().when(ephemeryDatabaseReset).deleteDirectoryRecursively(dbDataDir); doNothing().when(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); when(dbFactory.createDatabase()).thenThrow(new RuntimeException("Database creation failed")); final InvalidConfigurationException exception = @@ -106,7 +124,7 @@ void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws assertEquals( "The existing ephemery database was old, and was unable to reset it.", exception.getMessage()); - verify(ephemeryDatabaseReset).deleteDirectoryRecursively(beaconDataDir); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(dbDataDir); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); verify(dbFactory).createDatabase(); } From f95a61ad768c34f49f5d85bb3b1e1bb34b6eeab9 Mon Sep 17 00:00:00 2001 From: gconnect Date: Wed, 2 Oct 2024 05:50:06 +0100 Subject: [PATCH 14/16] Remove print statement and add final Signed-off-by: gconnect --- .../teku/services/chainstorage/EphemeryDatabaseReset.java | 2 -- .../teku/services/chainstorage/EphemeryDatabaseResetTest.java | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java index 64180b819e4..65cb26a0910 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -31,8 +31,6 @@ Database resetDatabaseAndCreate( serviceConfig.getDataDirLayout().getBeaconDataDirectory().getParent(); final Path dbDataDir = beaconDataDir.resolve("db"); final Path networkFile = beaconDataDir.resolve("network.yml"); - System.out.println("dbDataDir" + dbDataDir); - System.out.println("networkFile" + networkFile); final Path validatorDataDir = serviceConfig.getDataDirLayout().getValidatorDataDirectory(); final Path slashProtectionDir; if (validatorDataDir.endsWith("slashprotection")) { diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 18fd019ce1d..9821ad5a006 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -76,9 +76,9 @@ void setUp() throws IOException { @Test void shouldResetSpecificDirectoriesAndCreateDatabase() throws IOException { - Path kvStoreDir = beaconDataDir.resolve("kvstore"); + final Path kvStoreDir = beaconDataDir.resolve("kvstore"); Files.createDirectory(kvStoreDir); - Path dbVersion = beaconDataDir.resolve("db.version"); + final Path dbVersion = beaconDataDir.resolve("db.version"); Files.createFile(dbVersion); when(dbFactory.createDatabase()).thenReturn(database); From b2bcefd02641d7404e1ac4e39aec4624a57c077c Mon Sep 17 00:00:00 2001 From: gconnect Date: Wed, 2 Oct 2024 08:50:37 +0100 Subject: [PATCH 15/16] Update the recursive delete method and test Signed-off-by: gconnect --- .../chainstorage/EphemeryDatabaseReset.java | 48 +++++++++++-------- .../services/chainstorage/StorageService.java | 3 +- .../EphemeryDatabaseResetTest.java | 26 +++++----- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java index 65cb26a0910..b4d4d727cd2 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -27,8 +27,7 @@ public class EphemeryDatabaseReset { Database resetDatabaseAndCreate( final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) { try { - final Path beaconDataDir = - serviceConfig.getDataDirLayout().getBeaconDataDirectory().getParent(); + final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory(); final Path dbDataDir = beaconDataDir.resolve("db"); final Path networkFile = beaconDataDir.resolve("network.yml"); final Path validatorDataDir = serviceConfig.getDataDirLayout().getValidatorDataDirectory(); @@ -38,8 +37,13 @@ Database resetDatabaseAndCreate( } else { slashProtectionDir = validatorDataDir.resolve("slashprotection"); } + + System.out.println("beaconDir" + beaconDataDir); + System.out.println("networkFile" + networkFile); + System.out.println("dbdir" + dbDataDir); + deleteDirectoryRecursively(dbDataDir); - deleteFile(networkFile); + deleteDirectoryRecursively(networkFile); deleteDirectoryRecursively(slashProtectionDir); return dbFactory.createDatabase(); } catch (final Exception ex) { @@ -48,25 +52,27 @@ Database resetDatabaseAndCreate( } } - void deleteFile(final Path path) throws IOException { - if (Files.exists(path)) { - Files.delete(path); - } - } - void deleteDirectoryRecursively(final Path path) throws IOException { - if (Files.isDirectory(path)) { - try (var stream = Files.walk(path)) { - stream - .sorted((o1, o2) -> o2.compareTo(o1)) - .forEach( - p -> { - try { - Files.delete(p); - } catch (IOException e) { - throw new RuntimeException("Failed to delete file: " + p, e); - } - }); + if (Files.exists(path)) { + if (Files.isDirectory(path)) { + try (var stream = Files.walk(path)) { + stream + .sorted((o1, o2) -> o2.compareTo(o1)) + .forEach( + p -> { + try { + Files.delete(p); + } catch (IOException e) { + throw new RuntimeException("Failed to delete file/directory: " + p, e); + } + }); + } + } else { + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException("Failed to delete file: " + path, e); + } } } } diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index b4b46894df0..58b7c770b0f 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -86,14 +86,13 @@ protected SafeFuture doStart() { serviceConfig.getMetricsSystem(), serviceConfig.getDataDirLayout().getBeaconDataDirectory(), config); - try { database = dbFactory.createDatabase(); } catch (EphemeryException e) { final EphemeryDatabaseReset ephemeryDatabaseReset = new EphemeryDatabaseReset(); - database = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); LOG.warn( "Ephemery network deposit contract id has updated, resetting the stored database and slashing protection data."); + database = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); } final SettableLabelledGauge pruningTimingsLabelledGauge = SettableLabelledGauge.create( diff --git a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java index 9821ad5a006..1121926ce0c 100644 --- a/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java +++ b/services/chainstorage/src/test/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseResetTest.java @@ -22,15 +22,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; @@ -49,26 +48,27 @@ class EphemeryDatabaseResetTest { private Path beaconDataDir; private Path dbDataDir; private Path resolvedSlashProtectionDir; - private File networkFile; + private Path networkFilePath; @Mock private Database database; - @InjectMocks private EphemeryDatabaseReset ephemeryDatabaseReset; + @Mock private EphemeryDatabaseReset ephemeryDatabaseReset; @BeforeEach void setUp() throws IOException { MockitoAnnotations.openMocks(this); ephemeryDatabaseReset = spy(new EphemeryDatabaseReset()); - beaconDataDir = createTempDirectory("beaconDataDir"); + beaconDataDir = createTempDirectory("beacon"); dbDataDir = beaconDataDir.resolve("db"); - networkFile = new File(beaconDataDir.toFile(), "network.yml"); - final Path validatorDataDir = createTempDirectory("validatorDataDir"); + Files.createDirectory(dbDataDir); + Path networkFile = beaconDataDir.resolve("network.yml"); + Files.createFile(networkFile); + networkFilePath = networkFile; + + final Path validatorDataDir = createTempDirectory("validator"); resolvedSlashProtectionDir = validatorDataDir.resolve("slashprotection"); when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout); when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir); - when(dataDirLayout.getBeaconDataDirectory().resolve("db")).thenReturn(dbDataDir); - when(dataDirLayout.getBeaconDataDirectory().resolve("network.yml")) - .thenReturn(networkFile.toPath()); when(dataDirLayout.getValidatorDataDirectory()).thenReturn(validatorDataDir); when(dataDirLayout.getValidatorDataDirectory().resolve("slashprotection")) .thenReturn(resolvedSlashProtectionDir); @@ -85,8 +85,12 @@ void shouldResetSpecificDirectoriesAndCreateDatabase() throws IOException { final Database result = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(dbDataDir); - verify(ephemeryDatabaseReset).deleteFile(networkFile.toPath()); + verify(ephemeryDatabaseReset).deleteDirectoryRecursively(networkFilePath); verify(ephemeryDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir); + + verify(dbFactory).createDatabase(); + verifyNoMoreInteractions(dbFactory); + assertTrue(Files.exists(kvStoreDir)); assertTrue(Files.exists(dbVersion)); assertEquals(database, result); From 6cdc9948675d37ec014fce11ef7f8f782dd6525a Mon Sep 17 00:00:00 2001 From: gconnect Date: Thu, 3 Oct 2024 00:44:07 +0100 Subject: [PATCH 16/16] Remove print statement Signed-off-by: gconnect --- .../teku/services/chainstorage/EphemeryDatabaseReset.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java index b4d4d727cd2..92de802252b 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java @@ -37,11 +37,6 @@ Database resetDatabaseAndCreate( } else { slashProtectionDir = validatorDataDir.resolve("slashprotection"); } - - System.out.println("beaconDir" + beaconDataDir); - System.out.println("networkFile" + networkFile); - System.out.println("dbdir" + dbDataDir); - deleteDirectoryRecursively(dbDataDir); deleteDirectoryRecursively(networkFile); deleteDirectoryRecursively(slashProtectionDir);