Skip to content

Commit

Permalink
HIP-1056 Topic block item to record item transformer (#10318)
Browse files Browse the repository at this point in the history
 Add block item to record item transformers for ConsensusCreateTopic and ConsensusSubmitMessage

---------

Signed-off-by: filev94 <[email protected]>
  • Loading branch information
filev94 authored Feb 10, 2025
1 parent 0491663 commit 46ff040
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.hedera.mirror.importer.downloader.block.transformer;

import com.hedera.hapi.block.stream.output.protoc.StateIdentifier;
import com.hedera.mirror.common.domain.transaction.BlockItem;
import com.hedera.mirror.common.domain.transaction.TransactionType;
import com.hederahashgraph.api.proto.java.TransactionRecord;
import jakarta.inject.Named;

@Named
final class ConsensusCreateTopicTransformer extends AbstractBlockItemTransformer {
@Override
protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) {

if (!blockItem.successful()) {
return;
}

for (var stateChange : blockItem.stateChanges()) {
for (var change : stateChange.getStateChangesList()) {
if (change.getStateId() == StateIdentifier.STATE_ID_TOPICS.getNumber() && change.hasMapUpdate()) {
var key = change.getMapUpdate().getKey();
if (key.hasTopicIdKey()) {
transactionRecordBuilder.getReceiptBuilder().setTopicID(key.getTopicIdKey());
return;
}
}
}
}
}

@Override
public TransactionType getType() {
return TransactionType.CONSENSUSCREATETOPIC;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.hedera.mirror.importer.downloader.block.transformer;

import static com.hedera.mirror.importer.util.Utility.DEFAULT_RUNNING_HASH_VERSION;

import com.hedera.hapi.block.stream.output.protoc.StateIdentifier;
import com.hedera.mirror.common.domain.transaction.BlockItem;
import com.hedera.mirror.common.domain.transaction.TransactionType;
import com.hederahashgraph.api.proto.java.TransactionRecord;
import jakarta.inject.Named;

@Named
final class ConsensusSubmitMessageTransformer extends AbstractBlockItemTransformer {

@Override
protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) {

if (!blockItem.successful()) {
return;
}

for (var transactionOutput : blockItem.transactionOutput()) {
if (transactionOutput.hasSubmitMessage()) {
var submitMessageOutput = transactionOutput.getSubmitMessage();
var assessedCustomFees = submitMessageOutput.getAssessedCustomFeesList();
transactionRecordBuilder.addAllAssessedCustomFees(assessedCustomFees);
break;
}
}

for (var stateChange : blockItem.stateChanges()) {
for (var change : stateChange.getStateChangesList()) {
if (change.getStateId() == StateIdentifier.STATE_ID_TOPICS.getNumber() && change.hasMapUpdate()) {
var value = change.getMapUpdate().getValue();
if (value.hasTopicValue()) {
var topicValue = value.getTopicValue();
transactionRecordBuilder
.getReceiptBuilder()
.setTopicRunningHash(topicValue.getRunningHash())
.setTopicSequenceNumber(topicValue.getSequenceNumber())
.setTopicRunningHashVersion(DEFAULT_RUNNING_HASH_VERSION);

return;
}
}
}
}
}

@Override
public TransactionType getType() {
return TransactionType.CONSENSUSSUBMITMESSAGE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.hedera.mirror.importer.parser.record.transactionhandler;

import static com.hedera.mirror.common.util.DomainUtils.toBytes;
import static com.hedera.mirror.importer.util.Utility.DEFAULT_RUNNING_HASH_VERSION;

import com.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.domain.topic.TopicMessage;
Expand All @@ -33,9 +34,6 @@
@RequiredArgsConstructor
class ConsensusSubmitMessageTransactionHandler extends AbstractTransactionHandler {

// Blockstreams no longer contain runningHashVersion, this is the latest version
static final int DEFAULT_RUNNING_HASH_VERSION = 3;

private final EntityListener entityListener;
private final EntityProperties entityProperties;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class Utility {
static final String RECOVERABLE_ERROR = "Recoverable error. ";
static final String HALT_ON_ERROR_DEFAULT = "false";
private static final int ECDSA_SECP256K1_COMPRESSED_KEY_LENGTH = 33;
// Blockstreams no longer contain runningHashVersion, this is the latest version
public static final long DEFAULT_RUNNING_HASH_VERSION = 3;

/**
* Converts an ECDSA secp256k1 alias to a 20 byte EVM address by taking the keccak hash of it. Logic copied from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.hederahashgraph.api.proto.java.SignedTransaction;
import com.hederahashgraph.api.proto.java.Transaction;
import com.hederahashgraph.api.proto.java.TransactionID;
import com.hederahashgraph.api.proto.java.TransactionReceipt;
import com.hederahashgraph.api.proto.java.TransactionRecord;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -385,17 +386,12 @@ void fileDeleteTransform() {
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash));
}

@ParameterizedTest
@EnumSource(
value = ResponseCodeEnum.class,
mode = EnumSource.Mode.INCLUDE,
names = {"FEE_SCHEDULE_FILE_PART_UPLOADED", "SUCCESS", "SUCCESS_BUT_MISSING_EXPECTED_OPERATION"})
void fileCreateTransform(ResponseCodeEnum successfulStatus) {
@Test
void fileCreateTransform() {
// given
var expectedRecordItem = recordItemBuilder
.fileCreate()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.receipt(r -> r.setStatus(successfulStatus))
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem = blockItemBuilder.fileCreate(expectedRecordItem).build();
Expand Down Expand Up @@ -480,6 +476,207 @@ void fileUpdateTransform() {
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash));
}

@Test
void consensusCreateTopicTransform() {
// given
var expectedRecordItem = recordItemBuilder
.consensusCreateTopic()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem =
blockItemBuilder.consensusCreateTopic(expectedRecordItem).build();

var expectedTopicId = blockItem
.stateChanges()
.getFirst()
.getStateChanges(3)
.getMapUpdate()
.getKey()
.getTopicIdKey()
.getTopicNum();

var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash)
.returns(
expectedTopicId,
transactionRecord ->
transactionRecord.getReceipt().getTopicID().getTopicNum()));
}

@Test
void consensusCreateTopicTransformUnsuccessful() {
// given
var expectedRecordItem = recordItemBuilder
.consensusCreateTopic()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.receipt(TransactionReceipt.Builder::clearTopicID)
.status(ResponseCodeEnum.INVALID_TRANSACTION)
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem =
blockItemBuilder.consensusCreateTopic(expectedRecordItem).build();

var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash)
.returns(
0L,
transactionRecord ->
transactionRecord.getReceipt().getTopicID().getTopicNum()));
}

@Test
void consensusSubmitMessageTransform() {
// given
var expectedRecordItem = recordItemBuilder
.consensusSubmitMessage()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem =
blockItemBuilder.consensusSubmitMessage(expectedRecordItem).build();

var expectedFees =
blockItem.transactionOutput().getFirst().getSubmitMessage().getAssessedCustomFeesList();
var expectedRunningHash = blockItem
.stateChanges()
.getFirst()
.getStateChanges(3)
.getMapUpdate()
.getValue()
.getTopicValue()
.getRunningHash();

var expectedSequenceNumber = blockItem
.stateChanges()
.getFirst()
.getStateChanges(3)
.getMapUpdate()
.getValue()
.getTopicValue()
.getSequenceNumber();

var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash)
.returns(
expectedRunningHash,
transactionRecord -> transactionRecord.getReceipt().getTopicRunningHash())
.returns(
expectedSequenceNumber,
transactionRecord -> transactionRecord.getReceipt().getTopicSequenceNumber())
.returns(expectedFees, TransactionRecord::getAssessedCustomFeesList));
}

@Test
void consensusSubmitMessageTransformUnsuccessful() {
// given
var expectedRecordItem = recordItemBuilder
.consensusSubmitMessage()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.receipt(r -> r.setTopicRunningHash(ByteString.EMPTY))
.incrementer((b, r) -> r.getReceiptBuilder().setTopicSequenceNumber(0))
.status(ResponseCodeEnum.INVALID_TRANSACTION)
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem =
blockItemBuilder.consensusSubmitMessage(expectedRecordItem).build();

var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash));
}

@Test
void consensusUpdateTopicTransform() {
// given
var expectedRecordItem = recordItemBuilder
.consensusUpdateTopic()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem = blockItemBuilder.fileUpdate(expectedRecordItem).build();
var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash));
}

@Test
void consensusDeleteTopicTransform() {
// given
var expectedRecordItem = recordItemBuilder
.consensusDeleteTopic()
.recordItem(r -> r.hapiVersion(HAPI_VERSION))
.build();
var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem);
var blockItem = blockItemBuilder.fileUpdate(expectedRecordItem).build();
var blockFile = blockFileBuilder.items(List.of(blockItem)).build();

// when
var recordFile = blockFileTransformer.transform(blockFile);

// then
assertRecordFile(recordFile, blockFile, items -> assertThat(items)
.hasSize(1)
.first()
.satisfies(item -> assertRecordItem(item, expectedRecordItem))
.returns(null, RecordItem::getPrevious)
.extracting(RecordItem::getTransactionRecord)
.returns(expectedTransactionHash, TransactionRecord::getTransactionHash));
}

private void assertRecordFile(
RecordFile actual, BlockFile blockFile, Consumer<Collection<RecordItem>> itemsAssert) {
var hapiProtoVersion = blockFile.getBlockHeader().getHapiProtoVersion();
Expand Down Expand Up @@ -548,7 +745,11 @@ private void assertRecordItem(RecordItem recordItem, RecordItem expectedRecordIt
"transactionRecord.transactionID_.transactionValidStart_.memoizedSize",
"transactionRecord.receipt_.fileID_.memoizedHashCode",
"transactionRecord.receipt_.fileID_.memoizedSize",
"transactionRecord.receipt_.fileID_.memoizedIsInitialized")
"transactionRecord.receipt_.fileID_.memoizedIsInitialized",
"transactionRecord.receipt_.topicID_.memoizedHashCode",
"transactionRecord.receipt_.topicID_.memoizedSize",
"transactionRecord.receipt_.topicID_.memoizedIsInitialized",
"transactionRecord.receipt_.topicRunningHashVersion_")
.isEqualTo(expectedRecordItem);
}

Expand Down
Loading

0 comments on commit 46ff040

Please sign in to comment.