Skip to content

Commit

Permalink
feat: add Bytes support for transaction iteration in ConsensusEvent a…
Browse files Browse the repository at this point in the history
…nd its ancestors (#16844)

Signed-off-by: Ivan Kavaldzhiev <ivankavaldzhiev@gmail.com>
Signed-off-by: Mustafa Uzun <mustafa.uzun@limechain.tech>
Co-authored-by: Mustafa Uzun <mustafa.uzun@limechain.tech>
Co-authored-by: Roger Barker <roger.barker@swirldslabs.com>
  • Loading branch information
3 people authored Jan 10, 2025
1 parent 45f108d commit be1981d
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 20 deletions.
23 changes: 22 additions & 1 deletion platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2024-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.iss.ISSTestingToolMain"

mainModuleInfo { annotationProcessor("com.swirlds.config.processor") }

testModuleInfo {
requires("org.assertj.core")
requires("org.junit.jupiter.api")
requires("org.mockito")
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import static com.swirlds.platform.test.fixtures.state.FakeStateLifecycles.FAKE_MERKLE_STATE_LIFECYCLES;
import static com.swirlds.platform.test.fixtures.state.FakeStateLifecycles.registerMerkleStateRootClassIds;

import com.hedera.hapi.platform.event.StateSignatureTransaction;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.swirlds.common.constructable.ClassConstructorPair;
import com.swirlds.common.constructable.ConstructableRegistry;
import com.swirlds.common.constructable.ConstructableRegistryException;
Expand Down Expand Up @@ -134,4 +136,9 @@ public BasicSoftwareVersion getSoftwareVersion() {
public List<Class<? extends Record>> getConfigDataTypes() {
return List.of(ISSTestingToolConfig.class);
}

@Override
public Bytes encodeSystemTransaction(@NonNull final StateSignatureTransaction transaction) {
return StateSignatureTransaction.PROTOBUF.toBytes(transaction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.hedera.hapi.node.state.roster.Roster;
import com.hedera.hapi.node.state.roster.RosterEntry;
import com.hedera.hapi.platform.event.StateSignatureTransaction;
import com.hedera.pbj.runtime.ParseException;
import com.swirlds.common.constructable.ConstructableIgnored;
import com.swirlds.common.io.SelfSerializable;
import com.swirlds.common.io.streams.SerializableDataInputStream;
Expand All @@ -53,7 +54,9 @@
import com.swirlds.platform.system.Round;
import com.swirlds.platform.system.SoftwareVersion;
import com.swirlds.platform.system.events.ConsensusEvent;
import com.swirlds.platform.system.events.Event;
import com.swirlds.platform.system.transaction.ConsensusTransaction;
import com.swirlds.platform.system.transaction.Transaction;
import com.swirlds.platform.test.fixtures.state.FakeStateLifecycles;
import com.swirlds.state.merkle.singleton.StringLeaf;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand Down Expand Up @@ -193,11 +196,11 @@ public void init(
writeObjectByChildIndex(PLANNED_ISS_LIST_INDEX, plannedIssList);
writeObjectByChildIndex(PLANNED_LOG_ERROR_LIST_INDEX, plannedLogErrorList);
} else {
StringLeaf runningSumLeaf = getChild(RUNNING_SUM_INDEX);
final StringLeaf runningSumLeaf = getChild(RUNNING_SUM_INDEX);
if (runningSumLeaf != null) {
runningSum = Long.parseLong(runningSumLeaf.getLabel());
}
StringLeaf genesisTimestampLeaf = getChild(GENESIS_TIMESTAMP_INDEX);
final StringLeaf genesisTimestampLeaf = getChild(GENESIS_TIMESTAMP_INDEX);
if (genesisTimestampLeaf != null) {
genesisTimestamp = Instant.parse(genesisTimestampLeaf.getLabel());
}
Expand All @@ -210,47 +213,92 @@ public void init(
Scratchpad.create(platform.getContext(), selfId, IssTestingToolScratchpad.class, "ISSTestingTool");
}

<T extends SelfSerializable> List<T> readObjectByChildIndex(int index, Supplier<T> factory) {
StringLeaf stringValue = getChild(index);
<T extends SelfSerializable> List<T> readObjectByChildIndex(final int index, final Supplier<T> factory) {
final StringLeaf stringValue = getChild(index);
if (stringValue != null) {
try {
SerializableDataInputStream in = new SerializableDataInputStream(
final SerializableDataInputStream in = new SerializableDataInputStream(
new ByteArrayInputStream(stringValue.getLabel().getBytes(StandardCharsets.UTF_8)));
return in.readSerializableList(1024, false, factory);
} catch (IOException e) {
} catch (final IOException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}

<T extends SelfSerializable> void writeObjectByChildIndex(int index, List<T> list) {
<T extends SelfSerializable> void writeObjectByChildIndex(final int index, final List<T> list) {
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
SerializableDataOutputStream out = new SerializableDataOutputStream(byteOut);
final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
final SerializableDataOutputStream out = new SerializableDataOutputStream(byteOut);
out.writeSerializableList(list, false, true);
setChild(index, new StringLeaf(byteOut.toString(StandardCharsets.UTF_8)));
} catch (IOException e) {
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void preHandle(
@NonNull final Event event,
@NonNull
final Consumer<ScopedSystemTransaction<StateSignatureTransaction>>
stateSignatureTransactionCallback) {
event.forEachTransaction(transaction -> {
// We are not interested in pre-handling any system transactions, as they are
// specific for the platform only.We also don't want to consume deprecated
// EventTransaction.STATE_SIGNATURE_TRANSACTION system transactions in the
// callback,since it's intended to be used only for the new form of encoded system
// transactions in Bytes.Thus, we can directly skip the current
// iteration, if it processes a deprecated system transaction with the
// EventTransaction.STATE_SIGNATURE_TRANSACTION type.
if (transaction.isSystem()) {
return;
}

// We should consume in the callback the new form of system transactions in Bytes
if (areTransactionBytesSystemOnes(transaction)) {
consumeSystemTransaction(transaction, event, stateSignatureTransactionCallback);
}
});
}

/**
* {@inheritDoc}
*/
@Override
public void handleConsensusRound(
@NonNull final Round round,
@NonNull final PlatformStateModifier platformState,
@NonNull final Consumer<ScopedSystemTransaction<StateSignatureTransaction>> stateSignatureTransaction) {
@NonNull
final Consumer<ScopedSystemTransaction<StateSignatureTransaction>>
stateSignatureTransactionCallback) {
throwIfImmutable();
final Iterator<ConsensusEvent> eventIterator = round.iterator();

while (eventIterator.hasNext()) {
final ConsensusEvent event = eventIterator.next();
final var event = eventIterator.next();
captureTimestamp(event);
event.consensusTransactionIterator().forEachRemaining(this::handleTransaction);
event.consensusTransactionIterator().forEachRemaining(transaction -> {
// We are not interested in handling any system transactions, as they are specific
// for the platform only.We also don't want to consume deprecated
// EventTransaction.STATE_SIGNATURE_TRANSACTION system transactions in the
// callback,since it's intended to be used only for the new form of encoded system
// transactions in Bytes.Thus, we can directly skip the current
// iteration, if it processes a deprecated system transaction with the
// EventTransaction.STATE_SIGNATURE_TRANSACTION type.
if (transaction.isSystem()) {
return;
}

// We should consume in the callback the new form of system transactions in Bytes
if (areTransactionBytesSystemOnes(transaction)) {
consumeSystemTransaction(transaction, event, stateSignatureTransactionCallback);
} else {
handleTransaction(transaction);
}
});
if (!eventIterator.hasNext()) {
final Instant currentTimestamp = event.getConsensusTimestamp();
final Duration elapsedSinceGenesis = Duration.between(genesisTimestamp, currentTimestamp);
Expand Down Expand Up @@ -291,15 +339,37 @@ private void captureTimestamp(final ConsensusEvent event) {
* @param transaction the transaction to apply
*/
private void handleTransaction(final ConsensusTransaction transaction) {
if (transaction.isSystem()) {
return;
}
final int delta =
ByteUtils.byteArrayToInt(transaction.getApplicationTransaction().toByteArray(), 0);
runningSum += delta;
setChild(RUNNING_SUM_INDEX, new StringLeaf(Long.toString(runningSum)));
}

/**
* Checks if the transaction bytes are system ones. The test creates application transactions
* with max length of 4. System transactions will be always bigger than that.
*
* @param transaction the consensus transaction to check
* @return true if the transaction bytes are system ones, false otherwise
*/
private boolean areTransactionBytesSystemOnes(final Transaction transaction) {
return transaction.getApplicationTransaction().length() > 4;
}

private void consumeSystemTransaction(
final Transaction transaction,
final Event event,
final Consumer<ScopedSystemTransaction<StateSignatureTransaction>> stateSignatureTransactionCallback) {
try {
final var stateSignatureTransaction =
StateSignatureTransaction.PROTOBUF.parse(transaction.getApplicationTransaction());
stateSignatureTransactionCallback.accept(new ScopedSystemTransaction<>(
event.getCreatorId(), event.getSoftwareVersion(), stateSignatureTransaction));
} catch (final ParseException e) {
logger.error("Failed to parse StateSignatureTransaction", e);
}
}

/**
* Iterate over a list of planned incidents, and return the first one that should be triggered. If no incident from
* the list should be triggered, return null
Expand Down
Loading

0 comments on commit be1981d

Please sign in to comment.