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

refactor: enhance consistency testing tool #17136

Merged
merged 19 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
97eac92
refactor: enhance consistency testing tool
mustafauzunn Dec 19, 2024
63edb17
refactor: consumer for system transactions
mustafauzunn Dec 19, 2024
f4caa08
Merge branch '16703-refactor-consumer-system-transactions' of github.…
mustafauzunn Dec 20, 2024
8478fd9
Merge branch 'develop' into 16703-refactor-consumer-system-transactions
mustafauzunn Dec 20, 2024
072a689
refactor: after merge with consumer refactor branch
mustafauzunn Dec 20, 2024
84a256f
Merge branch '16703-refactor-consumer-system-transactions' of github.…
mustafauzunn Dec 20, 2024
5697b4a
docs: add javadoc
mustafauzunn Dec 20, 2024
7643692
Merge branch 'develop' of github.com:hashgraph/hedera-services into 1…
mustafauzunn Dec 20, 2024
312f184
refactor: consistency testing tool
mustafauzunn Dec 20, 2024
7a1f1aa
test: add unit tests for the new logic in handle and prehandle
mustafauzunn Jan 6, 2025
9da0556
Merge branch 'main' of github.com:hashgraph/hedera-services into 1679…
mustafauzunn Jan 6, 2025
c1f7e96
refactor: address PR comments
mustafauzunn Jan 7, 2025
c8f4bbf
refactor: unit tests
mustafauzunn Jan 7, 2025
fa9cd85
Merge branch 'main' of github.com:hashgraph/hedera-services into 1679…
mustafauzunn Jan 7, 2025
f39d6ff
refactor: unit test import
mustafauzunn Jan 7, 2025
c2af1a6
refactor: implementation
mustafauzunn Jan 7, 2025
4f73ef8
refactor: implementation
mustafauzunn Jan 10, 2025
203da89
Merge branch 'main' of github.com:hashgraph/hedera-services into 1679…
mustafauzunn Jan 10, 2025
ee1eaab
refactor: PR comments
mustafauzunn Jan 10, 2025
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
@@ -1,4 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2025 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.
*/

plugins { id("org.hiero.gradle.module.application") }

application.mainClass = "com.swirlds.demo.consistency.ConsistencyTestingToolMain"
Expand All @@ -7,6 +22,7 @@ mainModuleInfo { annotationProcessor("com.swirlds.config.processor") }

testModuleInfo {
requires("com.swirlds.common.test.fixtures")
requires("org.assertj.core")
requires("org.junit.jupiter.api")
requires("org.mockito")
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
* Copyright (C) 2023-2025 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
* Copyright (C) 2023-2025 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
/*
* Copyright (C) 2025 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.swirlds.demo.consistency;

import static com.swirlds.platform.test.fixtures.state.FakeMerkleStateLifecycles.FAKE_MERKLE_STATE_LIFECYCLES;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.hedera.hapi.platform.event.StateSignatureTransaction;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.swirlds.common.config.StateCommonConfig;
import com.swirlds.common.context.PlatformContext;
import com.swirlds.common.platform.NodeId;
import com.swirlds.config.api.Configuration;
import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction;
import com.swirlds.platform.state.PlatformStateModifier;
import com.swirlds.platform.system.BasicSoftwareVersion;
import com.swirlds.platform.system.InitTrigger;
import com.swirlds.platform.system.Platform;
import com.swirlds.platform.system.Round;
import com.swirlds.platform.system.SoftwareVersion;
import com.swirlds.platform.system.events.ConsensusEvent;
import com.swirlds.platform.system.transaction.ConsensusTransaction;
import com.swirlds.platform.system.transaction.Transaction;
import com.swirlds.platform.system.transaction.TransactionWrapper;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class ConsistencyTestingToolStateTest {

private static ConsistencyTestingToolState state;
private ConsistencyTestingToolMain main;
private Random random;
private PlatformStateModifier platformStateModifier;
private Platform platform;
private PlatformContext platformContext;
private Round round;
private ConsensusEvent event;
private List<ScopedSystemTransaction<StateSignatureTransaction>> consumedTransactions;
private Consumer<ScopedSystemTransaction<StateSignatureTransaction>> consumer;
private Transaction consensusTransaction;
private StateSignatureTransaction stateSignatureTransaction;
private InitTrigger initTrigger;
private SoftwareVersion softwareVersion;
private Configuration configuration;
private ConsistencyTestingToolConfig consistencyTestingToolConfig;
private StateCommonConfig stateCommonConfig;

@BeforeAll
static void initState() {
state = new ConsistencyTestingToolState(FAKE_MERKLE_STATE_LIFECYCLES, mock(Function.class));
FAKE_MERKLE_STATE_LIFECYCLES.initStates(state);
}

@BeforeEach
void setUp() {
platform = mock(Platform.class);
initTrigger = InitTrigger.GENESIS;
softwareVersion = new BasicSoftwareVersion(1);
platformContext = mock(PlatformContext.class);
configuration = mock(Configuration.class);
consistencyTestingToolConfig = mock(ConsistencyTestingToolConfig.class);
stateCommonConfig = mock(StateCommonConfig.class);

when(platform.getSelfId()).thenReturn(NodeId.of(1L));
when(platform.getContext()).thenReturn(platformContext);
when(platformContext.getConfiguration()).thenReturn(configuration);
when(configuration.getConfigData(ConsistencyTestingToolConfig.class)).thenReturn(consistencyTestingToolConfig);
when(configuration.getConfigData(StateCommonConfig.class)).thenReturn(stateCommonConfig);
when(consistencyTestingToolConfig.freezeAfterGenesis()).thenReturn(Duration.ZERO);
when(stateCommonConfig.savedStateDirectory()).thenReturn(Path.of("consistency-test"));
when(consistencyTestingToolConfig.logfileDirectory()).thenReturn("consistency-test");

state.init(platform, initTrigger, softwareVersion);

main = mock(ConsistencyTestingToolMain.class);
IvanKavaldzhiev marked this conversation as resolved.
Show resolved Hide resolved
random = new Random();
platformStateModifier = mock(PlatformStateModifier.class);
round = mock(Round.class);
event = mock(ConsensusEvent.class);

consumedTransactions = new ArrayList<>();
consumer = systemTransaction -> consumedTransactions.add(systemTransaction);
consensusTransaction = mock(TransactionWrapper.class);

final byte[] signature = new byte[384];
random.nextBytes(signature);
final byte[] hash = new byte[48];
random.nextBytes(hash);
stateSignatureTransaction = StateSignatureTransaction.newBuilder()
.signature(Bytes.wrap(signature))
.hash(Bytes.wrap(hash))
.round(round.getRoundNum())
.build();
}

@Test
void handleConsensusRoundWithApplicationTransactionEmptySystemTransactionList() {
IvanKavaldzhiev marked this conversation as resolved.
Show resolved Hide resolved
final var bytes = Bytes.wrap(new byte[] {1, 1, 1, 1, 1, 1, 1, 1});
when(consensusTransaction.getApplicationTransaction()).thenReturn(bytes);

doAnswer(invocation -> {
BiConsumer<ConsensusEvent, Transaction> consumer = invocation.getArgument(0);
consumer.accept(event, consensusTransaction);
return null;
})
.when(round)
.forEachEventTransaction(any());

state.handleConsensusRound(round, platformStateModifier, consumer);

assertThat(consumedTransactions).isEmpty();
}

@Test
void handleConsensusRoundWithSystemTransaction() {
final var stateSignatureTransactionBytes =
StateSignatureTransaction.PROTOBUF.toBytes(stateSignatureTransaction);
when(main.encodeSystemTransaction(stateSignatureTransaction)).thenReturn(stateSignatureTransactionBytes);
when(consensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);

doAnswer(invocation -> {
BiConsumer<ConsensusEvent, Transaction> consumer = invocation.getArgument(0);
consumer.accept(event, consensusTransaction);
return null;
})
.when(round)
.forEachEventTransaction(any());

state.handleConsensusRound(round, platformStateModifier, consumer);

assertThat(consumedTransactions).hasSize(1);
}

@Test
void handleConsensusRoundWithMultipleSystemTransactions() {
// Given
final var secondConsensusTransaction = mock(TransactionWrapper.class);
final var thirdConsensusTransaction = mock(TransactionWrapper.class);
when(round.iterator()).thenReturn(Collections.singletonList(event).iterator());
when(event.getConsensusTimestamp()).thenReturn(Instant.now());
when(event.consensusTransactionIterator())
.thenReturn(List.of(
(ConsensusTransaction) consensusTransaction,
secondConsensusTransaction,
thirdConsensusTransaction)
.iterator());

final var stateSignatureTransactionBytes =
StateSignatureTransaction.PROTOBUF.toBytes(stateSignatureTransaction);
when(main.encodeSystemTransaction(stateSignatureTransaction)).thenReturn(stateSignatureTransactionBytes);
when(consensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);
when(secondConsensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);
when(thirdConsensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);

doAnswer(invocation -> {
BiConsumer<ConsensusEvent, Transaction> consumer = invocation.getArgument(0);
consumer.accept(event, consensusTransaction);
consumer.accept(event, secondConsensusTransaction);
consumer.accept(event, thirdConsensusTransaction);
return null;
})
.when(round)
.forEachEventTransaction(any());

// When
state.handleConsensusRound(round, platformStateModifier, consumer);

assertThat(consumedTransactions).hasSize(3);
}

@Test
void handleConsensusRoundWithDeprecatedSystemTransaction() {
when(consensusTransaction.getApplicationTransaction()).thenReturn(Bytes.EMPTY);
when(consensusTransaction.isSystem()).thenReturn(true);

doAnswer(invocation -> {
BiConsumer<ConsensusEvent, Transaction> consumer = invocation.getArgument(0);
consumer.accept(event, consensusTransaction);
return null;
})
.when(round)
.forEachEventTransaction(any());

state.handleConsensusRound(round, platformStateModifier, consumer);

assertThat(consumedTransactions).isEmpty();
}

@Test
void preHandleEventWithMultipleSystemTransactions() {
when(round.iterator()).thenReturn(Collections.singletonList(event).iterator());
final var secondConsensusTransaction = mock(TransactionWrapper.class);
final var thirdConsensusTransaction = mock(TransactionWrapper.class);
when(event.transactionIterator())
.thenReturn(List.of(consensusTransaction, secondConsensusTransaction, thirdConsensusTransaction)
.iterator());
final var stateSignatureTransactionBytes =
StateSignatureTransaction.PROTOBUF.toBytes(stateSignatureTransaction);
when(consensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);
when(secondConsensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);
when(thirdConsensusTransaction.getApplicationTransaction()).thenReturn(stateSignatureTransactionBytes);

doAnswer(invocation -> {
Consumer<Transaction> consumer = invocation.getArgument(0);
consumer.accept(consensusTransaction);
consumer.accept(secondConsensusTransaction);
consumer.accept(thirdConsensusTransaction);
return null;
})
.when(event)
.forEachTransaction(any());

state.preHandle(event, consumer);

assertThat(consumedTransactions).hasSize(3);
}

@Test
void preHandleEventWithSystemTransaction() {
when(round.iterator()).thenReturn(Collections.singletonList(event).iterator());
when(event.transactionIterator())
.thenReturn(Collections.singletonList(consensusTransaction).iterator());
final var emptyStateSignatureBytes = StateSignatureTransaction.PROTOBUF.toBytes(stateSignatureTransaction);
when(consensusTransaction.getApplicationTransaction()).thenReturn(emptyStateSignatureBytes);

doAnswer(invocation -> {
Consumer<Transaction> consumer = invocation.getArgument(0);
consumer.accept(consensusTransaction);
return null;
})
.when(event)
.forEachTransaction(any());

state.preHandle(event, consumer);

assertThat(consumedTransactions).hasSize(1);
}

@Test
void preHandleEventWithDeprecatedSystemTransaction() {
when(round.iterator()).thenReturn(Collections.singletonList(event).iterator());
when(event.transactionIterator())
.thenReturn(Collections.singletonList(consensusTransaction).iterator());
when(consensusTransaction.isSystem()).thenReturn(true);

doAnswer(invocation -> {
Consumer<Transaction> consumer = invocation.getArgument(0);
consumer.accept(consensusTransaction);
return null;
})
.when(event)
.forEachTransaction(any());

state.preHandle(event, consumer);

assertThat(consumedTransactions).isEmpty();
}
IvanKavaldzhiev marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2024 Hedera Hashgraph, LLC
* Copyright (C) 2016-2025 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2024 Hedera Hashgraph, LLC
* Copyright (C) 2016-2025 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.
Expand Down Expand Up @@ -71,7 +71,7 @@ default void init(
* <strong>This method is always invoked on an immutable state.</strong>
*
* @param event the event to perform pre-handling on
* @param stateSignatureTransaction a consumer that accepts a list of {@link ScopedSystemTransaction}s that
* @param stateSignatureTransaction a consumer that accepts a {@link ScopedSystemTransaction} that
* will be used for callbacks
*/
default void preHandle(
Expand All @@ -83,7 +83,7 @@ default void preHandle(
*
* @param round the round to apply
* @param platformState the platform state
* @param stateSignatureTransaction a consumer that accepts a list of {@link ScopedSystemTransaction}s that
* @param stateSignatureTransaction a consumer that accepts a {@link ScopedSystemTransaction} that
* will be used for callbacks
*/
void handleConsensusRound(
Expand Down