Skip to content

Commit 0ddf9e1

Browse files
authored
Implemented a cli arg to clear beacon database on startup (#10315)
chose `--force-clear-db` of the 2 suggested options as it seemed closer to our naming. We don't really follow that pattern generally but it's preferable to not create another random flag to remember. fixes #10313
1 parent 00c6e4c commit 0ddf9e1

File tree

13 files changed

+327
-164
lines changed

13 files changed

+327
-164
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
### Additions and Improvements
1313

14-
- New CLI flag `--rest-api-getblobs-sidecars-download-enabled` allows the beacon node to serve `getBlobs` REST API responses by attempting to fetch missing blob sidecars from the p2p network. The new flag `--rest-api-getblobs-sidecars-download-timeout` controls the network fetch timeout (default: 5 seconds).
14+
- New CLI flag `--rest-api-getblobs-sidecars-download-enabled` allows the beacon node to serve `getBlobs` REST API responses by attempting to fetch missing blob sidecars from the p2p network. The new flag `--rest-api-getblobs-sidecars-download-timeout` controls the network fetch timeout (default: 5 seconds).
15+
- New CLI flag `--force-clear-db` to remove the beacon database on startup.
1516

1617

1718
### Bug Fixes

services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/EphemeryDatabaseReset.java renamed to services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/BeaconDatabaseReset.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313

1414
package tech.pegasys.teku.services.chainstorage;
1515

16+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.ARCHIVE_PATH;
17+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.DB_PATH;
18+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.DB_VERSION_FILENAME;
19+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.METADATA_FILENAME;
20+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.NETWORK_FILENAME;
21+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.SLASHING_PROTECTION_PATH;
22+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.STORAGE_MODE_FILENAME;
23+
1624
import java.io.IOException;
1725
import java.nio.file.Files;
1826
import java.nio.file.Path;
@@ -21,21 +29,21 @@
2129
import tech.pegasys.teku.storage.server.Database;
2230
import tech.pegasys.teku.storage.server.VersionedDatabaseFactory;
2331

24-
public class EphemeryDatabaseReset {
32+
public class BeaconDatabaseReset {
2533

2634
/** This method is called only on Ephemery network when reset is due. */
27-
Database resetDatabaseAndCreate(
35+
Database resetStorageForEphemeryAndCreate(
2836
final ServiceConfig serviceConfig, final VersionedDatabaseFactory dbFactory) {
2937
try {
3038
final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory();
31-
final Path dbDataDir = beaconDataDir.resolve("db");
32-
final Path networkFile = beaconDataDir.resolve("network.yml");
39+
final Path dbDataDir = beaconDataDir.resolve(DB_PATH);
40+
final Path networkFile = beaconDataDir.resolve(NETWORK_FILENAME);
3341
final Path validatorDataDir = serviceConfig.getDataDirLayout().getValidatorDataDirectory();
3442
final Path slashProtectionDir;
35-
if (validatorDataDir.endsWith("slashprotection")) {
43+
if (validatorDataDir.endsWith(SLASHING_PROTECTION_PATH)) {
3644
slashProtectionDir = validatorDataDir;
3745
} else {
38-
slashProtectionDir = validatorDataDir.resolve("slashprotection");
46+
slashProtectionDir = validatorDataDir.resolve(SLASHING_PROTECTION_PATH);
3947
}
4048
deleteDirectoryRecursively(dbDataDir);
4149
deleteDirectoryRecursively(networkFile);
@@ -47,6 +55,27 @@ Database resetDatabaseAndCreate(
4755
}
4856
}
4957

58+
public void clearBeaconDatabase(final ServiceConfig serviceConfig) {
59+
try {
60+
final Path beaconDataDir = serviceConfig.getDataDirLayout().getBeaconDataDirectory();
61+
deleteDirectoryRecursively(beaconDataDir.resolve(DB_PATH));
62+
deleteDirectoryRecursively(beaconDataDir.resolve(ARCHIVE_PATH));
63+
deleteFileIfExists(beaconDataDir.resolve(NETWORK_FILENAME));
64+
deleteFileIfExists(beaconDataDir.resolve(METADATA_FILENAME));
65+
deleteFileIfExists(beaconDataDir.resolve(DB_VERSION_FILENAME));
66+
deleteFileIfExists(beaconDataDir.resolve(STORAGE_MODE_FILENAME));
67+
} catch (final Exception ex) {
68+
throw new InvalidConfigurationException(
69+
"Failed to clear beacon database with --force-clear-db flag.", ex);
70+
}
71+
}
72+
73+
private void deleteFileIfExists(final Path path) throws IOException {
74+
if (Files.exists(path)) {
75+
Files.delete(path);
76+
}
77+
}
78+
5079
void deleteDirectoryRecursively(final Path path) throws IOException {
5180
if (Files.exists(path)) {
5281
if (Files.isDirectory(path)) {

services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,21 @@ protected SafeFuture<?> doStart() {
103103
serviceConfig.getDataDirLayout().getBeaconDataDirectory(),
104104
config,
105105
maybeNetwork);
106+
if (config.isForceClearDb()) {
107+
LOG.warn(
108+
"Force clear database flag is set. Deleting all beacon chain database files. "
109+
+ "Validator slashing protection data will be preserved.");
110+
final BeaconDatabaseReset databaseReset = new BeaconDatabaseReset();
111+
databaseReset.clearBeaconDatabase(serviceConfig);
112+
}
106113
try {
107114
database = dbFactory.createDatabase();
108115
} catch (EphemeryException e) {
109-
final EphemeryDatabaseReset ephemeryDatabaseReset = new EphemeryDatabaseReset();
116+
final BeaconDatabaseReset beaconDatabaseReset = new BeaconDatabaseReset();
110117
LOG.warn(
111118
"Ephemery network deposit contract id has updated, resetting the stored database and slashing protection data.");
112-
database = ephemeryDatabaseReset.resetDatabaseAndCreate(serviceConfig, dbFactory);
119+
database =
120+
beaconDatabaseReset.resetStorageForEphemeryAndCreate(serviceConfig, dbFactory);
113121
}
114122

115123
final SettableLabelledGauge pruningTimingsLabelledGauge =
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright Consensys Software Inc., 2026
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package tech.pegasys.teku.services.chainstorage;
15+
16+
import static java.nio.file.Files.createTempDirectory;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
import static org.mockito.Mockito.doNothing;
22+
import static org.mockito.Mockito.doThrow;
23+
import static org.mockito.Mockito.never;
24+
import static org.mockito.Mockito.spy;
25+
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.verifyNoMoreInteractions;
27+
import static org.mockito.Mockito.when;
28+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.ARCHIVE_PATH;
29+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.DB_PATH;
30+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.DB_VERSION_FILENAME;
31+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.METADATA_FILENAME;
32+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.NETWORK_FILENAME;
33+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.SLASHING_PROTECTION_PATH;
34+
import static tech.pegasys.teku.storage.server.VersionedDatabaseFactory.STORAGE_MODE_FILENAME;
35+
36+
import java.io.IOException;
37+
import java.nio.file.Files;
38+
import java.nio.file.Path;
39+
import org.junit.jupiter.api.BeforeEach;
40+
import org.junit.jupiter.api.Test;
41+
import org.mockito.Mock;
42+
import org.mockito.MockitoAnnotations;
43+
import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException;
44+
import tech.pegasys.teku.service.serviceutils.ServiceConfig;
45+
import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout;
46+
import tech.pegasys.teku.storage.server.Database;
47+
import tech.pegasys.teku.storage.server.VersionedDatabaseFactory;
48+
49+
class BeaconDatabaseResetTest {
50+
51+
@Mock private ServiceConfig serviceConfig;
52+
53+
@Mock private VersionedDatabaseFactory dbFactory;
54+
55+
@Mock private DataDirLayout dataDirLayout;
56+
private Path beaconDataDir;
57+
private Path dbDataDir;
58+
private Path resolvedSlashProtectionDir;
59+
private Path networkFilePath;
60+
@Mock private Database database;
61+
62+
private BeaconDatabaseReset beaconDatabaseReset;
63+
64+
@BeforeEach
65+
void setUp() throws IOException {
66+
MockitoAnnotations.openMocks(this);
67+
beaconDatabaseReset = spy(new BeaconDatabaseReset());
68+
beaconDataDir = createTempDirectory("beacon");
69+
dbDataDir = beaconDataDir.resolve(DB_PATH);
70+
Files.createDirectory(dbDataDir);
71+
Path networkFile = beaconDataDir.resolve(NETWORK_FILENAME);
72+
Files.createFile(networkFile);
73+
networkFilePath = networkFile;
74+
75+
final Path validatorDataDir = createTempDirectory("validator");
76+
resolvedSlashProtectionDir = validatorDataDir.resolve(SLASHING_PROTECTION_PATH);
77+
78+
when(serviceConfig.getDataDirLayout()).thenReturn(dataDirLayout);
79+
when(dataDirLayout.getBeaconDataDirectory()).thenReturn(beaconDataDir);
80+
when(dataDirLayout.getValidatorDataDirectory()).thenReturn(validatorDataDir);
81+
when(dataDirLayout.getValidatorDataDirectory().resolve(SLASHING_PROTECTION_PATH))
82+
.thenReturn(resolvedSlashProtectionDir);
83+
}
84+
85+
@Test
86+
void shouldResetSpecificDirectoriesAndCreateDatabase() throws IOException {
87+
final Path kvStoreDir = beaconDataDir.resolve("kvstore");
88+
Files.createDirectory(kvStoreDir);
89+
final Path dbVersion = beaconDataDir.resolve(DB_VERSION_FILENAME);
90+
Files.createFile(dbVersion);
91+
92+
when(dbFactory.createDatabase()).thenReturn(database);
93+
94+
final Database result =
95+
beaconDatabaseReset.resetStorageForEphemeryAndCreate(serviceConfig, dbFactory);
96+
verify(beaconDatabaseReset).deleteDirectoryRecursively(dbDataDir);
97+
verify(beaconDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir);
98+
99+
verify(dbFactory).createDatabase();
100+
verifyNoMoreInteractions(dbFactory);
101+
102+
assertTrue(Files.exists(kvStoreDir));
103+
assertTrue(Files.exists(dbVersion));
104+
assertFalse(Files.exists(networkFilePath));
105+
assertEquals(database, result);
106+
}
107+
108+
@Test
109+
void shouldThrowInvalidConfigurationExceptionWhenDirectoryDeletionFails() throws IOException {
110+
doThrow(new IOException("Failed to delete directory"))
111+
.when(beaconDatabaseReset)
112+
.deleteDirectoryRecursively(dbDataDir);
113+
final InvalidConfigurationException exception =
114+
assertThrows(
115+
InvalidConfigurationException.class,
116+
() -> {
117+
beaconDatabaseReset.resetStorageForEphemeryAndCreate(serviceConfig, dbFactory);
118+
});
119+
assertEquals(
120+
"The existing ephemery database was old, and was unable to reset it.",
121+
exception.getMessage());
122+
verify(dbFactory, never()).createDatabase();
123+
verify(beaconDatabaseReset, never()).deleteDirectoryRecursively(resolvedSlashProtectionDir);
124+
}
125+
126+
@Test
127+
void shouldThrowInvalidConfigurationExceptionWhenDatabaseCreationFails() throws IOException {
128+
doNothing().when(beaconDatabaseReset).deleteDirectoryRecursively(dbDataDir);
129+
doNothing().when(beaconDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir);
130+
when(dbFactory.createDatabase()).thenThrow(new RuntimeException("Database creation failed"));
131+
final InvalidConfigurationException exception =
132+
assertThrows(
133+
InvalidConfigurationException.class,
134+
() -> {
135+
beaconDatabaseReset.resetStorageForEphemeryAndCreate(serviceConfig, dbFactory);
136+
});
137+
assertEquals(
138+
"The existing ephemery database was old, and was unable to reset it.",
139+
exception.getMessage());
140+
verify(beaconDatabaseReset).deleteDirectoryRecursively(dbDataDir);
141+
verify(beaconDatabaseReset).deleteDirectoryRecursively(resolvedSlashProtectionDir);
142+
verify(dbFactory).createDatabase();
143+
}
144+
145+
@Test
146+
void clearBeaconDatabase_shouldDeleteBeaconFilesOnly() throws IOException {
147+
// Create beacon chain files
148+
final Path archiveDir = beaconDataDir.resolve(ARCHIVE_PATH);
149+
Files.createDirectory(archiveDir);
150+
final Path metadataFile = beaconDataDir.resolve(METADATA_FILENAME);
151+
Files.createFile(metadataFile);
152+
final Path dbVersionFile = beaconDataDir.resolve(DB_VERSION_FILENAME);
153+
Files.createFile(dbVersionFile);
154+
final Path storageModeFile = beaconDataDir.resolve(STORAGE_MODE_FILENAME);
155+
Files.createFile(storageModeFile);
156+
157+
// Create slashing protection directory (should not be deleted)
158+
Files.createDirectory(resolvedSlashProtectionDir);
159+
final Path slashingProtectionFile =
160+
resolvedSlashProtectionDir.resolve(SLASHING_PROTECTION_PATH);
161+
Files.createFile(slashingProtectionFile);
162+
163+
beaconDatabaseReset.clearBeaconDatabase(serviceConfig);
164+
165+
// Verify beacon directories are deleted
166+
verify(beaconDatabaseReset).deleteDirectoryRecursively(dbDataDir);
167+
verify(beaconDatabaseReset).deleteDirectoryRecursively(archiveDir);
168+
169+
// Verify beacon files are deleted
170+
assertFalse(Files.exists(networkFilePath));
171+
assertFalse(Files.exists(metadataFile));
172+
assertFalse(Files.exists(dbVersionFile));
173+
assertFalse(Files.exists(storageModeFile));
174+
175+
// Verify slashing protection NOT deleted
176+
verify(beaconDatabaseReset, never()).deleteDirectoryRecursively(resolvedSlashProtectionDir);
177+
assertTrue(Files.exists(resolvedSlashProtectionDir));
178+
assertTrue(Files.exists(slashingProtectionFile));
179+
}
180+
181+
@Test
182+
void clearBeaconDatabase_shouldThrowExceptionOnError() throws IOException {
183+
doThrow(new IOException("Failed to delete directory"))
184+
.when(beaconDatabaseReset)
185+
.deleteDirectoryRecursively(dbDataDir);
186+
187+
final InvalidConfigurationException exception =
188+
assertThrows(
189+
InvalidConfigurationException.class,
190+
() -> beaconDatabaseReset.clearBeaconDatabase(serviceConfig));
191+
192+
assertEquals(
193+
"Failed to clear beacon database with --force-clear-db flag.", exception.getMessage());
194+
}
195+
}

0 commit comments

Comments
 (0)