diff --git a/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java b/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java index f2b15447..93524618 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java +++ b/server/src/main/java/com/hedera/block/server/persistence/PersistenceInjectionModule.java @@ -45,6 +45,7 @@ import dagger.Provides; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -74,8 +75,6 @@ static BlockWriter> providesBlockWriter( .type(); try { return switch (persistenceType) { - case null -> throw new NullPointerException( - "Persistence StorageType cannot be [null], cannot create an instance of BlockWriter"); case BLOCK_AS_FILE -> BlockAsFileWriterBuilder.newBuilder(blockNodeContext, blockRemover, pathResolver) .build(); case BLOCK_AS_DIR -> BlockAsDirWriterBuilder.newBuilder(blockNodeContext, blockRemover, pathResolver) @@ -83,7 +82,8 @@ static BlockWriter> providesBlockWriter( case NOOP -> new NoOpBlockWriter(blockNodeContext, blockRemover, pathResolver); }; } catch (final IOException e) { - throw new RuntimeException("Failed to create BlockWriter", e); + // we cannot have checked exceptions with dagger @Provides + throw new UncheckedIOException("Failed to create BlockWriter", e); } } @@ -99,8 +99,6 @@ static BlockWriter> providesBlockWriter( static BlockReader providesBlockReader(@NonNull final PersistenceStorageConfig config) { final StorageType persistenceType = Objects.requireNonNull(config).type(); return switch (persistenceType) { - case null -> throw new NullPointerException( - "Persistence StorageType cannot be [null], cannot create an instance of BlockReader"); case BLOCK_AS_FILE -> BlockAsFileReaderBuilder.newBuilder().build(); case BLOCK_AS_DIR -> BlockAsDirReaderBuilder.newBuilder(config).build(); case NOOP -> new NoOpBlockReader(); @@ -119,8 +117,6 @@ static BlockReader providesBlockReader(@NonNull final PersistenceStorageC static BlockRemover providesBlockRemover(@NonNull final PersistenceStorageConfig config) { final StorageType persistenceType = Objects.requireNonNull(config).type(); return switch (persistenceType) { - case null -> throw new NullPointerException( - "Persistence StorageType cannot be [null], cannot create an instance of BlockRemover"); case BLOCK_AS_FILE -> new BlockAsFileRemover(); case BLOCK_AS_DIR -> new BlockAsDirRemover(Path.of(config.rootPath())); case NOOP -> new NoOpRemover(); @@ -140,8 +136,6 @@ static PathResolver providesPathResolver(@NonNull final PersistenceStorageConfig final StorageType persistenceType = Objects.requireNonNull(config).type(); final Path root = Path.of(config.rootPath()); return switch (persistenceType) { - case null -> throw new NullPointerException( - "Persistence StorageType cannot be [null], cannot create an instance of PathResolver"); case BLOCK_AS_FILE -> new BlockAsFilePathResolver(root); case BLOCK_AS_DIR -> new BlockAsDirPathResolver(root); case NOOP -> new NoOpPathResolver(); diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java b/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java index 707b2f72..d1dee09e 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/PersistenceStorageConfig.java @@ -24,8 +24,10 @@ import com.swirlds.config.api.ConfigData; import com.swirlds.config.api.ConfigProperty; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Objects; /** * Use this configuration across the persistent storage package @@ -46,15 +48,20 @@ public record PersistenceStorageConfig( * directory in the current working directory */ public PersistenceStorageConfig { + Objects.requireNonNull(type); + Objects.requireNonNull(rootPath); + // verify rootPath prop Path path = Path.of(rootPath); - if (rootPath.isEmpty()) { - path = Paths.get(rootPath).toAbsolutePath().resolve("data"); + if (rootPath.isBlank()) { + path = Paths.get("").toAbsolutePath().resolve("data"); } + // Check if absolute if (!path.isAbsolute()) { throw new IllegalArgumentException(rootPath + " Root path must be absolute"); } + // Create Directory if it does not exist try { FileUtilities.createFolderPathIfNotExists(path, ERROR, BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME); @@ -62,11 +69,11 @@ public record PersistenceStorageConfig( final String message = "Unable to instantiate [%s]! Unable to create the root directory for the block storage [%s]" .formatted(this.getClass().getName(), path); - throw new RuntimeException(message, e); + throw new UncheckedIOException(message, e); } - LOGGER.log(INFO, "Persistence Storage configuration persistence.storage.rootPath: " + path); rootPath = path.toString(); - LOGGER.log(INFO, "Persistence configuration persistence.storage.type: " + type); + LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.rootPath=" + path); + LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.type= " + type); } } diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/path/BlockAsDirPathResolver.java b/server/src/main/java/com/hedera/block/server/persistence/storage/path/BlockAsDirPathResolver.java index b489d6c0..f957029f 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/path/BlockAsDirPathResolver.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/path/BlockAsDirPathResolver.java @@ -29,6 +29,6 @@ public BlockAsDirPathResolver(@NonNull final Path root) { @Override public Path resolvePathToBlock(final long blockNumber) { - throw new UnsupportedOperationException("Not implemented yet"); + return root.resolve(String.valueOf(blockNumber)); } } diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/AbstractBlockWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/AbstractBlockWriter.java deleted file mode 100644 index fc5f0a26..00000000 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/write/AbstractBlockWriter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 Hedera Hashgraph, LLC - * - * 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 com.hedera.block.server.persistence.storage.write; - -import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted; - -import com.hedera.block.server.metrics.MetricsService; -import com.hedera.block.server.persistence.storage.path.PathResolver; -import com.hedera.block.server.persistence.storage.remove.BlockRemover; -import com.swirlds.metrics.api.Counter; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; - -/** - * TODO: add documentation - */ -abstract class AbstractBlockWriter implements BlockWriter { - private final Counter blocksPersistedCounter; - protected final BlockRemover blockRemover; - protected final PathResolver pathResolver; - - protected AbstractBlockWriter( - @NonNull final MetricsService metricsService, - @NonNull final BlockRemover blockRemover, - @NonNull final PathResolver pathResolver) { - Objects.requireNonNull(metricsService); - this.blocksPersistedCounter = Objects.requireNonNull(metricsService.get(BlocksPersisted)); - this.blockRemover = Objects.requireNonNull(blockRemover); - this.pathResolver = Objects.requireNonNull(pathResolver); - } - - protected final void incrementBlocksPersisted() { - blocksPersistedCounter.increment(); - } -} diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsDirWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsDirWriter.java index 6157478c..03ee26d3 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsDirWriter.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsDirWriter.java @@ -18,6 +18,7 @@ import static com.hedera.block.server.Constants.BLOCK_FILE_EXTENSION; import static com.hedera.block.server.Constants.BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted; import static java.lang.System.Logger; import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.ERROR; @@ -25,6 +26,7 @@ import com.hedera.block.common.utils.FileUtilities; import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.metrics.MetricsService; import com.hedera.block.server.persistence.storage.PersistenceStorageConfig; import com.hedera.block.server.persistence.storage.path.PathResolver; import com.hedera.block.server.persistence.storage.remove.BlockRemover; @@ -51,12 +53,15 @@ * to remove the current, incomplete block (directory) before re-throwing the exception to the * caller. */ -class BlockAsDirWriter extends AbstractBlockWriter> { +class BlockAsDirWriter implements LocalBlockWriter> { private final Logger LOGGER = System.getLogger(getClass().getName()); private final Path blockNodeRootPath; + private final MetricsService metricsService; + private final BlockRemover blockRemover; + private final PathResolver pathResolver; private final FileAttribute> folderPermissions; private long blockNodeFileNameIndex; - private Path currentBlockDir; + private long currentBlockNumber; /** * Use the corresponding builder to construct a new BlockAsDirWriter with @@ -76,16 +81,16 @@ class BlockAsDirWriter extends AbstractBlockWriter> { @NonNull final PathResolver pathResolver, final FileAttribute> folderPermissions) throws IOException { - super(blockNodeContext.metricsService(), blockRemover, pathResolver); - LOGGER.log(INFO, "Initializing FileSystemBlockStorage"); + this.metricsService = Objects.requireNonNull(blockNodeContext.metricsService()); + this.blockRemover = Objects.requireNonNull(blockRemover); + this.pathResolver = Objects.requireNonNull(pathResolver); + final PersistenceStorageConfig config = blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class); - - final Path blockNodeRootPath = Path.of(config.rootPath()); + this.blockNodeRootPath = Path.of(config.rootPath()); LOGGER.log(INFO, "Block Node Root Path: " + blockNodeRootPath); - this.blockNodeRootPath = blockNodeRootPath; if (Objects.nonNull(folderPermissions)) { this.folderPermissions = folderPermissions; @@ -131,13 +136,13 @@ public Optional> write(@NonNull final List toWrite) t // Remove the block if repairing the permissions fails if (retries > 0) { // Attempt to remove the block - blockRemover.remove(Long.parseLong(currentBlockDir.toString())); + blockRemover.remove(currentBlockNumber); throw e; } else { // Attempt to repair the permissions on the block path // and the blockItem path repairPermissions(blockNodeRootPath); - repairPermissions(calculateBlockPath()); + repairPermissions(pathResolver.resolvePathToBlock(currentBlockNumber)); LOGGER.log(INFO, "Retrying to write the BlockItem protobuf to a file"); } } @@ -145,7 +150,7 @@ public Optional> write(@NonNull final List toWrite) t } if (toWrite.getLast().hasBlockProof()) { - incrementBlocksPersisted(); + metricsService.get(BlocksPersisted).increment(); return Optional.of(toWrite); } else { return Optional.empty(); @@ -173,7 +178,7 @@ protected void write(@NonNull final Path blockItemFilePath, @NonNull final Block private void resetState(@NonNull final BlockItem blockItem) throws IOException { // Here a "block" is represented as a directory of BlockItems. // Create the "block" directory based on the block_number - currentBlockDir = Path.of(String.valueOf(blockItem.blockHeader().number())); + currentBlockNumber = blockItem.blockHeader().number(); // Check the blockNodeRootPath permissions and // attempt to repair them if possible @@ -181,12 +186,16 @@ private void resetState(@NonNull final BlockItem blockItem) throws IOException { // Construct the path to the block directory FileUtilities.createFolderPathIfNotExists( - calculateBlockPath(), DEBUG, folderPermissions, BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME); + pathResolver.resolvePathToBlock(currentBlockNumber), + DEBUG, + folderPermissions, + BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME); // Reset blockNodeFileNameIndex = 0; } + // todo do we need this method at all? private void repairPermissions(@NonNull final Path path) throws IOException { final boolean isWritable = Files.isWritable(path); if (!isWritable) { @@ -200,13 +209,8 @@ private void repairPermissions(@NonNull final Path path) throws IOException { @NonNull private Path calculateBlockItemPath() { // Build the path to a .blk file - final Path blockPath = calculateBlockPath(); + final Path blockPath = pathResolver.resolvePathToBlock(currentBlockNumber); blockNodeFileNameIndex++; return blockPath.resolve(blockNodeFileNameIndex + BLOCK_FILE_EXTENSION); } - - @NonNull - private Path calculateBlockPath() { - return blockNodeRootPath.resolve(currentBlockDir); - } } diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsFileWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsFileWriter.java index 58f196b7..faed4ba3 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsFileWriter.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/BlockAsFileWriter.java @@ -16,7 +16,10 @@ package com.hedera.block.server.persistence.storage.write; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted; + import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.metrics.MetricsService; import com.hedera.block.server.persistence.storage.path.PathResolver; import com.hedera.block.server.persistence.storage.remove.BlockRemover; import com.hedera.hapi.block.stream.Block; @@ -28,19 +31,25 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.Optional; /** * TODO: add documentation */ -class BlockAsFileWriter extends AbstractBlockWriter> { +class BlockAsFileWriter implements LocalBlockWriter> { + private final MetricsService metricsService; + private final BlockRemover blockRemover; // todo do I need here? + private final PathResolver pathResolver; private Block curentBlock; // fixme this is temporary just to explore the workflow and make proof of concept BlockAsFileWriter( @NonNull final BlockNodeContext blockNodeContext, @NonNull final BlockRemover blockRemover, @NonNull final PathResolver pathResolver) { - super(blockNodeContext.metricsService(), blockRemover, pathResolver); + this.metricsService = Objects.requireNonNull(blockNodeContext.metricsService()); + this.blockRemover = Objects.requireNonNull(blockRemover); + this.pathResolver = Objects.requireNonNull(pathResolver); } @Override @@ -54,12 +63,15 @@ public Optional> write(@NonNull final List toWrite) t } if (toWrite.getLast().hasBlockProof()) { + metricsService.get(BlocksPersisted).increment(); return writeToFs(curentBlock); } else { return Optional.empty(); } } + // todo we could recursively retry if exception occurs, then after a few attempts + // if we cannot persist, we must throw the initial exception private Optional> writeToFs(final Block blockToWrite) throws IOException { final long number = blockToWrite.items().getFirst().blockHeader().number(); // fixme could be null, handle! @@ -72,7 +84,7 @@ private Optional> writeToFs(final Block blockToWrite) throws IOE try (final FileOutputStream fos = new FileOutputStream(blockToWritePathResolved.toFile())) { Block.PROTOBUF.toBytes(blockToWrite).writeTo(fos); // todo what should be fallback logic if something goes wrong here? we attempt to resolve the path - // with proper perms? we + // with proper perms (is that necessary)? we must clean up and retry? } catch (final IOException ioe) { // todo handle properly throw new UncheckedIOException(ioe); diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java new file mode 100644 index 00000000..b520cf9e --- /dev/null +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/LocalBlockWriter.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.block.server.persistence.storage.write; + +/** + * TODO: add documentation + */ +interface LocalBlockWriter extends BlockWriter {} diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java index c33ec355..95d639da 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/write/NoOpBlockWriter.java @@ -16,15 +16,17 @@ package com.hedera.block.server.persistence.storage.write; -import static java.lang.System.Logger.Level.INFO; +import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted; import com.hedera.block.server.config.BlockNodeContext; +import com.hedera.block.server.metrics.MetricsService; import com.hedera.block.server.persistence.storage.path.PathResolver; import com.hedera.block.server.persistence.storage.remove.BlockRemover; import com.hedera.hapi.block.stream.BlockItem; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.util.List; +import java.util.Objects; import java.util.Optional; /** @@ -32,7 +34,11 @@ * designed to isolate the Producer and Mediator components from storage implementation during testing while still * providing metrics and logging for troubleshooting. */ -public class NoOpBlockWriter extends AbstractBlockWriter> { +public class NoOpBlockWriter implements LocalBlockWriter> { + private final MetricsService metricsService; + private final BlockRemover blockRemover; // todo do I need here? + private final PathResolver pathResolver; // todo do I need here? + /** * Creates a new NoOpBlockWriter instance for testing and troubleshooting only. * @@ -43,8 +49,9 @@ public NoOpBlockWriter( @NonNull final BlockNodeContext blockNodeContext, @NonNull final BlockRemover blockRemover, @NonNull final PathResolver pathResolver) { - super(blockNodeContext.metricsService(), blockRemover, pathResolver); - System.getLogger(getClass().getName()).log(INFO, "Using " + getClass().getSimpleName()); + this.metricsService = Objects.requireNonNull(blockNodeContext.metricsService()); + this.blockRemover = Objects.requireNonNull(blockRemover); + this.pathResolver = Objects.requireNonNull(pathResolver); } /** @@ -53,7 +60,7 @@ public NoOpBlockWriter( @Override public Optional> write(@NonNull final List toWrite) throws IOException { if (toWrite.getLast().hasBlockProof()) { - incrementBlocksPersisted(); + metricsService.get(BlocksPersisted).increment(); } return Optional.empty(); }