diff --git a/app/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java b/app/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java index 635c4c52de8..3067c6736eb 100644 --- a/app/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java +++ b/app/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java @@ -33,11 +33,7 @@ import org.hyperledger.besu.ethereum.worldstate.PathBasedExtraStorageConfiguration; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; @@ -153,12 +149,7 @@ private void saveTrieLogBatches( final PathBasedWorldStateKeyValueStorage rootWorldStateStorage, final List trieLogKeys) { - try { - saveTrieLogsInFile(trieLogKeys, rootWorldStateStorage, batchFileName); - } catch (IOException e) { - LOG.error("Error saving trie logs to file: {}", e.getMessage()); - throw new RuntimeException(e); - } + saveTrieLogsAsRlpInFile(trieLogKeys, rootWorldStateStorage, batchFileName); } private void restoreTrieLogBatches( @@ -166,13 +157,8 @@ private void restoreTrieLogBatches( final long batchNumber, final String batchFileNameBase) { - try { - LOG.info("Restoring trie logs retained from batch {}...", batchNumber); - recreateTrieLogs(rootWorldStateStorage, batchNumber, batchFileNameBase); - } catch (IOException e) { - LOG.error("Error recreating trie logs from batch {}: {}", batchNumber, e.getMessage()); - throw new RuntimeException(e); - } + LOG.info("Restoring trie logs retained from batch {}...", batchNumber); + recreateTrieLogs(rootWorldStateStorage, batchNumber, batchFileNameBase); } private boolean deleteFiles(final String batchFileNameBase, final long numberOfBatches) { @@ -257,11 +243,10 @@ private boolean validatePruneRequirements( private void recreateTrieLogs( final PathBasedWorldStateKeyValueStorage rootWorldStateStorage, final long batchNumber, - final String batchFileNameBase) - throws IOException { + final String batchFileNameBase) { // process in chunk to avoid OOM final String batchFileName = batchFileNameBase + "-" + batchNumber; - IdentityHashMap trieLogsToRetain = readTrieLogsFromFile(batchFileName); + IdentityHashMap trieLogsToRetain = readTrieLogsAsRlpFromFile(batchFileName); final int chunkSize = ROCKSDB_MAX_INSERTS_PER_TRANSACTION; List keys = new ArrayList<>(trieLogsToRetain.keySet()); @@ -314,43 +299,6 @@ void validatePruneConfiguration(final DataStorageConfiguration config) { subStorageConfiguration.getMaxLayersToLoad())); } - private void saveTrieLogsInFile( - final List trieLogsKeys, - final PathBasedWorldStateKeyValueStorage rootWorldStateStorage, - final String batchFileName) - throws IOException { - - File file = new File(batchFileName); - if (file.exists()) { - LOG.warn("File already exists {}, skipping file creation", batchFileName); - return; - } - - try (FileOutputStream fos = new FileOutputStream(file)) { - ObjectOutputStream oos = new ObjectOutputStream(fos); - oos.writeObject(getTrieLogs(trieLogsKeys, rootWorldStateStorage)); - } catch (IOException e) { - LOG.error(e.getMessage()); - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - IdentityHashMap readTrieLogsFromFile(final String batchFileName) { - - IdentityHashMap trieLogs; - try (FileInputStream fis = new FileInputStream(batchFileName); - ObjectInputStream ois = new ObjectInputStream(fis)) { - - trieLogs = (IdentityHashMap) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - LOG.error(e.getMessage()); - throw new RuntimeException(e); - } - - return trieLogs; - } - private void saveTrieLogsAsRlpInFile( final List trieLogsKeys, final PathBasedWorldStateKeyValueStorage rootWorldStateStorage, diff --git a/app/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java b/app/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java index b1aa8b77050..726c23baf2d 100644 --- a/app/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java +++ b/app/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java @@ -38,7 +38,6 @@ import org.hyperledger.besu.ethereum.worldstate.ImmutablePathBasedExtraStorageConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -128,6 +127,44 @@ void mockBlockchainBase() { when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader3)); } + @Test + public void pruneFailsWhenBatchFileContainsJavaSerialization(final @TempDir Path dataDir) + throws IOException { + Files.createDirectories(dataDir.resolve("database")); + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .pathBasedExtraStorageConfiguration( + ImmutablePathBasedExtraStorageConfiguration.builder() + .maxLayersToLoad(3L) + .limitTrieLogsEnabled(true) + .build()) + .build(); + + mockBlockchainBase(); + when(blockchain.getBlockHeader(5)).thenReturn(Optional.of(blockHeader5)); + when(blockchain.getBlockHeader(4)).thenReturn(Optional.of(blockHeader4)); + when(blockchain.getBlockHeader(3)).thenReturn(Optional.of(blockHeader3)); + + // Pre-place a Java-serialized file at the expected batch file path. + // saveTrieLogsAsRlpInFile's exists-check will skip overwriting it. + // readTrieLogsAsRlpFromFile will then fail to parse it as RLP during restore, + // confirming the code no longer silently deserializes Java-serialized objects. + Path batchFile = dataDir.resolve("database").resolve("trieLogsToRetain-1"); + try (var oos = + new java.io.ObjectOutputStream(new java.io.FileOutputStream(batchFile.toFile()))) { + oos.writeObject("notrlp"); + } + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("RLP"); + } + @Test public void prune(final @TempDir Path dataDir) throws IOException { Files.createDirectories(dataDir.resolve("database")); @@ -388,7 +425,7 @@ public void exceptionWhileSavingFileStopsPruneProcess(final @TempDir Path dataDi blockchain, dataDir.resolve("unknownPath"))) .isInstanceOf(RuntimeException.class) - .hasCauseExactlyInstanceOf(FileNotFoundException.class); + .hasCauseInstanceOf(java.io.IOException.class); // assert all trie logs are still in the DB assertThat(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get())