Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -230,15 +230,6 @@ message PublishStreamResponse {
* one greater than this value.
*/
uint64 block_number = 1;

/**
* A hash of the virtual merkle root for the block.
* <p>
* This SHALL be the hash calculated by the Block-Node for the
* root node of the virtual merkle tree that is signed by the source
* system to validate the block.
*/
bytes block_root_hash = 2;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ public void onNext(final PublishStreamResponse publishStreamResponse) {
if (publishStreamResponse.hasAcknowledgement()) {
final BlockAcknowledgement ack = publishStreamResponse.getAcknowledgement();
try {
startupData.updateLatestAckBlockStartupData(
ack.getBlockNumber(), ack.getBlockRootHash().toByteArray());
startupData.updateLatestAckBlockStartupData(ack.getBlockNumber());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that the startup data will stop functioning w/o the hash to be saved on disk. This means that the next startup will be w/o previous hash and everything will be broken after that. We should add a way to save the current hash (as it is generated by the simulator) and then retrieving and storing that in the proper place once ack is received.

} catch (final IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ public interface SimulatorStartupData {
* response. At the next startup of the application, values based on the
* last update will be used for initialization.
* @param blockNumber the block number to update the startup data with
* @param blockHash the block hash to update the startup data with
* @throws IOException if an error occurs while updating the startup data
*/
void updateLatestAckBlockStartupData(final long blockNumber, final byte[] blockHash) throws IOException;
void updateLatestAckBlockStartupData(final long blockNumber) throws IOException;

/**
* This method returns the latest acknowledged block number based on startup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.nio.file.Path;
import javax.inject.Inject;
import org.hiero.block.common.hasher.StreamingTreeHasher;
import org.hiero.block.common.utils.FileUtilities;
import org.hiero.block.common.utils.StringUtilities;
import org.hiero.block.simulator.config.data.BlockGeneratorConfig;
import org.hiero.block.simulator.config.data.SimulatorStartupDataConfig;
Expand All @@ -20,7 +19,6 @@ public final class SimulatorStartupDataImpl implements SimulatorStartupData {
private final System.Logger LOGGER = System.getLogger(SimulatorStartupDataImpl.class.getName());
private final boolean enabled;
private final Path latestAckBlockNumberPath;
private final Path latestAckBlockHashPath;
private final long startupDataBlockNumber;
private final byte[] startupDataBlockHash;

Expand All @@ -30,54 +28,26 @@ public SimulatorStartupDataImpl(
@NonNull final BlockGeneratorConfig blockGeneratorConfig) {
this.enabled = simulatorStartupDataConfig.enabled();
this.latestAckBlockNumberPath = simulatorStartupDataConfig.latestAckBlockNumberPath();
this.latestAckBlockHashPath = simulatorStartupDataConfig.latestAckBlockHashPath();
long localStartupDataBlockNumber = blockGeneratorConfig.startBlockNumber() - 1L;
byte[] localStartupDataBlockHash = new byte[StreamingTreeHasher.HASH_LENGTH];
if (enabled) {
try {
final int existsLatestAckBlockNumberFile = Files.exists(latestAckBlockNumberPath) ? 1 : 0;
final int existsLatestAckBlockHashFile = Files.exists(latestAckBlockHashPath) ? 1 : 0;
// determine the number of existing startup data files
final int existingStartupDataFileCount = existsLatestAckBlockNumberFile + existsLatestAckBlockHashFile;
switch (existingStartupDataFileCount) {
case 0 -> {
// if no startup data files exist, this means that this
// is the initial setup, we only need to create the
// startup data files
FileUtilities.createFile(latestAckBlockNumberPath);
FileUtilities.createFile(latestAckBlockHashPath);
}
case 1 -> {
// if only one file exists, then this is an erroneous
// state. We must investigate why this is happening.
// Generally we never ever expect to enter here, but
// we cannot continue to initialize the simulator
if (!Files.exists(latestAckBlockNumberPath)) {
// if no startup data files exist, this means that this
// is the initial setup, we only need to create the
// startup data files
Files.createFile(latestAckBlockNumberPath);
} else {
// data from the files.
// If successful, we can finish initialization, otherwise
// we have broken state and cannot continue.
final String blockNumberFromFile = Files.readString(latestAckBlockNumberPath);
if (!StringUtilities.isBlank(blockNumberFromFile)) {
localStartupDataBlockNumber = Long.parseLong(blockNumberFromFile);
} else {
throw new IllegalStateException(
"Failed to initialize Simulator Startup Data, only one startup data file exists!");
}
case 2 -> {
// entering here means that both files exist, so now we
// must attempt to read the startup data from the files.
// If successful, we can finish initialization, otherwise
// we have broken state and cannot continue.
final String blockNumberFromFile = Files.readString(latestAckBlockNumberPath);
if (!StringUtilities.isBlank(blockNumberFromFile)) {
localStartupDataBlockNumber = Long.parseLong(blockNumberFromFile);
} else {
throw new IllegalStateException(
"Failed to initialize latest ack block number from Simulator Startup Data");
}
final byte[] previousHashFromFile = Files.readAllBytes(latestAckBlockHashPath);
if (previousHashFromFile.length == StreamingTreeHasher.HASH_LENGTH) {
localStartupDataBlockHash = previousHashFromFile;
} else {
throw new IllegalStateException(
"Failed to initialize latest ack block hash from Simulator Startup Data");
}
"Failed to initialize latest ack block number from Simulator Startup Data");
}
default ->
throw new IllegalStateException(
"Failed to initialize Simulator Startup Data, invalid number of startup data files!");
}
} catch (final IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -104,13 +74,12 @@ public boolean isEnabled() {
}

@Override
public void updateLatestAckBlockStartupData(final long blockNumber, final byte[] blockHash) throws IOException {
public void updateLatestAckBlockStartupData(final long blockNumber) throws IOException {
if (enabled) {
// @todo(904) we need the correct response code, currently it seems that
// the response code is not being set correctly? The if check should
// be different and based on the response code, only saving
Files.write(latestAckBlockNumberPath, String.valueOf(blockNumber).getBytes());
Files.write(latestAckBlockHashPath, blockHash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think we can omit this, we simply need another way to supply the hash.

LOGGER.log(DEBUG, "Updated startup data for latest ack block with number: {0}", blockNumber);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doThrow;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hiero.block.api.protoc.PublishStreamResponse;
Expand All @@ -21,7 +26,10 @@ class PublishStreamObserverTest {

@Test
void onNext() {
PublishStreamResponse response = PublishStreamResponse.newBuilder().build();
PublishStreamResponse response = PublishStreamResponse.newBuilder()
.setAcknowledgement(
PublishStreamResponse.BlockAcknowledgement.newBuilder().setBlockNumber(12345L))
.build();
AtomicBoolean streamEnabled = new AtomicBoolean(true);
ArrayDeque<String> lastKnownStatuses = new ArrayDeque<>();
final int lastKnownStatusesCapacity = 10;
Expand Down Expand Up @@ -58,4 +66,21 @@ void onCompleted() {
assertTrue(streamEnabled.get(), "streamEnabled should remain true after onCompleted");
assertEquals(0, lastKnownStatuses.size(), "lastKnownStatuses should not have elements after onCompleted");
}

@Test
void verifyUpdateLatestAckBlockStartupDataHandlesIOException() throws Exception {
PublishStreamResponse response = PublishStreamResponse.newBuilder()
.setAcknowledgement(
PublishStreamResponse.BlockAcknowledgement.newBuilder().setBlockNumber(12345L))
.build();
AtomicBoolean streamEnabled = new AtomicBoolean(true);
ArrayDeque<String> lastKnownStatuses = new ArrayDeque<>();
final int lastKnownStatusesCapacity = 10;
PublishStreamObserver publishStreamObserver =
new PublishStreamObserver(startupDataMock, streamEnabled, lastKnownStatuses, lastKnownStatusesCapacity);

doThrow(new IOException("Test exception")).when(startupDataMock).updateLatestAckBlockStartupData(anyLong());

assertThrows(UncheckedIOException.class, () -> publishStreamObserver.onNext(response));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ class SimulatorStartupDataImplTest {
@TempDir
private Path tempDir;

private byte[] validSimulatedBlockHash;
private Path latestAckBlockNumberPath;
private Path latestAckBlockHashPath;

@BeforeEach
void setup() {
validSimulatedBlockHash = new byte[StreamingTreeHasher.HASH_LENGTH];
for (byte i = 0; i < StreamingTreeHasher.HASH_LENGTH; i++) {
validSimulatedBlockHash[i] = i;
}
latestAckBlockNumberPath = tempDir.resolve("latestAckBlockNumber");
latestAckBlockHashPath = tempDir.resolve("latestAckBlockHash");
}

/**
Expand All @@ -57,7 +50,6 @@ private SimulatorStartupDataImpl newInstanceToTest(final boolean enabled) {
.withConfigDataType(SimulatorStartupDataConfig.class)
.withValue("simulator.startup.data.enabled", enabled ? "true" : "false")
.withValue("simulator.startup.data.latestAckBlockNumberPath", latestAckBlockNumberPath.toString())
.withValue("simulator.startup.data.latestAckBlockHashPath", latestAckBlockHashPath.toString())
.build();
final BlockGeneratorConfig blockGeneratorConfig = configuration.getConfigData(BlockGeneratorConfig.class);
final SimulatorStartupDataConfig simulatorStartupDataConfig =
Expand All @@ -73,10 +65,8 @@ private SimulatorStartupDataImpl newInstanceToTest(final boolean enabled) {
@Test
void testInitializationWhenDisabled() {
assertThat(latestAckBlockNumberPath).doesNotExist();
assertThat(latestAckBlockHashPath).doesNotExist();
newInstanceToTest(false);
assertThat(latestAckBlockNumberPath).doesNotExist();
assertThat(latestAckBlockHashPath).doesNotExist();
}

/**
Expand All @@ -87,20 +77,13 @@ void testInitializationWhenDisabled() {
@Test
void testInitializationWhenEnabled() {
assertThat(latestAckBlockNumberPath).doesNotExist();
assertThat(latestAckBlockHashPath).doesNotExist();
newInstanceToTest(true);
assertThat(latestAckBlockNumberPath)
.exists()
.isRegularFile()
.isReadable()
.isWritable()
.isEmptyFile();
assertThat(latestAckBlockHashPath)
.exists()
.isRegularFile()
.isReadable()
.isWritable()
.isEmptyFile();
}

/**
Expand All @@ -109,7 +92,6 @@ void testInitializationWhenEnabled() {
*/
@Test
void testDefaultValues() {
assertThat(latestAckBlockHashPath).doesNotExist();
assertThat(latestAckBlockNumberPath).doesNotExist();
final SimulatorStartupDataImpl toTest = newInstanceToTest(false);
assertThat(toTest)
Expand All @@ -126,7 +108,6 @@ void testDefaultValues() {
*/
@Test
void testDefaultValuesIfInitialStartup() {
assertThat(latestAckBlockHashPath).doesNotExist();
assertThat(latestAckBlockNumberPath).doesNotExist();
final SimulatorStartupDataImpl toTest = newInstanceToTest(true);
assertThat(toTest)
Expand All @@ -144,32 +125,17 @@ void testDefaultValuesIfInitialStartup() {
@Test
void testCorrectValuesStartup() throws IOException {
Files.write(latestAckBlockNumberPath, "1".getBytes());
Files.write(latestAckBlockHashPath, validSimulatedBlockHash);
final SimulatorStartupDataImpl toTest = newInstanceToTest(true);
assertThat(toTest)
.returns(1L, from(SimulatorStartupDataImpl::getLatestAckBlockNumber))
.returns(validSimulatedBlockHash, from(SimulatorStartupDataImpl::getLatestAckBlockHash));
assertThat(toTest).returns(1L, from(SimulatorStartupDataImpl::getLatestAckBlockNumber));
}

/**
* This test aims to verify that the {@link SimulatorStartupDataImpl} will
* fail initialization if only the block number startup data file exists.
*/
@Test
void testFailedInitializationUnavailableHashFile() throws IOException {
Files.write(latestAckBlockNumberPath, "1".getBytes());
assertThat(latestAckBlockHashPath).doesNotExist();
assertThatIllegalStateException().isThrownBy(() -> newInstanceToTest(true));
}

/**
* This test aims to verify that the {@link SimulatorStartupDataImpl} will
* fail initialization if only the block hash startup data file exists.
* fail initialization if the block number startup data file exists with a blank value.
*/
@Test
void testFailedInitializationUnavailableBlockNumberFile() throws IOException {
assertThat(latestAckBlockNumberPath).doesNotExist();
Files.write(latestAckBlockHashPath, validSimulatedBlockHash);
Files.write(latestAckBlockNumberPath, "".getBytes());
assertThatIllegalStateException().isThrownBy(() -> newInstanceToTest(true));
}

Expand All @@ -181,46 +147,29 @@ void testFailedInitializationUnavailableBlockNumberFile() throws IOException {
@Test
void testFailedInitializationWrongNumberFormat() throws IOException {
Files.write(latestAckBlockNumberPath, "wrongNumberFormat".getBytes());
Files.write(latestAckBlockHashPath, validSimulatedBlockHash);
assertThatExceptionOfType(NumberFormatException.class).isThrownBy(() -> newInstanceToTest(true));
}

/**
* This test aims to verify that the {@link SimulatorStartupDataImpl} will
* fail initialization if the block hash startup data file contains an invalid
* hash length.
*/
@Test
void testFailedInitializationWrongHashLength() throws IOException {
Files.write(latestAckBlockNumberPath, "1".getBytes());
Files.write(latestAckBlockHashPath, new byte[StreamingTreeHasher.HASH_LENGTH - 1]);
assertThatIllegalStateException().isThrownBy(() -> newInstanceToTest(true));
}

/**
* This test aims to verify that the
* {@link SimulatorStartupDataImpl#updateLatestAckBlockStartupData(long, byte[])}
* {@link SimulatorStartupDataImpl#updateLatestAckBlockStartupData(long)}
* will correctly not update the startup data if the functionality is disabled.
*/
@Test
void testUpdateStartupDataDisabled() throws IOException {
assertThat(latestAckBlockHashPath).doesNotExist();
assertThat(latestAckBlockNumberPath).doesNotExist();
final SimulatorStartupDataImpl toTest = newInstanceToTest(false);
assertThat(toTest.isEnabled()).isFalse();
toTest.updateLatestAckBlockStartupData(1L, validSimulatedBlockHash);
assertThat(latestAckBlockHashPath).doesNotExist();
assertThat(latestAckBlockNumberPath).doesNotExist();
}

/**
* This test aims to verify that the
* {@link SimulatorStartupDataImpl#updateLatestAckBlockStartupData(long, byte[])}
* {@link SimulatorStartupDataImpl#updateLatestAckBlockStartupData(long)}
* will correctly update the startup data if the functionality is enabled.
*/
@Test
void testUpdateStartupDataEnabled() throws IOException {
assertThat(latestAckBlockHashPath).doesNotExist();
assertThat(latestAckBlockNumberPath).doesNotExist();
final SimulatorStartupDataImpl toTest = newInstanceToTest(true);
assertThat(toTest.isEnabled()).isTrue();
Expand All @@ -230,27 +179,14 @@ void testUpdateStartupDataEnabled() throws IOException {
.isReadable()
.isWritable()
.isEmptyFile();
assertThat(latestAckBlockHashPath)
.exists()
.isRegularFile()
.isReadable()
.isWritable()
.isEmptyFile();
// @todo(904) we need the correct response code
toTest.updateLatestAckBlockStartupData(1L, validSimulatedBlockHash);
toTest.updateLatestAckBlockStartupData(1L);
assertThat(latestAckBlockNumberPath)
.exists()
.isRegularFile()
.isReadable()
.isWritable()
.isNotEmptyFile()
.hasBinaryContent("1".getBytes());
assertThat(latestAckBlockHashPath)
.exists()
.isRegularFile()
.isReadable()
.isWritable()
.isNotEmptyFile()
.hasBinaryContent(validSimulatedBlockHash);
}
}
Loading