Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new BlockReader implementation for block-as-file #385

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,17 @@ static BlockWriter<List<BlockItemUnparsed>> providesBlockWriter(
*
* @param config the persistence storage configuration needed to build the
* block reader
* @param blockPathResolver the block path resolver needed to build
* the block reader
* @return a block reader singleton
*/
@Provides
@Singleton
static BlockReader<BlockUnparsed> providesBlockReader(@NonNull final PersistenceStorageConfig config) {
static BlockReader<BlockUnparsed> providesBlockReader(
@NonNull final PersistenceStorageConfig config, @NonNull final BlockPathResolver blockPathResolver) {
final StorageType persistenceType = config.type();
return switch (persistenceType) {
case BLOCK_AS_LOCAL_FILE -> BlockAsLocalFileReader.newInstance();
case BLOCK_AS_LOCAL_FILE -> BlockAsLocalFileReader.of(blockPathResolver);
case BLOCK_AS_LOCAL_DIRECTORY -> BlockAsLocalDirReader.of(config);
case NO_OP -> NoOpBlockReader.newInstance();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,68 @@
package com.hedera.block.server.persistence.storage.read;

import com.hedera.block.common.utils.Preconditions;
import com.hedera.block.server.persistence.storage.path.BlockPathResolver;
import com.hedera.hapi.block.BlockUnparsed;
import com.hedera.pbj.runtime.ParseException;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;

/**
* A Block reader that reads block-as-file.
*/
public final class BlockAsLocalFileReader implements LocalBlockReader<BlockUnparsed> {
private final BlockPathResolver pathResolver;

/**
* Constructor.
*
* @param pathResolver valid, {@code non-null} instance of
* {@link BlockPathResolver} used to resolve paths to block files
*/
private BlockAsLocalFileReader() {}
private BlockAsLocalFileReader(@NonNull final BlockPathResolver pathResolver) {
this.pathResolver = Objects.requireNonNull(pathResolver);
}

/**
* This method creates and returns a new instance of
* {@link BlockAsLocalFileReader}.
*
* @param pathResolver valid, {@code non-null} instance of
* {@link BlockPathResolver} used to resolve paths to block files
* @return a new, fully initialized instance of
* {@link BlockAsLocalFileReader}
*/
public static BlockAsLocalFileReader newInstance() {
return new BlockAsLocalFileReader();
public static BlockAsLocalFileReader of(@NonNull final BlockPathResolver pathResolver) {
return new BlockAsLocalFileReader(pathResolver);
}

@NonNull
@Override
public Optional<BlockUnparsed> read(final long blockNumber) {
public Optional<BlockUnparsed> read(final long blockNumber) throws IOException, ParseException {
Preconditions.requireWhole(blockNumber);
throw new UnsupportedOperationException("Not implemented yet");
final Path resolvedBlockPath = pathResolver.resolvePathToBlock(blockNumber);
if (Files.exists(resolvedBlockPath)) {
return Optional.of(doRead(resolvedBlockPath));
} else {
// todo we generally expect the block to be there, so we
// return an empty optional here, or do something else?
// of course sometimes the block may not even exist yet.
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
return Optional.empty();

Check warning on line 72 in server/src/main/java/com/hedera/block/server/persistence/storage/read/BlockAsLocalFileReader.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/com/hedera/block/server/persistence/storage/read/BlockAsLocalFileReader.java#L72

Added line #L72 was not covered by tests
}
}

private BlockUnparsed doRead(final Path resolvedBlockPath) throws IOException, ParseException {
// todo there are other ways of parsing, for example we can pass ReadableSequentialData instead of the fis
// because the readAllBytes does not seem like the best idea, what is the correct (or better) way to parse
// the block file?
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
try (final FileInputStream fis = new FileInputStream(resolvedBlockPath.toFile())) {
return BlockUnparsed.PROTOBUF.parse(Bytes.wrap(fis.readAllBytes()));
AlfredoG87 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ void testProvidesBlockReader(final StorageType storageType) {
when(persistenceStorageConfigMock.type()).thenReturn(storageType);

final BlockReader<BlockUnparsed> actual =
PersistenceInjectionModule.providesBlockReader(persistenceStorageConfigMock);
PersistenceInjectionModule.providesBlockReader(persistenceStorageConfigMock, blockPathResolverMock);

final Class<?> targetInstanceType =
switch (storageType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,33 @@

package com.hedera.block.server.persistence.storage.read;

import static com.hedera.block.server.util.PersistTestUtils.PERSISTENCE_STORAGE_LIVE_ROOT_PATH_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.from;
import static org.mockito.Mockito.spy;

import com.hedera.block.server.config.BlockNodeContext;
import com.hedera.block.server.persistence.storage.PersistenceStorageConfig;
import com.hedera.block.server.persistence.storage.path.BlockAsLocalFilePathResolver;
import com.hedera.block.server.persistence.storage.write.BlockAsLocalFileWriter;
import com.hedera.block.server.util.PersistTestUtils;
import com.hedera.block.server.util.TestConfigUtil;
import com.hedera.hapi.block.BlockItemUnparsed;
import com.hedera.hapi.block.BlockUnparsed;
import com.hedera.hapi.block.stream.output.BlockHeader;
import com.hedera.pbj.runtime.ParseException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -29,25 +51,64 @@
* Tests for the {@link BlockAsLocalFileReader} class.
*/
class BlockAsLocalFileReaderTest {
private BlockAsLocalFileWriter blockAsLocalFileWriterMock;
private BlockAsLocalFilePathResolver blockAsLocalFileResolverMock;
private BlockNodeContext blockNodeContext;
private PersistenceStorageConfig testConfig;
private BlockAsLocalFileReader toTest;

@TempDir
private Path testLiveRootPath;

@BeforeEach
void setUp() {
toTest = BlockAsLocalFileReader.newInstance();
void setUp() throws IOException {
blockNodeContext = TestConfigUtil.getTestBlockNodeContext(
Map.of(PERSISTENCE_STORAGE_LIVE_ROOT_PATH_KEY, testLiveRootPath.toString()));
testConfig = blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class);

final String testConfigLiveRootPath = testConfig.liveRootPath();
assertThat(testConfigLiveRootPath).isEqualTo(testLiveRootPath.toString());

blockAsLocalFileResolverMock = spy(BlockAsLocalFilePathResolver.of(testLiveRootPath));
blockAsLocalFileWriterMock = spy(BlockAsLocalFileWriter.of(blockNodeContext, blockAsLocalFileResolverMock));
toTest = BlockAsLocalFileReader.of(blockAsLocalFileResolverMock);
}

/**
* This test aims to verify that the
* {@link BlockAsLocalFileReader#read(long)} correctly reads a block with
* a given block number.
*
* @param toRead parameterized, block number
*/
@ParameterizedTest
@MethodSource("validBlockNumbers")
void testSuccessfulBlockRead(final long toRead) {
// todo currently throws UnsupportedOperationException
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> toTest.read(toRead));
@Test
jsync-swirlds marked this conversation as resolved.
Show resolved Hide resolved
void testSuccessfulBlockRead() throws IOException, ParseException {
final List<BlockItemUnparsed> blockItemUnparsed = PersistTestUtils.generateBlockItemsUnparsed(1);
final Optional<List<BlockItemUnparsed>> written = blockAsLocalFileWriterMock.write(blockItemUnparsed);

assertThat(written).isNotNull().isPresent();

final Optional<BlockUnparsed> actual = toTest.read(1);
assertThat(actual)
.isNotNull()
.isPresent()
.get(InstanceOfAssertFactories.type(BlockUnparsed.class))
.isNotNull()
.isExactlyInstanceOf(BlockUnparsed.class)
.returns(1L, from(blockUnparsed -> {
try {
return BlockHeader.PROTOBUF
.parse(Objects.requireNonNull(
blockUnparsed.blockItems().getFirst().blockHeader()))
.number();
} catch (final ParseException e) {
throw new RuntimeException(e);
}
jsync-swirlds marked this conversation as resolved.
Show resolved Hide resolved
}))
.extracting(BlockUnparsed::blockItems)
.asList()
.isNotNull()
.isNotEmpty()
.hasSize(blockItemUnparsed.size())
.containsExactlyElementsOf(blockItemUnparsed);
jsync-swirlds marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -64,37 +125,6 @@ void testInvalidBlockNumber(final long toRead) {
assertThatIllegalArgumentException().isThrownBy(() -> toTest.read(toRead));
}

/**
* Some valid block numbers.
*
* @return a stream of valid block numbers
*/
public static Stream<Arguments> validBlockNumbers() {
return Stream.of(
Arguments.of(0L),
Arguments.of(1L),
Arguments.of(2L),
Arguments.of(10L),
Arguments.of(100L),
Arguments.of(1_000L),
Arguments.of(10_000L),
Arguments.of(100_000L),
Arguments.of(1_000_000L),
Arguments.of(10_000_000L),
Arguments.of(100_000_000L),
Arguments.of(1_000_000_000L),
Arguments.of(10_000_000_000L),
Arguments.of(100_000_000_000L),
Arguments.of(1_000_000_000_000L),
Arguments.of(10_000_000_000_000L),
Arguments.of(100_000_000_000_000L),
Arguments.of(1_000_000_000_000_000L),
Arguments.of(10_000_000_000_000_000L),
Arguments.of(100_000_000_000_000_000L),
Arguments.of(1_000_000_000_000_000_000L),
Arguments.of(Long.MAX_VALUE));
}

/**
* Some invalid block numbers.
*
Expand Down
Loading