From 4ceede553b1f1e7b7ff5d9b13db78b61013b20c9 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Wed, 29 Jan 2025 17:04:12 -0500 Subject: [PATCH 01/18] Add schedule block item transformers Signed-off-by: Edwin Greene --- .../AbstractBlockItemTransformer.java | 9 +- .../CryptoTransferTransformer.java | 2 +- ...ansformer.java => DefaultTransformer.java} | 5 +- .../ScheduleCreateTransformer.java | 53 ++++++ .../ScheduleDeleteTransformer.java | 57 +++++++ .../transformer/ScheduleSignTransformer.java | 51 ++++++ .../importer/ImporterIntegrationTest.java | 58 +++++++ .../block/BlockFileTransformerTest.java | 157 ++++++++++++------ .../parser/domain/BlockItemBuilder.java | 133 +++++++++++++-- .../parser/domain/RecordItemBuilder.java | 6 +- .../EntityRecordItemListenerScheduleTest.java | 118 ++++++++----- 11 files changed, 540 insertions(+), 109 deletions(-) rename hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/{UnknownTransformer.java => DefaultTransformer.java} (81%) create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java index 7f8df331974..2ced117154c 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java @@ -38,16 +38,21 @@ public TransactionRecord getTransactionRecord(BlockItem blockItem, TransactionBo .addAllPaidStakingRewards(transactionResult.getPaidStakingRewardsList()) .addAllTokenTransferLists(transactionResult.getTokenTransferListsList()) .setConsensusTimestamp(transactionResult.getConsensusTimestamp()) - .setParentConsensusTimestamp(transactionResult.getParentConsensusTimestamp()) .setMemo(transactionBody.getMemo()) .setReceipt(receiptBuilder) - .setScheduleRef(transactionResult.getScheduleRef()) .setTransactionFee(transactionResult.getTransactionFeeCharged()) .setTransactionHash( calculateTransactionHash(blockItem.transaction().getSignedTransactionBytes())) .setTransactionID(transactionBody.getTransactionID()) .setTransferList(transactionResult.getTransferList()); + if (transactionResult.hasParentConsensusTimestamp()) { + transactionRecordBuilder.setParentConsensusTimestamp(transactionResult.getParentConsensusTimestamp()); + } + if (transactionResult.hasScheduleRef()) { + transactionRecordBuilder.setScheduleRef(transactionResult.getScheduleRef()); + } + updateTransactionRecord(blockItem, transactionRecordBuilder); return transactionRecordBuilder.build(); } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java index 808161802fb..b6a1464d61f 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java @@ -22,7 +22,7 @@ import jakarta.inject.Named; @Named -class CryptoTransferTransformer extends AbstractBlockItemTransformer { +final class CryptoTransferTransformer extends AbstractBlockItemTransformer { @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/UnknownTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/DefaultTransformer.java similarity index 81% rename from hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/UnknownTransformer.java rename to hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/DefaultTransformer.java index d7862475fc9..b736eff391c 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/UnknownTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/DefaultTransformer.java @@ -20,8 +20,11 @@ import jakarta.inject.Named; @Named -class UnknownTransformer extends AbstractBlockItemTransformer { +final class DefaultTransformer extends AbstractBlockItemTransformer { + /** + * Any transaction type that has no operations for the updateTransactionRecord method can use this transformer + */ @Override public TransactionType getType() { return TransactionType.UNKNOWN; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java new file mode 100644 index 00000000000..81ffb82802d --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java @@ -0,0 +1,53 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; + +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 ScheduleCreateTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (blockItem.transactionResult().getStatus() != SUCCESS) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_SCHEDULES_BY_ID.getNumber() && stateChange.hasMapUpdate()) { + var key = stateChange.getMapUpdate().getKey(); + if (key.hasScheduleIdKey()) { + transactionRecordBuilder.getReceiptBuilder().setScheduleID(key.getScheduleIdKey()); + return; + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.SCHEDULECREATE; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java new file mode 100644 index 00000000000..b9c484e48f2 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java @@ -0,0 +1,57 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; + +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 ScheduleDeleteTransformer extends AbstractBlockItemTransformer { + + @SuppressWarnings("java:S3776") + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (blockItem.transactionResult().getStatus() != SUCCESS) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_SCHEDULES_BY_ID.getNumber() && stateChange.hasMapUpdate()) { + var value = stateChange.getMapUpdate().getValue(); + if (value.hasScheduleValue()) { + var schedule = value.getScheduleValue(); + if (schedule.getDeleted()) { + transactionRecordBuilder.getReceiptBuilder().setScheduleID(schedule.getScheduleId()); + return; + } + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.SCHEDULEDELETE; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java new file mode 100644 index 00000000000..21fdcc79172 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java @@ -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 static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; + +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 ScheduleSignTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (blockItem.transactionResult().getStatus() != SUCCESS) { + return; + } + + for (var transactionOutput : blockItem.transactionOutput()) { + if (transactionOutput.hasSignSchedule() + && transactionOutput.getSignSchedule().hasScheduledTransactionId()) { + transactionRecordBuilder + .getReceiptBuilder() + .setScheduledTransactionID( + transactionOutput.getSignSchedule().getScheduledTransactionId()); + return; + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.SCHEDULESIGN; + } +} diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java index 6b0d955865d..1ca8429e7b0 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java @@ -18,13 +18,21 @@ import com.google.common.base.CaseFormat; import com.google.common.collect.Range; +import com.hedera.hapi.block.stream.output.protoc.BlockHeader; import com.hedera.mirror.common.config.CommonIntegrationTest; import com.hedera.mirror.common.config.RedisTestConfiguration; import com.hedera.mirror.common.converter.EntityIdConverter; +import com.hedera.mirror.common.domain.DigestAlgorithm; import com.hedera.mirror.common.domain.entity.EntityId; +import com.hedera.mirror.common.domain.transaction.BlockFile; +import com.hedera.mirror.common.domain.transaction.BlockItem; +import com.hedera.mirror.common.domain.transaction.RecordItem; +import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.config.DateRangeCalculator; import com.hedera.mirror.importer.converter.JsonbToListConverter; +import com.hedera.mirror.importer.downloader.block.BlockFileTransformer; import com.hedera.mirror.importer.parser.record.entity.ParserContext; +import com.hederahashgraph.api.proto.java.SemanticVersion; import io.hypersistence.utils.hibernate.type.range.guava.PostgreSQLGuavaRangeType; import jakarta.annotation.Resource; import jakarta.persistence.Id; @@ -81,6 +89,9 @@ public abstract class ImporterIntegrationTest extends CommonIntegrationTest { @Resource private ImporterProperties importerProperties; + @Resource + private BlockFileTransformer blockFileTransformer; + @Getter @Value("#{environment.matchesProfiles('!v2')}") private boolean v1; @@ -145,6 +156,53 @@ protected Boolean tableExists(String name) { "select exists(select 1 from information_schema.tables where table_name = ?)", Boolean.class, name); } + protected RecordItem transformBlockItemToRecordItem(BlockItem blockItem) { + var blockFile = blockFile(List.of(blockItem)); + var blockRecordFile = blockFileTransformer.transform(blockFile); + return blockRecordFile.getItems().iterator().next(); + } + + protected BlockFile blockFile(List blockItems) { + long blockNumber = domainBuilder.number(); + byte[] bytes = domainBuilder.bytes(256); + String filename = StringUtils.leftPad(Long.toString(blockNumber), 36, "0") + ".blk.gz"; + var firstConsensusTimestamp = blockItems.isEmpty() + ? domainBuilder.protoTimestamp() + : blockItems.getFirst().transactionResult().getConsensusTimestamp(); + byte[] previousHash = domainBuilder.bytes(48); + long consensusStart = DomainUtils.timestampInNanosMax(firstConsensusTimestamp); + long consensusEnd = blockItems.isEmpty() + ? consensusStart + : DomainUtils.timestampInNanosMax( + blockItems.getLast().transactionResult().getConsensusTimestamp()); + + return BlockFile.builder() + .blockHeader(BlockHeader.newBuilder() + .setFirstTransactionConsensusTime(firstConsensusTimestamp) + .setNumber(blockNumber) + .setPreviousBlockHash(DomainUtils.fromBytes(previousHash)) + .setHapiProtoVersion(SemanticVersion.newBuilder().setMinor(57)) + .setSoftwareVersion(SemanticVersion.newBuilder().setMinor(57)) + .build()) + .bytes(bytes) + .consensusEnd(consensusEnd) + .consensusStart(consensusStart) + .count((long) blockItems.size()) + .digestAlgorithm(DigestAlgorithm.SHA_384) + .hash(DomainUtils.bytesToHex(domainBuilder.bytes(48))) + .index(blockNumber) + .items(blockItems) + .loadStart(System.currentTimeMillis()) + .name(filename) + .nodeId(domainBuilder.number()) + .previousHash(DomainUtils.bytesToHex(previousHash)) + .roundEnd(blockNumber + 1) + .roundStart(blockNumber + 1) + .size(bytes.length) + .version(7) + .build(); + } + private String getDefaultIdColumns(Class entityClass) { Stream idFields; var idClassAnnotation = AnnotationUtils.findAnnotation(entityClass, IdClass.class); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index 394ccf3aa4a..7f881dc5276 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -21,9 +21,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.protobuf.ByteString; -import com.hedera.hapi.block.stream.output.protoc.BlockHeader; import com.hedera.hapi.block.stream.output.protoc.TransactionResult; -import com.hedera.mirror.common.domain.DigestAlgorithm; import com.hedera.mirror.common.domain.transaction.BlockFile; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.RecordFile; @@ -34,9 +32,9 @@ import com.hedera.mirror.importer.parser.domain.BlockItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder.TransferType; -import com.hederahashgraph.api.proto.java.SemanticVersion; 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.TransactionRecord; import java.util.Collection; import java.util.Collections; @@ -47,6 +45,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.data.util.Version; @RequiredArgsConstructor @@ -154,6 +153,111 @@ void emptyBlockFile() { assertRecordFile(recordFile, blockFile, items -> {}); } + @Test + void scheduleCreate() { + // given + var expectedRecordItem = recordItemBuilder + .scheduleCreate() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .build(); + var blockItem = blockItemBuilder.scheduleCreate(expectedRecordItem).build(); + var expectedScheduleId = blockItem + .stateChanges() + .getFirst() + .getStateChangesList() + .getFirst() + .getMapUpdate() + .getKey() + .getScheduleIdKey() + .getScheduleNum(); + var blockFile = blockFile(List.of(blockItem)); + + // 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(expectedScheduleId, transactionRecord -> transactionRecord + .getReceipt() + .getScheduleID() + .getScheduleNum()); + }); + } + + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleDelete(boolean deleted) { + // given + var scheduleId = recordItemBuilder.scheduleId(); + var builder = recordItemBuilder.scheduleDelete(scheduleId).recordItem(r -> r.hapiVersion(HAPI_VERSION)); + if (deleted) { + builder.receipt(r -> r.setScheduleID(scheduleId)); + } + var expectedRecordItem = builder.build(); + + var blockItem = + blockItemBuilder.scheduleDelete(expectedRecordItem, deleted).build(); + var blockFile = blockFile(List.of(blockItem)); + + // when + var recordFile = blockFileTransformer.transform(blockFile); + + // then + assertRecordFile(recordFile, blockFile, items -> { + assertThat(items) + .hasSize(1) + .first() + .satisfies(item -> assertRecordItem(item, expectedRecordItem)) + .satisfies(item -> { + if (deleted) { + assertThat(item.getTransactionRecord() + .getReceipt() + .getScheduleID() + .getScheduleNum()) + .isEqualTo(scheduleId.getScheduleNum()); + } + }); + }); + } + + @Test + void scheduleSign() { + // given + var accountId = recordItemBuilder.accountId(); + var expectedRecordItem = recordItemBuilder + .scheduleSign() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setScheduledTransactionID( + TransactionID.newBuilder().setAccountID(accountId).build())) + .build(); + var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); + var expectedScheduleId = + blockItem.transactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); + var blockFile = blockFile(List.of(blockItem)); + + // 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( + expectedScheduleId, + transactionRecord -> transactionRecord.getReceipt().getScheduledTransactionID()); + }); + } + @Test void unknownTransform() { // given @@ -224,8 +328,10 @@ private void assertRecordItem(RecordItem recordItem, RecordItem expectedRecordIt // contains memoBytes or a memo String value. "transactionRecord.memo_", "transactionRecord.receipt_.memoizedIsInitialized", + "transactionRecord.receipt_.scheduleID_.memoizedHashCode", + "transactionRecord.receipt_.scheduleID_.memoizedIsInitialized", + "transactionRecord.receipt_.scheduleID_.memoizedSize", "transactionRecord.assessedCustomFees_", - "transactionRecord.scheduleRef_", "transactionRecord.parentConsensusTimestamp_", // Record file builder transaction hash is not generated based on transaction bytes, so these // will not match @@ -245,47 +351,4 @@ private ByteString getExpectedTransactionHash(RecordItem recordItem) { return ByteString.copyFrom( digest.digest(DomainUtils.toBytes(recordItem.getTransaction().getSignedTransactionBytes()))); } - - private BlockFile blockFile(List blockItems) { - long blockNumber = domainBuilder.number(); - byte[] bytes = domainBuilder.bytes(256); - String filename = StringUtils.leftPad(Long.toString(blockNumber), 36, "0") + ".blk.gz"; - var firstConsensusTimestamp = blockItems.isEmpty() - ? domainBuilder.protoTimestamp() - : blockItems.getFirst().transactionResult().getConsensusTimestamp(); - byte[] previousHash = domainBuilder.bytes(48); - long consensusStart = DomainUtils.timestampInNanosMax(firstConsensusTimestamp); - long consensusEnd = blockItems.isEmpty() - ? consensusStart - : DomainUtils.timestampInNanosMax( - blockItems.getLast().transactionResult().getConsensusTimestamp()); - - return BlockFile.builder() - .blockHeader(BlockHeader.newBuilder() - .setFirstTransactionConsensusTime(firstConsensusTimestamp) - .setNumber(blockNumber) - .setPreviousBlockHash(DomainUtils.fromBytes(previousHash)) - .setHapiProtoVersion(SemanticVersion.newBuilder().setMinor(HAPI_VERSION_MINOR)) - .setSoftwareVersion(SemanticVersion.newBuilder() - .setMinor(HAPI_VERSION_MINOR) - .setPatch(1)) - .build()) - .bytes(bytes) - .consensusEnd(consensusEnd) - .consensusStart(consensusStart) - .count((long) blockItems.size()) - .digestAlgorithm(DigestAlgorithm.SHA_384) - .hash(DomainUtils.bytesToHex(domainBuilder.bytes(48))) - .index(blockNumber) - .items(blockItems) - .loadStart(System.currentTimeMillis()) - .name(filename) - .nodeId(domainBuilder.number()) - .previousHash(DomainUtils.bytesToHex(previousHash)) - .roundEnd(blockNumber + 1) - .roundStart(blockNumber + 1) - .size(bytes.length) - .version(7) - .build(); - } } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index e67b67833d6..33b7a5d02d9 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -16,8 +16,17 @@ package com.hedera.mirror.importer.parser.domain; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; + import com.hedera.hapi.block.stream.output.protoc.CallContractOutput; +import com.hedera.hapi.block.stream.output.protoc.CreateScheduleOutput; import com.hedera.hapi.block.stream.output.protoc.CryptoTransferOutput; +import com.hedera.hapi.block.stream.output.protoc.MapChangeKey; +import com.hedera.hapi.block.stream.output.protoc.MapChangeValue; +import com.hedera.hapi.block.stream.output.protoc.MapDeleteChange; +import com.hedera.hapi.block.stream.output.protoc.MapUpdateChange; +import com.hedera.hapi.block.stream.output.protoc.SignScheduleOutput; +import com.hedera.hapi.block.stream.output.protoc.StateChange; import com.hedera.hapi.block.stream.output.protoc.StateChanges; import com.hedera.hapi.block.stream.output.protoc.TransactionOutput; import com.hedera.hapi.block.stream.output.protoc.TransactionResult; @@ -25,6 +34,7 @@ import com.hedera.mirror.common.domain.transaction.RecordItem; import com.hedera.mirror.importer.util.Utility; import com.hederahashgraph.api.proto.java.AssessedCustomFee; +import com.hederahashgraph.api.proto.java.Schedule; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionRecord; @@ -50,11 +60,6 @@ public BlockItemBuilder.Builder cryptoTransfer() { } public BlockItemBuilder.Builder cryptoTransfer(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - var contractCallTransactionOutput = TransactionOutput.newBuilder() .setContractCall(CallContractOutput.newBuilder() .setContractCallResult(recordItemBuilder.contractFunctionResult()) @@ -68,24 +73,104 @@ public BlockItemBuilder.Builder cryptoTransfer(RecordItem recordItem) { return new BlockItemBuilder.Builder( recordItem.getTransaction(), - transactionResult, + transactionResult(recordItem), List.of(contractCallTransactionOutput, cryptoTransferTransactionOutput), Collections.emptyList()); } + public BlockItemBuilder.Builder scheduleCreate() { + var recordItem = recordItemBuilder.scheduleCreate().build(); + return scheduleCreate(recordItem); + } + + public BlockItemBuilder.Builder scheduleCreate(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var scheduleId = transactionRecord.getReceipt().getScheduleID(); + var transactionId = transactionRecord.getReceipt().getScheduledTransactionID(); + var transactionOutput = TransactionOutput.newBuilder() + .setCreateSchedule(CreateScheduleOutput.newBuilder() + .setScheduleId(scheduleId) + .setScheduledTransactionId(transactionId) + .build()) + .build(); + + var stateChange = StateChange.newBuilder() + .setStateId(STATE_ID_SCHEDULES_BY_ID.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setScheduleIdKey(scheduleId))) + .build(); + var stateChanges = + StateChanges.newBuilder().addStateChanges(stateChange).build(); + + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + List.of(transactionOutput), + List.of(stateChanges)); + } + + public BlockItemBuilder.Builder scheduleDelete() { + var recordItem = recordItemBuilder.scheduleDelete().build(); + return scheduleDelete(recordItem, false); + } + + public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem, boolean deleted) { + var scheduleId = recordItem.getTransactionBody().getScheduleDelete().getScheduleID(); + var stateChangeDelete = StateChange.newBuilder() + .setMapDelete(MapDeleteChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setScheduleIdKey(scheduleId))) + .build(); + var stateChangeUpdate = StateChange.newBuilder() + .setStateId(STATE_ID_SCHEDULES_BY_ID.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setValue(MapChangeValue.newBuilder() + .setScheduleValue(Schedule.newBuilder() + .setDeleted(deleted) + .setScheduleId(scheduleId) + .build()) + .build())) + .build(); + + var stateChanges = StateChanges.newBuilder() + .addStateChanges(stateChangeDelete) + .addStateChanges(stateChangeUpdate) + .build(); + + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(stateChanges)); + } + + public BlockItemBuilder.Builder scheduleSign() { + var recordItem = recordItemBuilder.scheduleSign().build(); + return scheduleSign(recordItem); + } + + public BlockItemBuilder.Builder scheduleSign(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var transactionId = transactionRecord.getReceipt().getScheduledTransactionID(); + var transactionOutput = TransactionOutput.newBuilder() + .setSignSchedule(SignScheduleOutput.newBuilder() + .setScheduledTransactionId(transactionId) + .build()) + .build(); + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + List.of(transactionOutput), + Collections.emptyList()); + } + public BlockItemBuilder.Builder unknown() { var recordItem = recordItemBuilder.unknown().build(); return unknown(recordItem); } public BlockItemBuilder.Builder unknown(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult, List.of(), Collections.emptyList()); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } private AssessedCustomFee.Builder assessedCustomFees() { @@ -96,14 +181,30 @@ private AssessedCustomFee.Builder assessedCustomFees() { .setTokenId(recordItemBuilder.tokenId()); } + private Timestamp timestamp(long consensusTimestamp) { + var instant = Instant.ofEpochSecond(0, consensusTimestamp); + return Utility.instantToTimestamp(instant); + } + + private TransactionResult transactionResult(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var timestamp = timestamp(recordItem.getConsensusTimestamp()); + return transactionResult(transactionRecord, timestamp).build(); + } + private TransactionResult.Builder transactionResult( TransactionRecord transactionRecord, Timestamp consensusTimestamp) { - return TransactionResult.newBuilder() - .addAllPaidStakingRewards(transactionRecord.getPaidStakingRewardsList()) + var builder = TransactionResult.newBuilder(); + if (transactionRecord.hasParentConsensusTimestamp()) { + builder.setParentConsensusTimestamp(transactionRecord.getParentConsensusTimestamp()); + } + if (transactionRecord.hasScheduleRef()) { + builder.setScheduleRef(transactionRecord.getScheduleRef()); + } + + return builder.addAllPaidStakingRewards(transactionRecord.getPaidStakingRewardsList()) .addAllTokenTransferLists(transactionRecord.getTokenTransferListsList()) .setConsensusTimestamp(consensusTimestamp) - .setParentConsensusTimestamp(transactionRecord.getParentConsensusTimestamp()) - .setScheduleRef(transactionRecord.getScheduleRef()) .setTransferList(transactionRecord.getTransferList()) .setTransactionFeeCharged(transactionRecord.getTransactionFee()) .setStatus(transactionRecord.getReceipt().getStatus()); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index 9b50e178b65..31f4cfcbe4a 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -785,7 +785,11 @@ public Builder scheduleCreate() { } public Builder scheduleDelete() { - var builder = ScheduleDeleteTransactionBody.newBuilder().setScheduleID(scheduleId()); + return scheduleDelete(scheduleId()); + } + + public Builder scheduleDelete(ScheduleID scheduleId) { + var builder = ScheduleDeleteTransactionBody.newBuilder().setScheduleID(scheduleId); return new Builder<>(TransactionType.SCHEDULEDELETE, builder); } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java index 6ce082a3822..12f737cf16e 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java @@ -31,6 +31,7 @@ import com.hedera.mirror.common.domain.transaction.TransactionSignature; import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.TestUtils; +import com.hedera.mirror.importer.parser.domain.BlockItemBuilder; import com.hedera.mirror.importer.repository.ScheduleRepository; import com.hedera.mirror.importer.repository.TransactionRepository; import com.hedera.mirror.importer.repository.TransactionSignatureRepository; @@ -57,6 +58,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; class EntityRecordItemListenerScheduleTest extends AbstractEntityRecordItemListenerTest { @@ -73,6 +75,8 @@ class EntityRecordItemListenerScheduleTest extends AbstractEntityRecordItemListe private static final Key SCHEDULE_REF_KEY = keyFromString(KEY); private static final long SIGN_TIMESTAMP = 10L; + private final BlockItemBuilder blockItemBuilder = new BlockItemBuilder(); + @Resource protected ScheduleRepository scheduleRepository; @@ -86,9 +90,12 @@ class EntityRecordItemListenerScheduleTest extends AbstractEntityRecordItemListe private static Stream provideScheduleCreatePayer() { return Stream.of( - Arguments.of(null, PAYER, "no payer expect same as creator"), - Arguments.of(PAYER, PAYER, "payer set to creator"), - Arguments.of(PAYER2, PAYER2, "payer different than creator")); + Arguments.of(null, PAYER, "no payer expect same as creator", false), + Arguments.of(PAYER, PAYER, "payer set to creator", false), + Arguments.of(PAYER2, PAYER2, "payer different than creator", false), + Arguments.of(null, PAYER, "no payer expect same as creator", true), + Arguments.of(PAYER, PAYER, "payer set to creator", true), + Arguments.of(PAYER2, PAYER2, "payer different than creator", true)); } @BeforeEach @@ -99,8 +106,8 @@ void before() { @ParameterizedTest(name = "{2}") @MethodSource("provideScheduleCreatePayer") - void scheduleCreate(AccountID payer, AccountID expectedPayer, String name) { - insertScheduleCreateTransaction(CREATE_TIMESTAMP, payer, SCHEDULE_ID); + void scheduleCreate(AccountID payer, AccountID expectedPayer, String name, boolean useBlockTransformer) { + insertScheduleCreateTransaction(CREATE_TIMESTAMP, payer, SCHEDULE_ID, useBlockTransformer); // verify entity count Entity expectedEntity = createEntity( @@ -191,14 +198,15 @@ void scheduleCreateLongTermScheduledTransaction() { assertTransactionInRepository(timestamp, false, SUCCESS); } - @Test - void scheduleDelete() { + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleDelete(boolean useBlockTransformer) { // given - insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID); + insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID, useBlockTransformer); // when long deletedTimestamp = CREATE_TIMESTAMP + 10; - insertScheduleDeleteTransaction(deletedTimestamp, SCHEDULE_ID); + insertScheduleDeleteTransaction(deletedTimestamp, SCHEDULE_ID, useBlockTransformer); // then Entity expected = createEntity( @@ -224,13 +232,14 @@ void scheduleDelete() { assertTransactionInRepository(deletedTimestamp, false, SUCCESS); } - @Test - void scheduleSign() { - insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID); + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleSign(boolean useBlockTransformer) { + insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID, useBlockTransformer); // sign SignatureMap signatureMap = getSigMap(3, true); - insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID, useBlockTransformer); // verify entity count assertEquals(1, entityRepository.count()); @@ -248,13 +257,14 @@ void scheduleSign() { assertTransactionInRepository(SIGN_TIMESTAMP, false, SUCCESS); } - @Test - void scheduleSignTwoBatches() { - insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID); + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleSignTwoBatches(boolean useBlockTransformer) { + insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID, useBlockTransformer); // first sign SignatureMap firstSignatureMap = getSigMap(2, true); - insertScheduleSign(SIGN_TIMESTAMP, firstSignatureMap, SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, firstSignatureMap, SCHEDULE_ID, useBlockTransformer); // verify schedule signatures List expectedTransactionSignatureList = new ArrayList<>(defaultSignatureList); @@ -265,7 +275,7 @@ void scheduleSignTwoBatches() { // second sign long timestamp = SIGN_TIMESTAMP + 10; SignatureMap secondSignatureMap = getSigMap(3, true); - insertScheduleSign(timestamp, secondSignatureMap, SCHEDULE_ID); + insertScheduleSign(timestamp, secondSignatureMap, SCHEDULE_ID, useBlockTransformer); expectedTransactionSignatureList.addAll(toTransactionSignatureList(timestamp, SCHEDULE_ID, secondSignatureMap)); assertTransactionSignatureInRepository(expectedTransactionSignatureList); @@ -294,23 +304,25 @@ void scheduleSignTwoBatches() { assertTransactionInRepository(SIGN_TIMESTAMP, false, SUCCESS); } - @Test - void scheduleSignDuplicateEd25519Signatures() { + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleSignDuplicateEd25519Signatures(boolean useBlockTransformer) { SignatureMap signatureMap = getSigMap(3, true); SignaturePair first = signatureMap.getSigPair(0); SignaturePair third = signatureMap.getSigPair(2); SignatureMap signatureMapWithDuplicate = signatureMap.toBuilder().addSigPair(first).addSigPair(third).build(); - insertScheduleSign(SIGN_TIMESTAMP, signatureMapWithDuplicate, SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, signatureMapWithDuplicate, SCHEDULE_ID, useBlockTransformer); // verify lack of schedule data and transaction assertTransactionSignatureInRepository(toTransactionSignatureList(SIGN_TIMESTAMP, SCHEDULE_ID, signatureMap)); assertThat(transactionRepository.count()).isEqualTo(1); } - @Test - void unknownSignatureType() { + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void unknownSignatureType(boolean useBlockTransformer) { int unknownType = 999; ByteString sig = ByteString.copyFromUtf8("123"); UnknownFieldSet.Field unknownField = @@ -344,7 +356,7 @@ void unknownSignatureType() { .build()) .build()); - insertScheduleSign(SIGN_TIMESTAMP, signatureMap.build(), SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, signatureMap.build(), SCHEDULE_ID, useBlockTransformer); // verify assertThat(transactionRepository.count()).isEqualTo(1); @@ -365,12 +377,13 @@ void unknownSignatureType() { unknownType); } - @Test - void unsupportedSignature() { + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void unsupportedSignature(boolean useBlockTransformer) { SignatureMap signatureMap = SignatureMap.newBuilder() .addSigPair(SignaturePair.newBuilder().build()) .build(); - insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID, useBlockTransformer); // verify assertThat(transactionRepository.count()).isOne(); @@ -379,26 +392,28 @@ void unsupportedSignature() { assertTransactionInRepository(SIGN_TIMESTAMP, false, SUCCESS); } - @Test - void scheduleExecuteOnSuccess() { - scheduleExecute(SUCCESS); + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleExecuteOnSuccess(boolean useBlockTransformer) { + scheduleExecute(SUCCESS, useBlockTransformer); } - @Test - void scheduleExecuteOnFailure() { - scheduleExecute(ResponseCodeEnum.INVALID_CHUNK_TRANSACTION_ID); + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void scheduleExecuteOnFailure(boolean useBlockTransformer) { + scheduleExecute(ResponseCodeEnum.INVALID_CHUNK_TRANSACTION_ID, useBlockTransformer); } private ByteString byteString(int length) { return ByteString.copyFrom(domainBuilder.bytes(length)); } - void scheduleExecute(ResponseCodeEnum responseCodeEnum) { - insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID); + void scheduleExecute(ResponseCodeEnum responseCodeEnum, boolean useBlockTransformer) { + insertScheduleCreateTransaction(CREATE_TIMESTAMP, null, SCHEDULE_ID, useBlockTransformer); // sign SignatureMap signatureMap = getSigMap(3, true); - insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID); + insertScheduleSign(SIGN_TIMESTAMP, signatureMap, SCHEDULE_ID, useBlockTransformer); // scheduled transaction insertScheduledTransaction(EXECUTE_TIMESTAMP, SCHEDULE_ID, responseCodeEnum); @@ -505,7 +520,8 @@ private TransactionRecord createTransactionRecord( responseCode.getNumber()); } - private void insertScheduleCreateTransaction(long createdTimestamp, AccountID payer, ScheduleID scheduleID) { + private void insertScheduleCreateTransaction( + long createdTimestamp, AccountID payer, ScheduleID scheduleID, boolean useBlockTransformer) { Transaction createTransaction = scheduleCreateTransaction(payer); TransactionBody createTransactionBody = getTransactionBody(createTransaction); var createTransactionRecord = @@ -515,21 +531,35 @@ private void insertScheduleCreateTransaction(long createdTimestamp, AccountID pa .transactionRecord(createTransactionRecord) .transaction(createTransaction) .build(); + + if (useBlockTransformer) { + var blockItem = blockItemBuilder.scheduleCreate(recordItem).build(); + recordItem = transformBlockItemToRecordItem(blockItem); + } + parseRecordItemAndCommit(recordItem); } - private void insertScheduleDeleteTransaction(long timestamp, ScheduleID scheduleId) { + private void insertScheduleDeleteTransaction(long timestamp, ScheduleID scheduleId, boolean useBlockTransformer) { var transaction = scheduleDeleteTransaction(scheduleId); var transactionBody = getTransactionBody(transaction); var transactionRecord = createTransactionRecord(timestamp, scheduleId, transactionBody, SUCCESS, false); - parseRecordItemAndCommit(RecordItem.builder() + var recordItem = RecordItem.builder() .transactionRecord(transactionRecord) .transaction(transaction) - .build()); + .build(); + + if (useBlockTransformer) { + var blockItem = blockItemBuilder.scheduleDelete(recordItem, false).build(); + recordItem = transformBlockItemToRecordItem(blockItem); + } + + parseRecordItemAndCommit(recordItem); } - private void insertScheduleSign(long signTimestamp, SignatureMap signatureMap, ScheduleID scheduleID) { + private void insertScheduleSign( + long signTimestamp, SignatureMap signatureMap, ScheduleID scheduleID, boolean useBlockTransformer) { Transaction signTransaction = scheduleSignTransaction(scheduleID, signatureMap); TransactionBody signTransactionBody = getTransactionBody(signTransaction); var signTransactionRecord = @@ -539,6 +569,12 @@ private void insertScheduleSign(long signTimestamp, SignatureMap signatureMap, S .transaction(signTransaction) .transactionRecord(signTransactionRecord) .build(); + + if (useBlockTransformer) { + var blockItem = blockItemBuilder.scheduleSign(recordItem).build(); + recordItem = transformBlockItemToRecordItem(blockItem); + } + parseRecordItemAndCommit(recordItem); } From 0d731532c34641e94df3ea3e193b30dd6dacf11b Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Fri, 31 Jan 2025 16:16:28 -0500 Subject: [PATCH 02/18] Address feedback, add tests Signed-off-by: Edwin Greene --- .../common/domain/transaction/BlockItem.java | 56 ++++++- .../block/BlockFileTransformer.java | 2 +- .../AbstractBlockItemTransformer.java | 4 +- .../BlockItemTransformerFactory.java | 2 +- .../CryptoTransferTransformer.java | 2 +- .../ScheduleCreateTransformer.java | 21 ++- .../ScheduleDeleteTransformer.java | 57 ------- .../transformer/ScheduleSignTransformer.java | 6 +- .../reader/block/ProtoBlockFileReader.java | 3 + .../importer/ImporterIntegrationTest.java | 58 ------- .../block/BlockFileTransformerTest.java | 150 +++++++++++------- .../parser/domain/BlockFileBuilder.java | 76 +++++++++ .../parser/domain/BlockItemBuilder.java | 5 +- .../parser/domain/RecordItemBuilder.java | 6 +- .../EntityRecordItemListenerScheduleTest.java | 20 ++- 15 files changed, 268 insertions(+), 200 deletions(-) delete mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java create mode 100644 hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockFileBuilder.java diff --git a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/transaction/BlockItem.java b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/transaction/BlockItem.java index 3f4b622fffb..3fe35035a16 100644 --- a/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/transaction/BlockItem.java +++ b/hedera-mirror-common/src/main/java/com/hedera/mirror/common/domain/transaction/BlockItem.java @@ -20,14 +20,56 @@ import com.hedera.hapi.block.stream.output.protoc.TransactionOutput; import com.hedera.hapi.block.stream.output.protoc.TransactionResult; import com.hedera.mirror.common.domain.StreamItem; +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.Transaction; import java.util.List; import lombok.Builder; +import lombok.Data; -@Builder(toBuilder = true) -public record BlockItem( - Transaction transaction, - TransactionResult transactionResult, - List transactionOutput, - List stateChanges) - implements StreamItem {} +@Builder(buildMethodName = "buildInternal") +@Data +public class BlockItem implements StreamItem { + + public final Transaction transaction; + public final TransactionResult transactionResult; + public final List transactionOutput; + public final List stateChanges; + + private BlockItem parent; + private BlockItem previous; + private boolean successful; + + public static class BlockItemBuilder { + public BlockItem build() { + this.parent = parseParent(); + successful = parseSuccess(this.parent); + return buildInternal(); + } + + private BlockItem parseParent() { + // set parent, parent-child items are assured to exist in sequential order of [Parent, Child1,..., ChildN] + if (transactionResult.hasParentConsensusTimestamp() && previous != null) { + var parentTimestamp = transactionResult.getParentConsensusTimestamp(); + if (parentTimestamp.equals(previous.transactionResult.getConsensusTimestamp())) { + return previous; + } else if (previous.parent != null + && parentTimestamp.equals(previous.parent.transactionResult.getConsensusTimestamp())) { + // check older siblings parent, if child count is > 1 this prevents having to search to parent + return previous.parent; + } + } + return null; + } + + private boolean parseSuccess(BlockItem parent) { + if (parent != null && !parent.isSuccessful()) { + return false; + } + + var status = transactionResult.getStatus(); + return status == ResponseCodeEnum.FEE_SCHEDULE_FILE_PART_UPLOADED + || status == ResponseCodeEnum.SUCCESS + || status == ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION; + } + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformer.java index 9c56c2e29ae..eae368625b0 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformer.java @@ -85,7 +85,7 @@ private List getRecordItems(Collection blockItems, Versio var recordItem = RecordItem.builder() .hapiVersion(hapiVersion) .previous(previousItem) - .transaction(blockItem.transaction()) + .transaction(blockItem.getTransaction()) .transactionIndex(recordItems.size()) .transactionRecord(blockItemTransformerFactory.getTransactionRecord(blockItem)) .build(); diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java index 2ced117154c..e02fe8f0100 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java @@ -31,7 +31,7 @@ abstract class AbstractBlockItemTransformer implements BlockItemTransformer { private static final MessageDigest DIGEST = createSha384Digest(); public TransactionRecord getTransactionRecord(BlockItem blockItem, TransactionBody transactionBody) { - var transactionResult = blockItem.transactionResult(); + var transactionResult = blockItem.getTransactionResult(); var receiptBuilder = TransactionReceipt.newBuilder().setStatus(transactionResult.getStatus()); var transactionRecordBuilder = TransactionRecord.newBuilder() .addAllAutomaticTokenAssociations(transactionResult.getAutomaticTokenAssociationsList()) @@ -42,7 +42,7 @@ public TransactionRecord getTransactionRecord(BlockItem blockItem, TransactionBo .setReceipt(receiptBuilder) .setTransactionFee(transactionResult.getTransactionFeeCharged()) .setTransactionHash( - calculateTransactionHash(blockItem.transaction().getSignedTransactionBytes())) + calculateTransactionHash(blockItem.getTransaction().getSignedTransactionBytes())) .setTransactionID(transactionBody.getTransactionID()) .setTransferList(transactionResult.getTransferList()); diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/BlockItemTransformerFactory.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/BlockItemTransformerFactory.java index 4cb9cf6a765..8d8adca6714 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/BlockItemTransformerFactory.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/BlockItemTransformerFactory.java @@ -43,7 +43,7 @@ public class BlockItemTransformerFactory { } public TransactionRecord getTransactionRecord(BlockItem blockItem) { - var transactionBody = parse(blockItem.transaction().getSignedTransactionBytes()); + var transactionBody = parse(blockItem.getTransaction().getSignedTransactionBytes()); var blockItemTransformer = get(transactionBody); // pass transactionBody for performance return blockItemTransformer.getTransactionRecord(blockItem, transactionBody); diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java index b6a1464d61f..1f70166808b 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java @@ -26,7 +26,7 @@ final class CryptoTransferTransformer extends AbstractBlockItemTransformer { @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - for (var transactionOutput : blockItem.transactionOutput()) { + for (var transactionOutput : blockItem.getTransactionOutput()) { if (transactionOutput.hasCryptoTransfer()) { var cryptoTransferOutput = transactionOutput.getCryptoTransfer(); var assessedCustomFees = cryptoTransferOutput.getAssessedCustomFeesList(); diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java index 81ffb82802d..bccbb4a925b 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java @@ -17,7 +17,6 @@ package com.hedera.mirror.importer.downloader.block.transformer; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; @@ -29,21 +28,33 @@ final class ScheduleCreateTransformer extends AbstractBlockItemTransformer { @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (blockItem.transactionResult().getStatus() != SUCCESS) { + if (!blockItem.isSuccessful()) { return; } - for (var stateChanges : blockItem.stateChanges()) { + var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); + outer: + for (var stateChanges : blockItem.getStateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { if (stateChange.getStateId() == STATE_ID_SCHEDULES_BY_ID.getNumber() && stateChange.hasMapUpdate()) { var key = stateChange.getMapUpdate().getKey(); if (key.hasScheduleIdKey()) { - transactionRecordBuilder.getReceiptBuilder().setScheduleID(key.getScheduleIdKey()); - return; + receiptBuilder.setScheduleID(key.getScheduleIdKey()); + break outer; } } } } + + for (var transactionOutput : blockItem.getTransactionOutput()) { + if (transactionOutput.hasCreateSchedule()) { + var output = transactionOutput.getCreateSchedule(); + if (output.hasScheduledTransactionId()) { + receiptBuilder.setScheduledTransactionID(output.getScheduledTransactionId()); + return; + } + } + } } @Override diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java deleted file mode 100644 index b9c484e48f2..00000000000 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleDeleteTransformer.java +++ /dev/null @@ -1,57 +0,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. - */ - -package com.hedera.mirror.importer.downloader.block.transformer; - -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; - -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 ScheduleDeleteTransformer extends AbstractBlockItemTransformer { - - @SuppressWarnings("java:S3776") - @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (blockItem.transactionResult().getStatus() != SUCCESS) { - return; - } - - for (var stateChanges : blockItem.stateChanges()) { - for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.getStateId() == STATE_ID_SCHEDULES_BY_ID.getNumber() && stateChange.hasMapUpdate()) { - var value = stateChange.getMapUpdate().getValue(); - if (value.hasScheduleValue()) { - var schedule = value.getScheduleValue(); - if (schedule.getDeleted()) { - transactionRecordBuilder.getReceiptBuilder().setScheduleID(schedule.getScheduleId()); - return; - } - } - } - } - } - } - - @Override - public TransactionType getType() { - return TransactionType.SCHEDULEDELETE; - } -} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java index 21fdcc79172..c6eb911a6e6 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java @@ -16,8 +16,6 @@ package com.hedera.mirror.importer.downloader.block.transformer; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; - import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; import com.hederahashgraph.api.proto.java.TransactionRecord; @@ -28,11 +26,11 @@ final class ScheduleSignTransformer extends AbstractBlockItemTransformer { @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (blockItem.transactionResult().getStatus() != SUCCESS) { + if (!blockItem.isSuccessful()) { return; } - for (var transactionOutput : blockItem.transactionOutput()) { + for (var transactionOutput : blockItem.getTransactionOutput()) { if (transactionOutput.hasSignSchedule() && transactionOutput.getSignSchedule().hasScheduledTransactionId()) { transactionRecordBuilder diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/reader/block/ProtoBlockFileReader.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/reader/block/ProtoBlockFileReader.java index 752d9d20d06..2ed6a870069 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/reader/block/ProtoBlockFileReader.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/reader/block/ProtoBlockFileReader.java @@ -140,6 +140,7 @@ private void readEvents(ReaderContext context) { private void readEventTransactions(ReaderContext context) { BlockItem protoBlockItem; + com.hedera.mirror.common.domain.transaction.BlockItem previous = null; while ((protoBlockItem = context.readBlockItemFor(EVENT_TRANSACTION)) != null) { try { var eventTransaction = protoBlockItem.getEventTransaction(); @@ -171,10 +172,12 @@ private void readEventTransactions(ReaderContext context) { .transactionResult(transactionResult) .transactionOutput(Collections.unmodifiableList(transactionOutputs)) .stateChanges(Collections.unmodifiableList(stateChangesList)) + .previous(previous) .build(); context.getBlockFile() .item(blockItem) .onNewTransaction(getTransactionConsensusTimestamp(transactionResult)); + previous = blockItem; } } catch (InvalidProtocolBufferException e) { throw new InvalidStreamFileException( diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java index 1ca8429e7b0..6b0d955865d 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/ImporterIntegrationTest.java @@ -18,21 +18,13 @@ import com.google.common.base.CaseFormat; import com.google.common.collect.Range; -import com.hedera.hapi.block.stream.output.protoc.BlockHeader; import com.hedera.mirror.common.config.CommonIntegrationTest; import com.hedera.mirror.common.config.RedisTestConfiguration; import com.hedera.mirror.common.converter.EntityIdConverter; -import com.hedera.mirror.common.domain.DigestAlgorithm; import com.hedera.mirror.common.domain.entity.EntityId; -import com.hedera.mirror.common.domain.transaction.BlockFile; -import com.hedera.mirror.common.domain.transaction.BlockItem; -import com.hedera.mirror.common.domain.transaction.RecordItem; -import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.config.DateRangeCalculator; import com.hedera.mirror.importer.converter.JsonbToListConverter; -import com.hedera.mirror.importer.downloader.block.BlockFileTransformer; import com.hedera.mirror.importer.parser.record.entity.ParserContext; -import com.hederahashgraph.api.proto.java.SemanticVersion; import io.hypersistence.utils.hibernate.type.range.guava.PostgreSQLGuavaRangeType; import jakarta.annotation.Resource; import jakarta.persistence.Id; @@ -89,9 +81,6 @@ public abstract class ImporterIntegrationTest extends CommonIntegrationTest { @Resource private ImporterProperties importerProperties; - @Resource - private BlockFileTransformer blockFileTransformer; - @Getter @Value("#{environment.matchesProfiles('!v2')}") private boolean v1; @@ -156,53 +145,6 @@ protected Boolean tableExists(String name) { "select exists(select 1 from information_schema.tables where table_name = ?)", Boolean.class, name); } - protected RecordItem transformBlockItemToRecordItem(BlockItem blockItem) { - var blockFile = blockFile(List.of(blockItem)); - var blockRecordFile = blockFileTransformer.transform(blockFile); - return blockRecordFile.getItems().iterator().next(); - } - - protected BlockFile blockFile(List blockItems) { - long blockNumber = domainBuilder.number(); - byte[] bytes = domainBuilder.bytes(256); - String filename = StringUtils.leftPad(Long.toString(blockNumber), 36, "0") + ".blk.gz"; - var firstConsensusTimestamp = blockItems.isEmpty() - ? domainBuilder.protoTimestamp() - : blockItems.getFirst().transactionResult().getConsensusTimestamp(); - byte[] previousHash = domainBuilder.bytes(48); - long consensusStart = DomainUtils.timestampInNanosMax(firstConsensusTimestamp); - long consensusEnd = blockItems.isEmpty() - ? consensusStart - : DomainUtils.timestampInNanosMax( - blockItems.getLast().transactionResult().getConsensusTimestamp()); - - return BlockFile.builder() - .blockHeader(BlockHeader.newBuilder() - .setFirstTransactionConsensusTime(firstConsensusTimestamp) - .setNumber(blockNumber) - .setPreviousBlockHash(DomainUtils.fromBytes(previousHash)) - .setHapiProtoVersion(SemanticVersion.newBuilder().setMinor(57)) - .setSoftwareVersion(SemanticVersion.newBuilder().setMinor(57)) - .build()) - .bytes(bytes) - .consensusEnd(consensusEnd) - .consensusStart(consensusStart) - .count((long) blockItems.size()) - .digestAlgorithm(DigestAlgorithm.SHA_384) - .hash(DomainUtils.bytesToHex(domainBuilder.bytes(48))) - .index(blockNumber) - .items(blockItems) - .loadStart(System.currentTimeMillis()) - .name(filename) - .nodeId(domainBuilder.number()) - .previousHash(DomainUtils.bytesToHex(previousHash)) - .roundEnd(blockNumber + 1) - .roundStart(blockNumber + 1) - .size(bytes.length) - .version(7) - .build(); - } - private String getDefaultIdColumns(Class entityClass) { Stream idFields; var idClassAnnotation = AnnotationUtils.findAnnotation(entityClass, IdClass.class); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index 7f881dc5276..3681feaddb9 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -29,9 +29,12 @@ import com.hedera.mirror.common.exception.ProtobufException; import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.ImporterIntegrationTest; +import com.hedera.mirror.importer.parser.domain.BlockFileBuilder; import com.hedera.mirror.importer.parser.domain.BlockItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder.TransferType; +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import com.hederahashgraph.api.proto.java.ScheduleID; import com.hederahashgraph.api.proto.java.SignedTransaction; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionID; @@ -45,7 +48,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.ValueSource; import org.springframework.data.util.Version; @RequiredArgsConstructor @@ -54,7 +56,8 @@ class BlockFileTransformerTest extends ImporterIntegrationTest { private static final int HAPI_VERSION_MINOR = 57; private static final Version HAPI_VERSION = new Version(0, HAPI_VERSION_MINOR); - private final BlockItemBuilder blockItemBuilder = new BlockItemBuilder(); + private final BlockFileBuilder blockFileBuilder; + private final BlockItemBuilder blockItemBuilder; private final BlockFileTransformer blockFileTransformer; private final RecordItemBuilder recordItemBuilder = new RecordItemBuilder(); @@ -76,11 +79,11 @@ void cryptoTransfer(TransferType transferType) { var blockItem1 = blockItemBuilder.cryptoTransfer(expectedRecordItem).build(); var expectedFees = - blockItem1.transactionOutput().get(1).getCryptoTransfer().getAssessedCustomFeesList(); + blockItem1.getTransactionOutput().get(1).getCryptoTransfer().getAssessedCustomFeesList(); var blockItem2 = blockItemBuilder.cryptoTransfer(expectedRecordItem2).build(); var expectedFees2 = - blockItem2.transactionOutput().get(1).getCryptoTransfer().getAssessedCustomFeesList(); - var blockFile = blockFile(List.of(blockItem1, blockItem2)); + blockItem2.getTransactionOutput().get(1).getCryptoTransfer().getAssessedCustomFeesList(); + var blockFile = blockFileBuilder.items(List.of(blockItem1, blockItem2)).build(); // when var recordFile = blockFileTransformer.transform(blockFile); @@ -108,17 +111,18 @@ void cryptoTransfer(TransferType transferType) { @Test void corruptedTransactionBodyBytes() { // given - var blockItem = new BlockItem( - Transaction.newBuilder() + var blockItem = BlockItem.builder() + .transaction(Transaction.newBuilder() .setSignedTransactionBytes(SignedTransaction.newBuilder() .setBodyBytes(DomainUtils.fromBytes(domainBuilder.bytes(512))) .build() .toByteString()) - .build(), - TransactionResult.newBuilder().build(), - Collections.emptyList(), - Collections.emptyList()); - var blockFile = blockFile(List.of(blockItem)); + .build()) + .transactionResult(TransactionResult.newBuilder().build()) + .transactionOutput(Collections.emptyList()) + .stateChanges(Collections.emptyList()) + .build(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when, then assertThatThrownBy(() -> blockFileTransformer.transform(blockFile)).isInstanceOf(ProtobufException.class); @@ -127,14 +131,15 @@ void corruptedTransactionBodyBytes() { @Test void corruptedSignedTransactionBytes() { // given - var blockItem = new BlockItem( - Transaction.newBuilder() + var blockItem = BlockItem.builder() + .transaction(Transaction.newBuilder() .setSignedTransactionBytes(DomainUtils.fromBytes(domainBuilder.bytes(256))) - .build(), - TransactionResult.newBuilder().build(), - Collections.emptyList(), - Collections.emptyList()); - var blockFile = blockFile(List.of(blockItem)); + .build()) + .transactionResult(TransactionResult.newBuilder().build()) + .transactionOutput(Collections.emptyList()) + .stateChanges(Collections.emptyList()) + .build(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when, then assertThatThrownBy(() -> blockFileTransformer.transform(blockFile)).isInstanceOf(ProtobufException.class); @@ -143,7 +148,7 @@ void corruptedSignedTransactionBytes() { @Test void emptyBlockFile() { // given - var blockFile = blockFile(Collections.emptyList()); + var blockFile = blockFileBuilder.items(Collections.emptyList()).build(); // when var recordFile = blockFileTransformer.transform(blockFile); @@ -156,13 +161,16 @@ void emptyBlockFile() { @Test void scheduleCreate() { // given + var accountId = recordItemBuilder.accountId(); var expectedRecordItem = recordItemBuilder .scheduleCreate() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setScheduledTransactionID( + TransactionID.newBuilder().setAccountID(accountId).build())) .build(); var blockItem = blockItemBuilder.scheduleCreate(expectedRecordItem).build(); var expectedScheduleId = blockItem - .stateChanges() + .getStateChanges() .getFirst() .getStateChangesList() .getFirst() @@ -170,7 +178,7 @@ void scheduleCreate() { .getKey() .getScheduleIdKey() .getScheduleNum(); - var blockFile = blockFile(List.of(blockItem)); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when var recordFile = blockFileTransformer.transform(blockFile); @@ -183,46 +191,51 @@ void scheduleCreate() { .satisfies(item -> assertRecordItem(item, expectedRecordItem)) .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) - .returns(expectedScheduleId, transactionRecord -> transactionRecord - .getReceipt() - .getScheduleID() - .getScheduleNum()); + .extracting(TransactionRecord::getReceipt) + .returns(accountId, r -> r.getScheduledTransactionID().getAccountID()) + .returns(expectedScheduleId, r -> r.getScheduleID().getScheduleNum()); }); } - @ValueSource(booleans = {true, false}) - @ParameterizedTest - void scheduleDelete(boolean deleted) { + @Test + void scheduleCreateUnsuccessful() { + // given + var expectedRecordItem = recordItemBuilder + .scheduleCreate() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) + .build(); + var blockItem = blockItemBuilder.scheduleCreate(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().returns(ScheduleID.getDefaultInstance(), r -> r.getTransactionRecord() + .getReceipt() + .getScheduleID()); + }); + } + + @Test + void scheduleDelete() { // given - var scheduleId = recordItemBuilder.scheduleId(); - var builder = recordItemBuilder.scheduleDelete(scheduleId).recordItem(r -> r.hapiVersion(HAPI_VERSION)); - if (deleted) { - builder.receipt(r -> r.setScheduleID(scheduleId)); - } - var expectedRecordItem = builder.build(); + var expectedRecordItem = recordItemBuilder + .scheduleDelete() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .build(); - var blockItem = - blockItemBuilder.scheduleDelete(expectedRecordItem, deleted).build(); - var blockFile = blockFile(List.of(blockItem)); + var blockItem = blockItemBuilder.scheduleDelete(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)) - .satisfies(item -> { - if (deleted) { - assertThat(item.getTransactionRecord() - .getReceipt() - .getScheduleID() - .getScheduleNum()) - .isEqualTo(scheduleId.getScheduleNum()); - } - }); + assertThat(items).hasSize(1).first().satisfies(item -> assertRecordItem(item, expectedRecordItem)); }); } @@ -238,8 +251,8 @@ void scheduleSign() { .build(); var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); var expectedScheduleId = - blockItem.transactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); - var blockFile = blockFile(List.of(blockItem)); + blockItem.getTransactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when var recordFile = blockFileTransformer.transform(blockFile); @@ -258,6 +271,35 @@ void scheduleSign() { }); } + @Test + void scheduleSignUnsuccessful() { + // given + var accountId = recordItemBuilder.accountId(); + var expectedRecordItem = recordItemBuilder + .scheduleSign() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setScheduledTransactionID(TransactionID.newBuilder() + .setAccountID(accountId) + .build()) + .setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) + .build(); + var blockItem = blockItemBuilder.scheduleSign(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() + .returns( + TransactionID.getDefaultInstance(), + r -> r.getTransactionRecord().getReceipt().getScheduledTransactionID()); + }); + } + @Test void unknownTransform() { // given @@ -266,7 +308,7 @@ void unknownTransform() { .recordItem(r -> r.hapiVersion(HAPI_VERSION)) .build(); var blockItem = blockItemBuilder.unknown(expectedRecordItem).build(); - var blockFile = blockFile(List.of(blockItem)); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when var recordFile = blockFileTransformer.transform(blockFile); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockFileBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockFileBuilder.java new file mode 100644 index 00000000000..571369ecc9c --- /dev/null +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockFileBuilder.java @@ -0,0 +1,76 @@ +/* + * 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.parser.domain; + +import com.hedera.hapi.block.stream.output.protoc.BlockHeader; +import com.hedera.mirror.common.domain.DigestAlgorithm; +import com.hedera.mirror.common.domain.DomainBuilder; +import com.hedera.mirror.common.domain.transaction.BlockFile; +import com.hedera.mirror.common.domain.transaction.BlockItem; +import com.hedera.mirror.common.util.DomainUtils; +import com.hederahashgraph.api.proto.java.SemanticVersion; +import jakarta.inject.Named; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +@Named +@RequiredArgsConstructor +public class BlockFileBuilder { + + private final DomainBuilder domainBuilder; + + public BlockFile.BlockFileBuilder items(List blockItems) { + long blockNumber = domainBuilder.number(); + byte[] bytes = domainBuilder.bytes(256); + String filename = StringUtils.leftPad(Long.toString(blockNumber), 36, "0") + ".blk.gz"; + var firstConsensusTimestamp = blockItems.isEmpty() + ? domainBuilder.protoTimestamp() + : blockItems.getFirst().getTransactionResult().getConsensusTimestamp(); + byte[] previousHash = domainBuilder.bytes(48); + long consensusStart = DomainUtils.timestampInNanosMax(firstConsensusTimestamp); + long consensusEnd = blockItems.isEmpty() + ? consensusStart + : DomainUtils.timestampInNanosMax( + blockItems.getLast().getTransactionResult().getConsensusTimestamp()); + + return BlockFile.builder() + .blockHeader(BlockHeader.newBuilder() + .setFirstTransactionConsensusTime(firstConsensusTimestamp) + .setNumber(blockNumber) + .setPreviousBlockHash(DomainUtils.fromBytes(previousHash)) + .setHapiProtoVersion(SemanticVersion.newBuilder().setMinor(57)) + .setSoftwareVersion(SemanticVersion.newBuilder().setMinor(57)) + .build()) + .bytes(bytes) + .consensusEnd(consensusEnd) + .consensusStart(consensusStart) + .count((long) blockItems.size()) + .digestAlgorithm(DigestAlgorithm.SHA_384) + .hash(DomainUtils.bytesToHex(domainBuilder.bytes(48))) + .index(blockNumber) + .items(blockItems) + .loadStart(System.currentTimeMillis()) + .name(filename) + .nodeId(domainBuilder.number()) + .previousHash(DomainUtils.bytesToHex(previousHash)) + .roundEnd(blockNumber + 1) + .roundStart(blockNumber + 1) + .size(bytes.length) + .version(7); + } +} diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 33b7a5d02d9..db6181b0f85 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -111,10 +111,10 @@ public BlockItemBuilder.Builder scheduleCreate(RecordItem recordItem) { public BlockItemBuilder.Builder scheduleDelete() { var recordItem = recordItemBuilder.scheduleDelete().build(); - return scheduleDelete(recordItem, false); + return scheduleDelete(recordItem); } - public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem, boolean deleted) { + public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem) { var scheduleId = recordItem.getTransactionBody().getScheduleDelete().getScheduleID(); var stateChangeDelete = StateChange.newBuilder() .setMapDelete(MapDeleteChange.newBuilder() @@ -125,7 +125,6 @@ public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem, boolean de .setMapUpdate(MapUpdateChange.newBuilder() .setValue(MapChangeValue.newBuilder() .setScheduleValue(Schedule.newBuilder() - .setDeleted(deleted) .setScheduleId(scheduleId) .build()) .build())) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index d484ae7cd0b..9f336eb49c4 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -785,11 +785,7 @@ public Builder scheduleCreate() { } public Builder scheduleDelete() { - return scheduleDelete(scheduleId()); - } - - public Builder scheduleDelete(ScheduleID scheduleId) { - var builder = ScheduleDeleteTransactionBody.newBuilder().setScheduleID(scheduleId); + var builder = ScheduleDeleteTransactionBody.newBuilder().setScheduleID(scheduleId()); return new Builder<>(TransactionType.SCHEDULEDELETE, builder); } diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java index decd32ec164..fea917926f8 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/record/entity/EntityRecordItemListenerScheduleTest.java @@ -27,10 +27,13 @@ import com.hedera.mirror.common.domain.entity.Entity; import com.hedera.mirror.common.domain.entity.EntityId; import com.hedera.mirror.common.domain.schedule.Schedule; +import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.RecordItem; import com.hedera.mirror.common.domain.transaction.TransactionSignature; import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.TestUtils; +import com.hedera.mirror.importer.downloader.block.BlockFileTransformer; +import com.hedera.mirror.importer.parser.domain.BlockFileBuilder; import com.hedera.mirror.importer.parser.domain.BlockItemBuilder; import com.hedera.mirror.importer.repository.ScheduleRepository; import com.hedera.mirror.importer.repository.TransactionRepository; @@ -75,7 +78,14 @@ class EntityRecordItemListenerScheduleTest extends AbstractEntityRecordItemListe private static final Key SCHEDULE_REF_KEY = keyFromString(KEY); private static final long SIGN_TIMESTAMP = 10L; - private final BlockItemBuilder blockItemBuilder = new BlockItemBuilder(); + @Resource + private BlockFileBuilder blockFileBuilder; + + @Resource + private BlockFileTransformer blockFileTransformer; + + @Resource + private BlockItemBuilder blockItemBuilder; @Resource protected ScheduleRepository scheduleRepository; @@ -553,7 +563,7 @@ private void insertScheduleDeleteTransaction(long timestamp, ScheduleID schedule .build(); if (useBlockTransformer) { - var blockItem = blockItemBuilder.scheduleDelete(recordItem, false).build(); + var blockItem = blockItemBuilder.scheduleDelete(recordItem).build(); recordItem = transformBlockItemToRecordItem(blockItem); } @@ -619,6 +629,12 @@ private void assertTransactionInRepository( from(com.hedera.mirror.common.domain.transaction.Transaction::getResult)); } + public RecordItem transformBlockItemToRecordItem(BlockItem blockItem) { + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); + var blockRecordFile = blockFileTransformer.transform(blockFile); + return blockRecordFile.getItems().iterator().next(); + } + private List toTransactionSignatureList( long timestamp, ScheduleID scheduleId, SignatureMap signatureMap) { return signatureMap.getSigPairList().stream() From 5f1b6d3e4586e7fbb9a09a8cc7eb9985f5c5420f Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Fri, 31 Jan 2025 16:35:09 -0500 Subject: [PATCH 03/18] Fix sonar warnings Signed-off-by: Edwin Greene --- .../ScheduleCreateTransformer.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java index bccbb4a925b..08cd7d632de 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java @@ -23,6 +23,7 @@ import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; +@SuppressWarnings("java:S3776") @Named final class ScheduleCreateTransformer extends AbstractBlockItemTransformer { @@ -33,28 +34,27 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu } var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); - outer: + for (var transactionOutput : blockItem.getTransactionOutput()) { + if (transactionOutput.hasCreateSchedule()) { + var output = transactionOutput.getCreateSchedule(); + if (output.hasScheduledTransactionId()) { + receiptBuilder.setScheduledTransactionID(output.getScheduledTransactionId()); + break; + } + } + } + for (var stateChanges : blockItem.getStateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { if (stateChange.getStateId() == STATE_ID_SCHEDULES_BY_ID.getNumber() && stateChange.hasMapUpdate()) { var key = stateChange.getMapUpdate().getKey(); if (key.hasScheduleIdKey()) { receiptBuilder.setScheduleID(key.getScheduleIdKey()); - break outer; + return; } } } } - - for (var transactionOutput : blockItem.getTransactionOutput()) { - if (transactionOutput.hasCreateSchedule()) { - var output = transactionOutput.getCreateSchedule(); - if (output.hasScheduledTransactionId()) { - receiptBuilder.setScheduledTransactionID(output.getScheduledTransactionId()); - return; - } - } - } } @Override From 78ada55e22ca9637e6140dd1a536349ace8b42f9 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 3 Feb 2025 12:01:55 -0500 Subject: [PATCH 04/18] Simplify builder Signed-off-by: Edwin Greene --- .../parser/domain/BlockItemBuilder.java | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 19580ef5535..70c622c4043 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -175,45 +175,25 @@ public BlockItemBuilder.Builder unknown(RecordItem recordItem) { } public Builder fileAppend(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult, List.of(), Collections.emptyList()); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } public Builder fileDelete(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult, List.of(), Collections.emptyList()); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } public Builder fileCreate(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - var stateChanges = buildFileIdStateChanges(recordItem); return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult, List.of(), List.of(stateChanges)); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), List.of(stateChanges)); } public Builder fileUpdate(RecordItem recordItem) { - var instant = Instant.ofEpochSecond(0, recordItem.getConsensusTimestamp()); - var timestamp = Utility.instantToTimestamp(instant); - var transactionRecord = recordItem.getTransactionRecord(); - var transactionResult = transactionResult(transactionRecord, timestamp).build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult, List.of(), Collections.emptyList()); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } private static StateChanges buildFileIdStateChanges(RecordItem recordItem) { From a41484f8ce79e0ac61b4e61ddb0951a216601120 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 3 Feb 2025 12:32:42 -0500 Subject: [PATCH 05/18] Add expected hash verification to new tests Signed-off-by: Edwin Greene --- .../block/BlockFileTransformerTest.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index 76e547c9ea8..dce49acb306 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -168,6 +168,7 @@ void scheduleCreate() { .receipt(r -> r.setScheduledTransactionID( TransactionID.newBuilder().setAccountID(accountId).build())) .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleCreate(expectedRecordItem).build(); var expectedScheduleId = blockItem .stateChanges() @@ -191,6 +192,7 @@ void scheduleCreate() { .satisfies(item -> assertRecordItem(item, expectedRecordItem)) .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) .extracting(TransactionRecord::getReceipt) .returns(accountId, r -> r.getScheduledTransactionID().getAccountID()) .returns(expectedScheduleId, r -> r.getScheduleID().getScheduleNum()); @@ -205,6 +207,7 @@ void scheduleCreateUnsuccessful() { .recordItem(r -> r.hapiVersion(HAPI_VERSION)) .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleCreate(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); @@ -213,9 +216,13 @@ void scheduleCreateUnsuccessful() { // then assertRecordFile(recordFile, blockFile, items -> { - assertThat(items).hasSize(1).first().returns(ScheduleID.getDefaultInstance(), r -> r.getTransactionRecord() - .getReceipt() - .getScheduleID()); + assertThat(items) + .hasSize(1) + .first() + .extracting(RecordItem::getTransactionRecord) + .returns( + ScheduleID.getDefaultInstance(), r -> r.getReceipt().getScheduleID()) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash); }); } @@ -226,7 +233,7 @@ void scheduleDelete() { .scheduleDelete() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) .build(); - + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleDelete(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); @@ -235,7 +242,12 @@ void scheduleDelete() { // then assertRecordFile(recordFile, blockFile, items -> { - assertThat(items).hasSize(1).first().satisfies(item -> assertRecordItem(item, expectedRecordItem)); + assertThat(items) + .hasSize(1) + .first() + .satisfies(item -> assertRecordItem(item, expectedRecordItem)) + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash); }); } @@ -249,6 +261,7 @@ void scheduleSign() { .receipt(r -> r.setScheduledTransactionID( TransactionID.newBuilder().setAccountID(accountId).build())) .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); var expectedScheduleId = blockItem.transactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); @@ -265,6 +278,7 @@ void scheduleSign() { .satisfies(item -> assertRecordItem(item, expectedRecordItem)) .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) .returns( expectedScheduleId, transactionRecord -> transactionRecord.getReceipt().getScheduledTransactionID()); @@ -283,6 +297,7 @@ void scheduleSignUnsuccessful() { .build()) .setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); @@ -294,9 +309,10 @@ void scheduleSignUnsuccessful() { assertThat(items) .hasSize(1) .first() - .returns( - TransactionID.getDefaultInstance(), - r -> r.getTransactionRecord().getReceipt().getScheduledTransactionID()); + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .returns(TransactionID.getDefaultInstance(), r -> r.getReceipt() + .getScheduledTransactionID()); }); } From 08b82a02729d6f5ec5b2e419b47b365d552655dc Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 14:25:23 -0500 Subject: [PATCH 06/18] Add token transformers Signed-off-by: Edwin Greene --- docs/design/block-streams.md | 6 - .../transformer/TokenAirdropTransformer.java | 74 +++ .../transformer/TokenBurnTransformer.java | 56 +++ .../transformer/TokenCreateTransformer.java | 52 +++ .../transformer/TokenMintTransformer.java | 73 +++ .../transformer/TokenWipeTransformer.java | 60 +++ .../block/BlockFileTransformerTest.java | 430 ++++++++++++++---- .../parser/domain/BlockItemBuilder.java | 182 +++++++- .../parser/domain/RecordItemBuilder.java | 3 +- 9 files changed, 819 insertions(+), 117 deletions(-) create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java diff --git a/docs/design/block-streams.md b/docs/design/block-streams.md index e3cad8cc972..9056521ac8e 100644 --- a/docs/design/block-streams.md +++ b/docs/design/block-streams.md @@ -407,13 +407,7 @@ Beginning from an `EventTransaction` block item, a record item is composed of on | token_airdrop.token_id (fungible) | state_changes[i].state_change.map_update.key.tokenReference.fungible_token_type | | token_airdrop.token_id (nft) | state_changes[i].state_change.map_update.key.tokenReference.non_fungible_token.nftId.tokenId | | assessed_custom_fee | Similar to crypto_transfer, retrieved from transaction_output.token_airdrop | -| token_account.account_id | transaction_output.token_airdrop.automatic_token_associations[i].accountId | -| token_account.token_id | transaction_output.token_airdrop.automatic_token_associations[i].tokenId | -| token_transfer.account_id | transaction_output.token_airdrop.token_transfer_lists[i].transfers[j].accountAmount.accountID | -| token_transfer.amount | transaction_output.token_airdrop.token_transfer_lists[i].transfers[j].accountAmount.amount | | token_transfer.consensus_timestamp | transaction_result.consensus_timestamp | -| token_transfer.is_approval | transaction_output.token_airdrop.token_transfer_lists[i].transfers[j].accountAmount.isApproval | -| token_transfer.token_id | transaction_output.token_airdrop.token_transfer_lists[i].token.tokenID | ### Token Create Transaction diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java new file mode 100644 index 00000000000..272ce089e86 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java @@ -0,0 +1,74 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_PENDING_AIRDROPS; + +import com.hedera.mirror.common.domain.transaction.BlockItem; +import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.PendingAirdropId.TokenReferenceCase; +import com.hederahashgraph.api.proto.java.PendingAirdropRecord; +import com.hederahashgraph.api.proto.java.TransactionRecord; +import jakarta.inject.Named; + +@Named +final class TokenAirdropTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_PENDING_AIRDROPS.getNumber() && stateChange.hasMapUpdate()) { + var mapUpdate = stateChange.getMapUpdate(); + var key = mapUpdate.getKey(); + if (key.hasPendingAirdropIdKey()) { + var pendingId = key.getPendingAirdropIdKey(); + var pendingAirdrop = PendingAirdropRecord.newBuilder().setPendingAirdropId(pendingId); + if (pendingId.getTokenReferenceCase() == TokenReferenceCase.FUNGIBLE_TOKEN_TYPE) { + var value = mapUpdate.getValue(); + if (value.hasAccountPendingAirdropValue()) { + var accountValue = value.getAccountPendingAirdropValue(); + if (accountValue.hasPendingAirdropValue()) { + pendingAirdrop.setPendingAirdropValue(accountValue.getPendingAirdropValue()); + } + } + } + + transactionRecordBuilder.addNewPendingAirdrops(pendingAirdrop); + } + } + } + } + + for (var transactionOutput : blockItem.transactionOutput()) { + if (transactionOutput.hasTokenAirdrop()) { + var output = transactionOutput.getTokenAirdrop(); + var assessedCustomFees = output.getAssessedCustomFeesList(); + transactionRecordBuilder.addAllAssessedCustomFees(assessedCustomFees); + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.TOKENAIRDROP; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java new file mode 100644 index 00000000000..945f10e1479 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java @@ -0,0 +1,56 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; + +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 TokenBurnTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() && stateChange.hasMapUpdate()) { + var mapUpdate = stateChange.getMapUpdate(); + if (mapUpdate.getKey().hasTokenIdKey()) { + var value = mapUpdate.getValue(); + if (value.hasTokenValue()) { + var totalSupply = value.getTokenValue().getTotalSupply(); + transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(totalSupply); + return; + } + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.TOKENBURN; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java new file mode 100644 index 00000000000..3817154a298 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java @@ -0,0 +1,52 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; + +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 TokenCreateTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() && stateChange.hasMapUpdate()) { + var key = stateChange.getMapUpdate().getKey(); + if (key.hasTokenIdKey()) { + transactionRecordBuilder.getReceiptBuilder().setTokenID(key.getTokenIdKey()); + return; + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.TOKENCREATION; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java new file mode 100644 index 00000000000..db33864b3e5 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -0,0 +1,73 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; + +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 TokenMintTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.hasMapUpdate()) { + if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber()) { + var mapUpdate = stateChange.getMapUpdate(); + if (mapUpdate.hasValue()) { + var value = mapUpdate.getValue(); + if (value.hasTokenValue()) { + receiptBuilder.setNewTotalSupply( + value.getTokenValue().getTotalSupply()); + return; + } + } + } else if (stateChange.getStateId() == STATE_ID_NFTS.getNumber()) { + var mapUpdate = stateChange.getMapUpdate(); + var key = mapUpdate.getKey(); + if (key.hasNftIdKey()) { + receiptBuilder.addSerialNumbers(key.getNftIdKey().getSerialNumber()); + if (mapUpdate.hasValue()) { + var value = mapUpdate.getValue(); + if (value.hasTokenValue()) { + receiptBuilder.setNewTotalSupply( + value.getTokenValue().getTotalSupply()); + } + } + } + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.TOKENMINT; + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java new file mode 100644 index 00000000000..34d9bc48feb --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java @@ -0,0 +1,60 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; + +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 TokenWipeTransformer extends AbstractBlockItemTransformer { + + @Override + protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + // Note: Only supporting fungible tokens for now. + // NFTs are represented in stateChanges as a MapDelete, which contains no total supply. This is an open + // issue. + if (stateChange.hasMapUpdate() && stateChange.getStateId() == STATE_ID_TOKENS.getNumber()) { + var mapUpdate = stateChange.getMapUpdate(); + if (mapUpdate.hasValue()) { + var value = mapUpdate.getValue(); + if (value.hasTokenValue()) { + receiptBuilder.setNewTotalSupply( + value.getTokenValue().getTotalSupply()); + return; + } + } + } + } + } + } + + @Override + public TransactionType getType() { + return TransactionType.TOKENWIPE; + } +} diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index dce49acb306..6e013104549 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -21,11 +21,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.protobuf.ByteString; +import com.google.protobuf.GeneratedMessageV3.Builder; import com.hedera.hapi.block.stream.output.protoc.TransactionResult; import com.hedera.mirror.common.domain.transaction.BlockFile; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.RecordFile; import com.hedera.mirror.common.domain.transaction.RecordItem; +import com.hedera.mirror.common.domain.transaction.TransactionType; import com.hedera.mirror.common.exception.ProtobufException; import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.importer.ImporterIntegrationTest; @@ -36,18 +38,25 @@ import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.ScheduleID; import com.hederahashgraph.api.proto.java.SignedTransaction; +import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenType; 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; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.data.util.Version; @RequiredArgsConstructor @@ -57,9 +66,56 @@ class BlockFileTransformerTest extends ImporterIntegrationTest { private static final Version HAPI_VERSION = new Version(0, HAPI_VERSION_MINOR); private final BlockFileBuilder blockFileBuilder; - private final BlockItemBuilder blockItemBuilder; + private final BlockItemBuilder blockItemBuilder = new BlockItemBuilder(); private final BlockFileTransformer blockFileTransformer; - private final RecordItemBuilder recordItemBuilder = new RecordItemBuilder(); + private static final RecordItemBuilder recordItemBuilder = new RecordItemBuilder(); + + private static Stream provideDefaultTransforms() { + return Stream.of( + Arguments.of(TransactionType.FILEAPPEND, recordItemBuilder.fileAppend()), + Arguments.of(TransactionType.FILEDELETE, recordItemBuilder.fileDelete()), + Arguments.of(TransactionType.FILEUPDATE, recordItemBuilder.fileUpdate()), + Arguments.of(TransactionType.SCHEDULEDELETE, recordItemBuilder.scheduleDelete()), + Arguments.of(TransactionType.TOKENASSOCIATE, recordItemBuilder.tokenAssociate()), + Arguments.of(TransactionType.TOKENCANCELAIRDROP, recordItemBuilder.tokenCancelAirdrop()), + Arguments.of(TransactionType.TOKENCLAIMAIRDROP, recordItemBuilder.tokenClaimAirdrop()), + Arguments.of(TransactionType.TOKENDELETION, recordItemBuilder.tokenDelete()), + Arguments.of(TransactionType.TOKENDISSOCIATE, recordItemBuilder.tokenDissociate()), + Arguments.of(TransactionType.TOKENFEESCHEDULEUPDATE, recordItemBuilder.tokenFeeScheduleUpdate()), + Arguments.of(TransactionType.TOKENGRANTKYC, recordItemBuilder.tokenGrantKyc()), + Arguments.of(TransactionType.TOKENFREEZE, recordItemBuilder.tokenFreeze()), + Arguments.of(TransactionType.TOKENPAUSE, recordItemBuilder.tokenPause()), + Arguments.of(TransactionType.TOKENREJECT, recordItemBuilder.tokenReject()), + Arguments.of(TransactionType.TOKENREVOKEKYC, recordItemBuilder.tokenRevokeKyc()), + Arguments.of(TransactionType.TOKENUNFREEZE, recordItemBuilder.tokenUnfreeze()), + Arguments.of(TransactionType.TOKENUNPAUSE, recordItemBuilder.tokenUnpause()), + Arguments.of(TransactionType.TOKENUPDATE, recordItemBuilder.tokenUpdate()), + Arguments.of(TransactionType.TOKENUPDATENFTS, recordItemBuilder.tokenUpdateNfts()), + Arguments.of(TransactionType.UNKNOWN, recordItemBuilder.unknown())); + } + + @ParameterizedTest(name = "Default transform for {0}") + @MethodSource("provideDefaultTransforms") + void defaultTransforms( + TransactionType type, RecordItemBuilder.Builder>> recordItem) { + var expectedRecordItem = + recordItem.recordItem(r -> r.hapiVersion(HAPI_VERSION)).build(); + var blockItem = blockItemBuilder.defaultRecordItem(expectedRecordItem).build(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + + // 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)); + } @ParameterizedTest @EnumSource(value = TransferType.class) @@ -227,14 +283,19 @@ void scheduleCreateUnsuccessful() { } @Test - void scheduleDelete() { + void scheduleSign() { // given + var accountId = recordItemBuilder.accountId(); var expectedRecordItem = recordItemBuilder - .scheduleDelete() + .scheduleSign() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setScheduledTransactionID( + TransactionID.newBuilder().setAccountID(accountId).build())) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.scheduleDelete(expectedRecordItem).build(); + var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); + var expectedScheduleId = + blockItem.transactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when @@ -246,25 +307,135 @@ void scheduleDelete() { .hasSize(1) .first() .satisfies(item -> assertRecordItem(item, expectedRecordItem)) + .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) - .returns(expectedTransactionHash, TransactionRecord::getTransactionHash); + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .returns( + expectedScheduleId, + transactionRecord -> transactionRecord.getReceipt().getScheduledTransactionID()); }); } @Test - void scheduleSign() { + void scheduleSignUnsuccessful() { // given var accountId = recordItemBuilder.accountId(); var expectedRecordItem = recordItemBuilder .scheduleSign() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .receipt(r -> r.setScheduledTransactionID( - TransactionID.newBuilder().setAccountID(accountId).build())) + .receipt(r -> r.setScheduledTransactionID(TransactionID.newBuilder() + .setAccountID(accountId) + .build()) + .setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); - var expectedScheduleId = - blockItem.transactionOutput().getFirst().getSignSchedule().getScheduledTransactionId(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); + + // when + var recordFile = blockFileTransformer.transform(blockFile); + + // then + assertRecordFile(recordFile, blockFile, items -> { + assertThat(items) + .hasSize(1) + .first() + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .returns(TransactionID.getDefaultInstance(), r -> r.getReceipt() + .getScheduledTransactionID()); + }); + } + + @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) { + // 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(); + var expectedFileId = blockItem + .stateChanges() + .getFirst() + .getStateChanges(3) + .getMapUpdate() + .getKey() + .getFileIdKey() + .getFileNum(); + 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( + expectedFileId, + transactionRecord -> + transactionRecord.getReceipt().getFileID().getFileNum())); + } + + @Test + void fileCreateTransformWhenStatusIsNotSuccess() { + // given + var expectedRecordItem = recordItemBuilder + .fileCreate() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.clearFileID().setStatus(ResponseCodeEnum.AUTHORIZATION_FAILED)) + .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.fileCreate(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().getFileID().getFileNum())); + } + + @Test + void tokenAirdrop() { + // given + var expectedRecordItem = recordItemBuilder + .tokenAirdrop() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.tokenAirdrop(expectedRecordItem).build(); + var expectedFees = + blockItem.transactionOutput().getFirst().getTokenAirdrop().getAssessedCustomFeesList(); + var pendingList = blockItem.stateChanges().getFirst().getStateChangesList(); + var mapUpdate = pendingList.getFirst().getMapUpdate(); + var expectedFungibleKey = mapUpdate.getKey().getPendingAirdropIdKey(); + var expectedAmount = + mapUpdate.getValue().getAccountPendingAirdropValue().getPendingAirdropValue(); + var expectedNftKey = pendingList.get(1).getMapUpdate().getKey().getPendingAirdropIdKey(); + var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when @@ -279,26 +450,25 @@ void scheduleSign() { .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .returns(expectedFees, TransactionRecord::getAssessedCustomFeesList) + .extracting(TransactionRecord::getNewPendingAirdropsList) .returns( - expectedScheduleId, - transactionRecord -> transactionRecord.getReceipt().getScheduledTransactionID()); + expectedFungibleKey, airdrops -> airdrops.getFirst().getPendingAirdropId()) + .returns(expectedAmount, airdrops -> airdrops.getFirst().getPendingAirdropValue()) + .returns(expectedNftKey, airdrops -> airdrops.get(1).getPendingAirdropId()); }); } @Test - void scheduleSignUnsuccessful() { + void tokenAirdropUnsuccessful() { // given - var accountId = recordItemBuilder.accountId(); var expectedRecordItem = recordItemBuilder - .scheduleSign() + .tokenAirdrop() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .receipt(r -> r.setScheduledTransactionID(TransactionID.newBuilder() - .setAccountID(accountId) - .build()) - .setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.scheduleSign(expectedRecordItem).build(); + var blockItem = blockItemBuilder.tokenAirdrop(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when @@ -309,21 +479,23 @@ void scheduleSignUnsuccessful() { assertThat(items) .hasSize(1) .first() + .returns(null, RecordItem::getPrevious) .extracting(RecordItem::getTransactionRecord) .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) - .returns(TransactionID.getDefaultInstance(), r -> r.getReceipt() - .getScheduledTransactionID()); + .returns(Collections.emptyList(), TransactionRecord::getAssessedCustomFeesList) + .returns(Collections.emptyList(), TransactionRecord::getNewPendingAirdropsList); }); } @Test - void unknownTransform() { + void tokenBurn() { // given var expectedRecordItem = recordItemBuilder - .unknown() + .tokenBurn() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) .build(); - var blockItem = blockItemBuilder.unknown(expectedRecordItem).build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.tokenBurn(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when @@ -331,153 +503,209 @@ void unknownTransform() { // then assertRecordFile(recordFile, blockFile, items -> { - assertThat(items).hasSize(1).first().satisfies(item -> assertRecordItem(item, expectedRecordItem)); + assertThat(items) + .hasSize(1) + .first() + .satisfies(item -> assertRecordItem(item, expectedRecordItem)) + .returns(null, RecordItem::getPrevious) + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash); }); } @Test - void fileAppendTransform() { + void tokenBurnUnsuccessful() { // given var expectedRecordItem = recordItemBuilder - .fileAppend() + .tokenBurn() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); - var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.tokenBurn(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() + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .extracting(TransactionRecord::getReceipt) + .returns(ResponseCodeEnum.INVALID_TRANSACTION, TransactionReceipt::getStatus) + .returns(0L, TransactionReceipt::getNewTotalSupply); + }); + } - var blockItem = blockItemBuilder.fileAppend(expectedRecordItem).build(); + @Test + void tokenCreate() { + // given + var expectedRecordItem = recordItemBuilder + .tokenCreate() + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.tokenCreate(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)); + 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 fileDeleteTransform() { + void tokenCreateUnsuccessful() { // given var expectedRecordItem = recordItemBuilder - .fileDelete() + .tokenCreate() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.fileDelete(expectedRecordItem).build(); + var blockItem = blockItemBuilder.tokenCreate(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)); + assertRecordFile(recordFile, blockFile, items -> { + assertThat(items) + .hasSize(1) + .first() + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .extracting(TransactionRecord::getReceipt) + .returns(TokenID.getDefaultInstance(), TransactionReceipt::getTokenID); + }); } @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) { + @EnumSource(value = TokenType.class, mode = Mode.EXCLUDE, names = "UNRECOGNIZED") + void tokenMint(TokenType type) { // given var expectedRecordItem = recordItemBuilder - .fileCreate() + .tokenMint(type) .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .receipt(r -> r.setStatus(successfulStatus)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.fileCreate(expectedRecordItem).build(); - var expectedFileId = blockItem - .stateChanges() - .getFirst() - .getStateChanges(3) - .getMapUpdate() - .getKey() - .getFileIdKey() - .getFileNum(); + var blockItem = blockItemBuilder.tokenMint(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( - expectedFileId, - transactionRecord -> - transactionRecord.getReceipt().getFileID().getFileNum())); + 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 fileCreateTransformWhenStatusIsNotSuccess() { + void tokenMintUnsuccessful() { // given var expectedRecordItem = recordItemBuilder - .fileCreate() + .tokenMint() .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .receipt(r -> r.clearFileID().setStatus(ResponseCodeEnum.AUTHORIZATION_FAILED)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.fileCreate(expectedRecordItem).build(); + var blockItem = blockItemBuilder.tokenMint(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() + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .extracting(TransactionRecord::getReceipt) + .returns(0, TransactionReceipt::getSerialNumbersCount) + .returns(0L, TransactionReceipt::getNewTotalSupply); + }); + } + @ParameterizedTest + @EnumSource( + value = TokenType.class, + names = {"FUNGIBLE_COMMON"}) // Add NFT once it is supported by blockstreams + void tokenWipe(TokenType type) { + // given + var expectedRecordItem = recordItemBuilder + .tokenWipe(type) + .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .build(); + var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); + var blockItem = blockItemBuilder.tokenWipe(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().getFileID().getFileNum())); + 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 fileUpdateTransform() { + @ParameterizedTest + @EnumSource( + value = TokenType.class, + names = {"FUNGIBLE_COMMON", "NON_FUNGIBLE_UNIQUE"}) + void tokenWipeUnsuccessful(TokenType type) { // given var expectedRecordItem = recordItemBuilder - .fileUpdate() + .tokenWipe(type) .recordItem(r -> r.hapiVersion(HAPI_VERSION)) + .receipt(r -> r.setStatus(ResponseCodeEnum.INVALID_TRANSACTION)) .build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); - var blockItem = blockItemBuilder.fileUpdate(expectedRecordItem).build(); + var blockItem = blockItemBuilder.tokenWipe(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)); + assertRecordFile(recordFile, blockFile, items -> { + assertThat(items) + .hasSize(1) + .first() + .extracting(RecordItem::getTransactionRecord) + .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) + .extracting(TransactionRecord::getReceipt) + .returns(0L, TransactionReceipt::getNewTotalSupply); + }); } private void assertRecordFile( diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 70c622c4043..b6cec374710 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -16,7 +16,10 @@ package com.hedera.mirror.importer.parser.domain; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_PENDING_AIRDROPS; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_SCHEDULES_BY_ID; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; import com.hedera.hapi.block.stream.output.protoc.CallContractOutput; import com.hedera.hapi.block.stream.output.protoc.CreateScheduleOutput; @@ -28,19 +31,24 @@ import com.hedera.hapi.block.stream.output.protoc.SignScheduleOutput; import com.hedera.hapi.block.stream.output.protoc.StateChange; import com.hedera.hapi.block.stream.output.protoc.StateChanges; +import com.hedera.hapi.block.stream.output.protoc.TokenAirdropOutput; import com.hedera.hapi.block.stream.output.protoc.TransactionOutput; import com.hedera.hapi.block.stream.output.protoc.TransactionResult; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.RecordItem; import com.hedera.mirror.importer.util.Utility; +import com.hederahashgraph.api.proto.java.AccountPendingAirdrop; import com.hederahashgraph.api.proto.java.AssessedCustomFee; import com.hederahashgraph.api.proto.java.FileID; +import com.hederahashgraph.api.proto.java.NftID; import com.hederahashgraph.api.proto.java.Schedule; import com.hederahashgraph.api.proto.java.Timestamp; +import com.hederahashgraph.api.proto.java.Token; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -170,30 +178,185 @@ public BlockItemBuilder.Builder unknown() { } public BlockItemBuilder.Builder unknown(RecordItem recordItem) { + return defaultRecordItem(recordItem); + } + + public BlockItemBuilder.Builder defaultRecordItem(RecordItem recordItem) { return new BlockItemBuilder.Builder( recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } - public Builder fileAppend(RecordItem recordItem) { + public Builder fileCreate(RecordItem recordItem) { + var stateChanges = buildFileIdStateChanges(recordItem); + return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); + recordItem.getTransaction(), transactionResult(recordItem), List.of(), List.of(stateChanges)); } - public Builder fileDelete(RecordItem recordItem) { + public Builder tokenAirdrop(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var pendingAirdrops = transactionRecord.getNewPendingAirdropsList(); + List changes = new ArrayList<>(); + for (var pendingAirdrop : pendingAirdrops) { + var accountPendingAirdrop = + AccountPendingAirdrop.newBuilder().setPendingAirdropValue(pendingAirdrop.getPendingAirdropValue()); + changes.add(StateChange.newBuilder() + .setStateId(STATE_ID_PENDING_AIRDROPS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setPendingAirdropIdKey(pendingAirdrop.getPendingAirdropId())) + .setValue(MapChangeValue.newBuilder() + .setAccountPendingAirdropValue(accountPendingAirdrop) + .build()) + .build()) + .build()); + } + var stateChanges = StateChanges.newBuilder().addAllStateChanges(changes).build(); + + var transactionOutput = TransactionOutput.newBuilder() + .setTokenAirdrop(TokenAirdropOutput.newBuilder() + .addAssessedCustomFees(assessedCustomFees()) + .build()) + .build(); + return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); + recordItem.getTransaction(), + transactionResult(recordItem), + List.of(transactionOutput), + List.of(stateChanges)); } - public Builder fileCreate(RecordItem recordItem) { - var stateChanges = buildFileIdStateChanges(recordItem); + public Builder tokenBurn(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var receipt = transactionRecord.getReceipt(); + var tokenId = receipt.getTokenID(); + var supply = receipt.getNewTotalSupply(); + var stateChange = StateChange.newBuilder() + .setStateId(STATE_ID_TOKENS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) + .setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(supply))) + .build()) + .build(); + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(StateChanges.newBuilder().addStateChanges(stateChange).build())); + } + public Builder tokenCreate(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var receipt = transactionRecord.getReceipt(); + var tokenId = receipt.getTokenID(); + var stateChange = StateChange.newBuilder() + .setStateId(STATE_ID_TOKENS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) + .build()) + .build(); return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult(recordItem), List.of(), List.of(stateChanges)); + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(StateChanges.newBuilder().addStateChanges(stateChange).build())); + } + + public Builder tokenMint(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var receipt = transactionRecord.getReceipt(); + var tokenId = receipt.getTokenID(); + var serialNumbers = receipt.getSerialNumbersList(); + var supply = receipt.getNewTotalSupply(); + if (serialNumbers.isEmpty()) { + var stateChange = StateChange.newBuilder() + .setStateId(STATE_ID_TOKENS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) + .setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(supply))) + .build()) + .build(); + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(StateChanges.newBuilder() + .addStateChanges(stateChange) + .build())); + } else { + var stateChangesBuilder = StateChanges.newBuilder(); + for (int i = 0; i < serialNumbers.size(); i++) { + var mapUpdate = MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setNftIdKey(NftID.newBuilder() + .setTokenID(tokenId) + .setSerialNumber(serialNumbers.get(i)) + .build())); + + if (i == serialNumbers.size() - 1) { + mapUpdate.setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(supply)) + .build()); + } + stateChangesBuilder.addStateChanges(StateChange.newBuilder() + .setStateId(STATE_ID_NFTS.getNumber()) + .setMapUpdate(mapUpdate.build())); + } + + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(stateChangesBuilder.build())); + } } - public Builder fileUpdate(RecordItem recordItem) { + public Builder tokenWipe(RecordItem recordItem) { + var transactionRecord = recordItem.getTransactionRecord(); + var transactionBody = recordItem.getTransactionBody(); + var receipt = transactionRecord.getReceipt(); + var tokenId = receipt.getTokenID(); + var supply = receipt.getNewTotalSupply(); + + var serialNumbers = transactionBody.getTokenWipe().getSerialNumbersList(); + if (serialNumbers.isEmpty()) { + var stateChange = StateChange.newBuilder() + .setStateId(STATE_ID_TOKENS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) + .setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(supply))) + .build()) + .build(); + return new BlockItemBuilder.Builder( + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(StateChanges.newBuilder() + .addStateChanges(stateChange) + .build())); + } + + var stateChangesBuilder = StateChanges.newBuilder(); + for (var serialNumber : serialNumbers) { + var mapDelete = MapDeleteChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setNftIdKey(NftID.newBuilder() + .setTokenID(tokenId) + .setSerialNumber(serialNumber) + .build())); + stateChangesBuilder.addStateChanges(StateChange.newBuilder() + .setStateId(STATE_ID_NFTS.getNumber()) + .setMapDelete(mapDelete.build())); + } + return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); + recordItem.getTransaction(), + transactionResult(recordItem), + Collections.emptyList(), + List.of(stateChangesBuilder.build())); } private static StateChanges buildFileIdStateChanges(RecordItem recordItem) { @@ -254,6 +417,7 @@ private TransactionResult.Builder transactionResult( } return builder.addAllPaidStakingRewards(transactionRecord.getPaidStakingRewardsList()) + .addAllAutomaticTokenAssociations(transactionRecord.getAutomaticTokenAssociationsList()) .addAllTokenTransferLists(transactionRecord.getTokenTransferListsList()) .setConsensusTimestamp(consensusTimestamp) .setTransferList(transactionRecord.getTransferList()) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index d417c5203bd..fead3f07199 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -914,9 +914,10 @@ public Builder tokenMint(TokenType tokenType) if (tokenType == FUNGIBLE_COMMON) { transactionBody.setAmount(1000L); + builder.receipt(b -> b.setNewTotalSupply(2000L)); } else { transactionBody.addMetadata(bytes(16)).addMetadata(bytes(16)); - builder.receipt(b -> b.addSerialNumbers(1L).addSerialNumbers(2L)); + builder.receipt(b -> b.addSerialNumbers(1L).addSerialNumbers(2L).setNewTotalSupply(3L)); } return builder; From 6d0c7e7f53b2dbe41dc899f8b7d3163dc9826cbf Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 16:08:47 -0500 Subject: [PATCH 07/18] Update for nft token wipe Signed-off-by: Edwin Greene --- .../transformer/TokenWipeTransformer.java | 8 +-- .../block/BlockFileTransformerTest.java | 62 +++---------------- .../parser/domain/BlockItemBuilder.java | 9 ++- 3 files changed, 18 insertions(+), 61 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java index 34d9bc48feb..ed3e3c41f58 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java @@ -16,6 +16,7 @@ package com.hedera.mirror.importer.downloader.block.transformer; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; import com.hedera.mirror.common.domain.transaction.BlockItem; @@ -35,10 +36,9 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); for (var stateChanges : blockItem.stateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { - // Note: Only supporting fungible tokens for now. - // NFTs are represented in stateChanges as a MapDelete, which contains no total supply. This is an open - // issue. - if (stateChange.hasMapUpdate() && stateChange.getStateId() == STATE_ID_TOKENS.getNumber()) { + if (stateChange.hasMapUpdate() + && (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() + || stateChange.getStateId() == STATE_ID_NFTS.getNumber())) { var mapUpdate = stateChange.getMapUpdate(); if (mapUpdate.hasValue()) { var value = mapUpdate.getValue(); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index 5d16053a69f..ac025b508b6 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -66,12 +66,14 @@ class BlockFileTransformerTest extends ImporterIntegrationTest { private static final Version HAPI_VERSION = new Version(0, HAPI_VERSION_MINOR); private final BlockFileBuilder blockFileBuilder; - private final BlockItemBuilder blockItemBuilder = new BlockItemBuilder(); + private final BlockItemBuilder blockItemBuilder; private final BlockFileTransformer blockFileTransformer; private static final RecordItemBuilder recordItemBuilder = new RecordItemBuilder(); private static Stream provideDefaultTransforms() { return Stream.of( + Arguments.of(TransactionType.CONSENSUSUPDATETOPIC, recordItemBuilder.consensusUpdateTopic()), + Arguments.of(TransactionType.CONSENSUSDELETETOPIC, recordItemBuilder.consensusDeleteTopic()), Arguments.of(TransactionType.FILEAPPEND, recordItemBuilder.fileAppend()), Arguments.of(TransactionType.FILEDELETE, recordItemBuilder.fileDelete()), Arguments.of(TransactionType.FILEUPDATE, recordItemBuilder.fileUpdate()), @@ -566,54 +568,6 @@ void consensusSubmitMessageTransformUnsuccessful() { .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)); - } - @Test void tokenAirdrop() { // given @@ -848,13 +802,13 @@ void tokenMintUnsuccessful() { @ParameterizedTest @EnumSource( value = TokenType.class, - names = {"FUNGIBLE_COMMON"}) // Add NFT once it is supported by blockstreams + names = {"FUNGIBLE_COMMON", "NON_FUNGIBLE_UNIQUE"}) void tokenWipe(TokenType type) { // given - var expectedRecordItem = recordItemBuilder - .tokenWipe(type) - .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .build(); + var builder = recordItemBuilder.tokenWipe(type).recordItem(r -> r.hapiVersion(HAPI_VERSION)); + var expectedRecordItem = type.equals(TokenType.NON_FUNGIBLE_UNIQUE) + ? builder.transactionBody(t -> t.addSerialNumbers(2)).build() + : builder.build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.tokenWipe(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index daca55d7132..44329661310 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -418,15 +418,18 @@ public Builder tokenWipe(RecordItem recordItem) { var stateChangesBuilder = StateChanges.newBuilder(); for (var serialNumber : serialNumbers) { - var mapDelete = MapDeleteChange.newBuilder() + var mapUpdate = MapUpdateChange.newBuilder() .setKey(MapChangeKey.newBuilder() .setNftIdKey(NftID.newBuilder() .setTokenID(tokenId) .setSerialNumber(serialNumber) - .build())); + .build())) + .setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(supply))) + .build(); stateChangesBuilder.addStateChanges(StateChange.newBuilder() .setStateId(STATE_ID_NFTS.getNumber()) - .setMapDelete(mapDelete.build())); + .setMapUpdate(mapUpdate)); } return new BlockItemBuilder.Builder( From 4e43d0c3a12cf18487d5fa78f2e5d8461d742747 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 16:19:03 -0500 Subject: [PATCH 08/18] Clean up token mint Signed-off-by: Edwin Greene --- .../transformer/TokenMintTransformer.java | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java index db33864b3e5..80f5b0be5f1 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -19,8 +19,10 @@ import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; +import com.hedera.hapi.block.stream.output.protoc.MapUpdateChange; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionReceipt; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -37,28 +39,17 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu for (var stateChanges : blockItem.stateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { if (stateChange.hasMapUpdate()) { + var mapUpdate = stateChange.getMapUpdate(); if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber()) { - var mapUpdate = stateChange.getMapUpdate(); - if (mapUpdate.hasValue()) { - var value = mapUpdate.getValue(); - if (value.hasTokenValue()) { - receiptBuilder.setNewTotalSupply( - value.getTokenValue().getTotalSupply()); - return; - } + if (setSupply(mapUpdate, receiptBuilder)) { + return; } + ; } else if (stateChange.getStateId() == STATE_ID_NFTS.getNumber()) { - var mapUpdate = stateChange.getMapUpdate(); var key = mapUpdate.getKey(); if (key.hasNftIdKey()) { receiptBuilder.addSerialNumbers(key.getNftIdKey().getSerialNumber()); - if (mapUpdate.hasValue()) { - var value = mapUpdate.getValue(); - if (value.hasTokenValue()) { - receiptBuilder.setNewTotalSupply( - value.getTokenValue().getTotalSupply()); - } - } + setSupply(mapUpdate, receiptBuilder); } } } @@ -66,6 +57,18 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu } } + private boolean setSupply(MapUpdateChange mapUpdate, TransactionReceipt.Builder receiptBuilder) { + if (mapUpdate.hasValue()) { + var value = mapUpdate.getValue(); + if (value.hasTokenValue()) { + receiptBuilder.setNewTotalSupply(value.getTokenValue().getTotalSupply()); + return true; + } + } + + return false; + } + @Override public TransactionType getType() { return TransactionType.TOKENMINT; From 1241938c63a2c78931a333fbeed74fc5bdcfcb41 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 16:23:47 -0500 Subject: [PATCH 09/18] Fix sonar Signed-off-by: Edwin Greene --- .../downloader/block/transformer/TokenAirdropTransformer.java | 1 + .../downloader/block/transformer/TokenBurnTransformer.java | 1 + .../downloader/block/transformer/TokenMintTransformer.java | 2 +- .../downloader/block/transformer/TokenWipeTransformer.java | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java index 272ce089e86..f22e4ce75f5 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java @@ -28,6 +28,7 @@ @Named final class TokenAirdropTransformer extends AbstractBlockItemTransformer { + @SuppressWarnings("java:S3776") @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java index 945f10e1479..f4e3beeb30b 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java @@ -26,6 +26,7 @@ @Named final class TokenBurnTransformer extends AbstractBlockItemTransformer { + @SuppressWarnings("java:S3776") @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java index 80f5b0be5f1..6c3c3454458 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -29,6 +29,7 @@ @Named final class TokenMintTransformer extends AbstractBlockItemTransformer { + @SuppressWarnings("java:S3776") @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { @@ -44,7 +45,6 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu if (setSupply(mapUpdate, receiptBuilder)) { return; } - ; } else if (stateChange.getStateId() == STATE_ID_NFTS.getNumber()) { var key = mapUpdate.getKey(); if (key.hasNftIdKey()) { diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java index ed3e3c41f58..efeba936659 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java @@ -27,6 +27,7 @@ @Named final class TokenWipeTransformer extends AbstractBlockItemTransformer { + @SuppressWarnings("java:S3776") @Override protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { From 960958eec866d92f67e9e1a6e1b80dce141f4a4a Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 16:31:27 -0500 Subject: [PATCH 10/18] Nit fix ordering Signed-off-by: Edwin Greene --- .../importer/downloader/block/BlockFileTransformerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index ac025b508b6..597d7367859 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -72,8 +72,8 @@ class BlockFileTransformerTest extends ImporterIntegrationTest { private static Stream provideDefaultTransforms() { return Stream.of( - Arguments.of(TransactionType.CONSENSUSUPDATETOPIC, recordItemBuilder.consensusUpdateTopic()), Arguments.of(TransactionType.CONSENSUSDELETETOPIC, recordItemBuilder.consensusDeleteTopic()), + Arguments.of(TransactionType.CONSENSUSUPDATETOPIC, recordItemBuilder.consensusUpdateTopic()), Arguments.of(TransactionType.FILEAPPEND, recordItemBuilder.fileAppend()), Arguments.of(TransactionType.FILEDELETE, recordItemBuilder.fileDelete()), Arguments.of(TransactionType.FILEUPDATE, recordItemBuilder.fileUpdate()), From 67de520e607a8a526520ea033e82b31fb8dc16d2 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Mon, 10 Feb 2025 16:45:05 -0500 Subject: [PATCH 11/18] Remove unused methods Signed-off-by: Edwin Greene --- .../block/BlockFileTransformerTest.java | 2 +- .../parser/domain/BlockItemBuilder.java | 24 ++----------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index 597d7367859..de2d1f7a94f 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -102,7 +102,7 @@ void defaultTransforms( TransactionType type, RecordItemBuilder.Builder>> recordItem) { var expectedRecordItem = recordItem.recordItem(r -> r.hapiVersion(HAPI_VERSION)).build(); - var blockItem = blockItemBuilder.defaultRecordItem(expectedRecordItem).build(); + var blockItem = blockItemBuilder.defaultBlockItem(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 44329661310..97758c88eb7 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -92,11 +92,6 @@ public BlockItemBuilder.Builder cryptoTransfer(RecordItem recordItem) { Collections.emptyList()); } - public BlockItemBuilder.Builder scheduleCreate() { - var recordItem = recordItemBuilder.scheduleCreate().build(); - return scheduleCreate(recordItem); - } - public BlockItemBuilder.Builder scheduleCreate(RecordItem recordItem) { var transactionRecord = recordItem.getTransactionRecord(); var scheduleId = transactionRecord.getReceipt().getScheduleID(); @@ -123,11 +118,6 @@ public BlockItemBuilder.Builder scheduleCreate(RecordItem recordItem) { List.of(stateChanges)); } - public BlockItemBuilder.Builder scheduleDelete() { - var recordItem = recordItemBuilder.scheduleDelete().build(); - return scheduleDelete(recordItem); - } - public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem) { var scheduleId = recordItem.getTransactionBody().getScheduleDelete().getScheduleID(); var stateChangeDelete = StateChange.newBuilder() @@ -156,11 +146,6 @@ public BlockItemBuilder.Builder scheduleDelete(RecordItem recordItem) { List.of(stateChanges)); } - public BlockItemBuilder.Builder scheduleSign() { - var recordItem = recordItemBuilder.scheduleSign().build(); - return scheduleSign(recordItem); - } - public BlockItemBuilder.Builder scheduleSign(RecordItem recordItem) { var transactionRecord = recordItem.getTransactionRecord(); var transactionId = transactionRecord.getReceipt().getScheduledTransactionID(); @@ -182,10 +167,10 @@ public BlockItemBuilder.Builder unknown() { } public BlockItemBuilder.Builder unknown(RecordItem recordItem) { - return defaultRecordItem(recordItem); + return defaultBlockItem(recordItem); } - public BlockItemBuilder.Builder defaultRecordItem(RecordItem recordItem) { + public BlockItemBuilder.Builder defaultBlockItem(RecordItem recordItem) { return new BlockItemBuilder.Builder( recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); } @@ -197,11 +182,6 @@ public Builder fileCreate(RecordItem recordItem) { recordItem.getTransaction(), transactionResult(recordItem), List.of(), List.of(stateChanges)); } - public Builder fileUpdate(RecordItem recordItem) { - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), transactionResult(recordItem), List.of(), Collections.emptyList()); - } - public Builder consensusCreateTopic(RecordItem recordItem) { var id = recordItem.getTransactionRecord().getReceipt().getTopicID().getTopicNum(); var topicId = TopicID.newBuilder().setTopicNum(id).build(); From 369a404485d5a926f5152df91c3e6796778648f5 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 13 Feb 2025 02:54:40 -0500 Subject: [PATCH 12/18] Update token airdrop transformer Signed-off-by: Edwin Greene --- .../AbstractBlockItemTransformer.java | 5 +- .../transformer/AbstractTokenTransformer.java | 52 +++++++ .../ConsensusCreateTopicTransformer.java | 4 +- .../ConsensusSubmitMessageTransformer.java | 4 +- .../CryptoTransferTransformer.java | 4 +- .../transformer/FileCreateTransformer.java | 4 +- .../ScheduleCreateTransformer.java | 4 +- .../transformer/ScheduleSignTransformer.java | 4 +- .../transformer/TokenAirdropTransformer.java | 129 ++++++++++++++---- .../transformer/TokenBurnTransformer.java | 29 +--- .../transformer/TokenCreateTransformer.java | 16 ++- .../transformer/TokenMintTransformer.java | 4 +- .../transformer/TokenWipeTransformer.java | 33 +---- .../block/BlockFileTransformerTest.java | 10 +- .../parser/domain/BlockItemBuilder.java | 37 +++++ .../parser/domain/RecordItemBuilder.java | 25 +++- 16 files changed, 264 insertions(+), 100 deletions(-) create mode 100644 hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java index 2ced117154c..85ba18f6753 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractBlockItemTransformer.java @@ -53,11 +53,12 @@ public TransactionRecord getTransactionRecord(BlockItem blockItem, TransactionBo transactionRecordBuilder.setScheduleRef(transactionResult.getScheduleRef()); } - updateTransactionRecord(blockItem, transactionRecordBuilder); + updateTransactionRecord(blockItem, transactionBody, transactionRecordBuilder); return transactionRecordBuilder.build(); } - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { // do nothing } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java new file mode 100644 index 00000000000..3822881ce80 --- /dev/null +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java @@ -0,0 +1,52 @@ +/* + * 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.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; +import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; + +import com.hedera.mirror.common.domain.transaction.BlockItem; +import com.hederahashgraph.api.proto.java.TransactionRecord; +import jakarta.inject.Named; + +@Named +abstract class AbstractTokenTransformer extends AbstractBlockItemTransformer { + + void updateTotalSupply(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful()) { + return; + } + + for (var stateChanges : blockItem.stateChanges()) { + for (var stateChange : stateChanges.getStateChangesList()) { + if ((stateChange.getStateId() == STATE_ID_TOKENS.getNumber() + || stateChange.getStateId() == STATE_ID_NFTS.getNumber()) + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().hasValue() + && stateChange.getMapUpdate().getValue().hasTokenValue()) { + var value = stateChange + .getMapUpdate() + .getValue() + .getTokenValue() + .getTotalSupply(); + transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(value); + return; + } + } + } + } +} diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusCreateTopicTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusCreateTopicTransformer.java index 566fd1726a5..c82ce9128b0 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusCreateTopicTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusCreateTopicTransformer.java @@ -19,13 +19,15 @@ 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.TransactionBody; 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) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusSubmitMessageTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusSubmitMessageTransformer.java index 9880d260913..df37f1acb0c 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusSubmitMessageTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ConsensusSubmitMessageTransformer.java @@ -21,6 +21,7 @@ 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.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -28,7 +29,8 @@ final class ConsensusSubmitMessageTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java index b6a1464d61f..7317409f480 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/CryptoTransferTransformer.java @@ -18,6 +18,7 @@ import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -25,7 +26,8 @@ final class CryptoTransferTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { for (var transactionOutput : blockItem.transactionOutput()) { if (transactionOutput.hasCryptoTransfer()) { var cryptoTransferOutput = transactionOutput.getCryptoTransfer(); diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/FileCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/FileCreateTransformer.java index 51ede228d5f..5a855ecb743 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/FileCreateTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/FileCreateTransformer.java @@ -19,6 +19,7 @@ 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.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -26,7 +27,8 @@ final class FileCreateTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java index b828842ed8c..30b9e9a3341 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleCreateTransformer.java @@ -20,6 +20,7 @@ import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -28,7 +29,8 @@ final class ScheduleCreateTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java index 652895400be..3d17ba95f35 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/ScheduleSignTransformer.java @@ -18,6 +18,7 @@ import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -25,7 +26,8 @@ final class ScheduleSignTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java index f22e4ce75f5..5491beb81c2 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java @@ -18,43 +18,43 @@ import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_PENDING_AIRDROPS; +import com.hedera.hapi.block.stream.output.protoc.StateChanges; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.AccountAmount; +import com.hederahashgraph.api.proto.java.AccountID; +import com.hederahashgraph.api.proto.java.NftID; +import com.hederahashgraph.api.proto.java.NftTransfer; +import com.hederahashgraph.api.proto.java.PendingAirdropId; import com.hederahashgraph.api.proto.java.PendingAirdropId.TokenReferenceCase; import com.hederahashgraph.api.proto.java.PendingAirdropRecord; +import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenTransferList; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @Named final class TokenAirdropTransformer extends AbstractBlockItemTransformer { - @SuppressWarnings("java:S3776") @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (!blockItem.successful()) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { + if (!blockItem.successful() || !transactionBody.hasTokenAirdrop()) { return; } - for (var stateChanges : blockItem.stateChanges()) { - for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.getStateId() == STATE_ID_PENDING_AIRDROPS.getNumber() && stateChange.hasMapUpdate()) { - var mapUpdate = stateChange.getMapUpdate(); - var key = mapUpdate.getKey(); - if (key.hasPendingAirdropIdKey()) { - var pendingId = key.getPendingAirdropIdKey(); - var pendingAirdrop = PendingAirdropRecord.newBuilder().setPendingAirdropId(pendingId); - if (pendingId.getTokenReferenceCase() == TokenReferenceCase.FUNGIBLE_TOKEN_TYPE) { - var value = mapUpdate.getValue(); - if (value.hasAccountPendingAirdropValue()) { - var accountValue = value.getAccountPendingAirdropValue(); - if (accountValue.hasPendingAirdropValue()) { - pendingAirdrop.setPendingAirdropValue(accountValue.getPendingAirdropValue()); - } - } - } - - transactionRecordBuilder.addNewPendingAirdrops(pendingAirdrop); - } + var pendingAirdropIds = pendingAirdropsInState(blockItem.stateChanges()); + if (!pendingAirdropIds.isEmpty()) { + var eligibleAirdropIds = + eligibleAirdropIds(transactionBody.getTokenAirdrop().getTokenTransfersList()); + for (var pendingAirdrop : pendingAirdropIds) { + // Do not add airdrops that could not appear in the transfer list + if (eligibleAirdropIds.contains(pendingAirdrop.getPendingAirdropId())) { + transactionRecordBuilder.addNewPendingAirdrops(pendingAirdrop); } } } @@ -68,6 +68,89 @@ protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Bu } } + private Set pendingAirdropsInState(List stateChangesList) { + Set pendingAirdropIds = new HashSet<>(); + for (var stateChanges : stateChangesList) { + for (var stateChange : stateChanges.getStateChangesList()) { + if (stateChange.getStateId() == STATE_ID_PENDING_AIRDROPS.getNumber() + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().hasKey() + && stateChange.getMapUpdate().getKey().hasPendingAirdropIdKey()) { + var mapUpdate = stateChange.getMapUpdate(); + var pendingId = mapUpdate.getKey().getPendingAirdropIdKey(); + var pendingAirdrop = PendingAirdropRecord.newBuilder().setPendingAirdropId(pendingId); + if (pendingId.getTokenReferenceCase() == TokenReferenceCase.FUNGIBLE_TOKEN_TYPE + && mapUpdate.getValue().hasAccountPendingAirdropValue() + && mapUpdate + .getValue() + .getAccountPendingAirdropValue() + .hasPendingAirdropValue()) { + var accountValue = mapUpdate.getValue().getAccountPendingAirdropValue(); + pendingAirdrop.setPendingAirdropValue(accountValue.getPendingAirdropValue()); + } + + pendingAirdropIds.add(pendingAirdrop.build()); + } + } + } + + return pendingAirdropIds; + } + + private Set eligibleAirdropIds(List tokenTransfers) { + var eligibleAirdrops = new HashSet(); + for (var transfer : tokenTransfers) { + var tokenId = transfer.getToken(); + var accountAmounts = transfer.getTransfersList(); + if (!accountAmounts.isEmpty()) { + eligibleFungiblePendingAirdrops(accountAmounts, tokenId, eligibleAirdrops); + } + + var nftTransfers = transfer.getNftTransfersList(); + if (!nftTransfers.isEmpty()) { + eligibleNftPendingAirdrops(nftTransfers, tokenId, eligibleAirdrops); + } + } + + return eligibleAirdrops; + } + + private void eligibleFungiblePendingAirdrops( + List accountAmounts, TokenID tokenId, Set eligibleAirdrops) { + var builder = PendingAirdropId.newBuilder().setFungibleTokenType(tokenId); + var receivers = new HashSet(); + var senders = new HashSet(); + for (var accountAmount : accountAmounts) { + if (accountAmount.hasAccountID()) { + var accountId = accountAmount.getAccountID(); + if (accountAmount.getAmount() < 0) { + senders.add(accountId); + } else { + receivers.add(accountId); + } + } + } + + for (var receiver : receivers) { + for (var sender : senders) { + eligibleAirdrops.add( + builder.setReceiverId(receiver).setSenderId(sender).build()); + } + } + } + + private void eligibleNftPendingAirdrops( + List nftTransfers, TokenID tokenId, Set eligibleAirdrops) { + for (var nftTransfer : nftTransfers) { + var nftId = NftID.newBuilder().setTokenID(tokenId).setSerialNumber(nftTransfer.getSerialNumber()); + eligibleAirdrops.add(PendingAirdropId.newBuilder() + .setNonFungibleToken(nftId) + .setReceiverId(nftTransfer.getReceiverAccountID()) + .setSenderId(nftTransfer.getSenderAccountID()) + .build()); + } + } + @Override public TransactionType getType() { return TransactionType.TOKENAIRDROP; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java index f4e3beeb30b..0049009f261 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java @@ -16,38 +16,19 @@ package com.hedera.mirror.importer.downloader.block.transformer; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; - import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @Named -final class TokenBurnTransformer extends AbstractBlockItemTransformer { +final class TokenBurnTransformer extends AbstractTokenTransformer { - @SuppressWarnings("java:S3776") @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (!blockItem.successful()) { - return; - } - - for (var stateChanges : blockItem.stateChanges()) { - for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() && stateChange.hasMapUpdate()) { - var mapUpdate = stateChange.getMapUpdate(); - if (mapUpdate.getKey().hasTokenIdKey()) { - var value = mapUpdate.getValue(); - if (value.hasTokenValue()) { - var totalSupply = value.getTokenValue().getTotalSupply(); - transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(totalSupply); - return; - } - } - } - } - } + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { + updateTotalSupply(blockItem, transactionRecordBuilder); } @Override diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java index 3817154a298..fcbdce23091 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenCreateTransformer.java @@ -20,6 +20,7 @@ import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -27,19 +28,20 @@ final class TokenCreateTransformer extends AbstractBlockItemTransformer { @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; } for (var stateChanges : blockItem.stateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() && stateChange.hasMapUpdate()) { - var key = stateChange.getMapUpdate().getKey(); - if (key.hasTokenIdKey()) { - transactionRecordBuilder.getReceiptBuilder().setTokenID(key.getTokenIdKey()); - return; - } + if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().getKey().hasTokenIdKey()) { + var key = stateChange.getMapUpdate().getKey().getTokenIdKey(); + transactionRecordBuilder.getReceiptBuilder().setTokenID(key); + return; } } } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java index 6c3c3454458..1142d03e0c0 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -22,6 +22,7 @@ import com.hedera.hapi.block.stream.output.protoc.MapUpdateChange; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionReceipt; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -31,7 +32,8 @@ final class TokenMintTransformer extends AbstractBlockItemTransformer { @SuppressWarnings("java:S3776") @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { if (!blockItem.successful()) { return; } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java index efeba936659..0ff9a862966 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java @@ -16,42 +16,19 @@ package com.hedera.mirror.importer.downloader.block.transformer; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; - import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; +import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @Named -final class TokenWipeTransformer extends AbstractBlockItemTransformer { +final class TokenWipeTransformer extends AbstractTokenTransformer { - @SuppressWarnings("java:S3776") @Override - protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (!blockItem.successful()) { - return; - } - - var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); - for (var stateChanges : blockItem.stateChanges()) { - for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.hasMapUpdate() - && (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() - || stateChange.getStateId() == STATE_ID_NFTS.getNumber())) { - var mapUpdate = stateChange.getMapUpdate(); - if (mapUpdate.hasValue()) { - var value = mapUpdate.getValue(); - if (value.hasTokenValue()) { - receiptBuilder.setNewTotalSupply( - value.getTokenValue().getTotalSupply()); - return; - } - } - } - } - } + protected void updateTransactionRecord( + BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { + updateTotalSupply(blockItem, transactionRecordBuilder); } @Override diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index de2d1f7a94f..bfbfca89d44 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -35,6 +35,7 @@ import com.hedera.mirror.importer.parser.domain.BlockItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder.TransferType; +import com.hederahashgraph.api.proto.java.PendingAirdropRecord; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.ScheduleID; import com.hederahashgraph.api.proto.java.SignedTransaction; @@ -602,10 +603,10 @@ void tokenAirdrop() { .returns(expectedTransactionHash, TransactionRecord::getTransactionHash) .returns(expectedFees, TransactionRecord::getAssessedCustomFeesList) .extracting(TransactionRecord::getNewPendingAirdropsList) - .returns( - expectedFungibleKey, airdrops -> airdrops.getFirst().getPendingAirdropId()) - .returns(expectedAmount, airdrops -> airdrops.getFirst().getPendingAirdropValue()) - .returns(expectedNftKey, airdrops -> airdrops.get(1).getPendingAirdropId()); + .satisfies((Consumer>) airdrops -> assertThat(airdrops) + .hasSize(2) + .containsExactlyInAnyOrderElementsOf( + expectedRecordItem.getTransactionRecord().getNewPendingAirdropsList())); }); } @@ -913,6 +914,7 @@ private void assertRecordItem(RecordItem recordItem, RecordItem expectedRecordIt "transactionRecord.receipt_.scheduleID_.memoizedIsInitialized", "transactionRecord.receipt_.scheduleID_.memoizedSize", "transactionRecord.assessedCustomFees_", + "transactionRecord.newPendingAirdrops_", "transactionRecord.parentConsensusTimestamp_", // Record file builder transaction hash is not generated based on transaction bytes, so these // will not match diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 97758c88eb7..4425bf81ae7 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -42,6 +42,8 @@ import com.hederahashgraph.api.proto.java.AssessedCustomFee; import com.hederahashgraph.api.proto.java.FileID; import com.hederahashgraph.api.proto.java.NftID; +import com.hederahashgraph.api.proto.java.PendingAirdropId; +import com.hederahashgraph.api.proto.java.PendingAirdropValue; import com.hederahashgraph.api.proto.java.Schedule; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Token; @@ -268,6 +270,41 @@ public Builder tokenAirdrop(RecordItem recordItem) { .build()) .build()); } + + // Add state changes that are not reflected in the possible state changes from the transaction body + changes.add(StateChange.newBuilder() + .setStateId(STATE_ID_PENDING_AIRDROPS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setPendingAirdropIdKey(PendingAirdropId.newBuilder() + .setFungibleTokenType(recordItemBuilder.tokenId()) + .setReceiverId(recordItemBuilder.accountId()) + .setSenderId(recordItemBuilder.accountId()) + .build())) + .setValue(MapChangeValue.newBuilder() + .setAccountPendingAirdropValue(AccountPendingAirdrop.newBuilder() + .setPendingAirdropValue(PendingAirdropValue.newBuilder() + .setAmount(1) + .build()) + .build()) + .build()) + .build()) + .build()); + changes.add(StateChange.newBuilder() + .setStateId(STATE_ID_PENDING_AIRDROPS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setPendingAirdropIdKey(PendingAirdropId.newBuilder() + .setNonFungibleToken(NftID.newBuilder() + .setTokenID(recordItemBuilder.tokenId()) + .setSerialNumber(5000) + .build()) + .setReceiverId(recordItemBuilder.accountId()) + .setSenderId(recordItemBuilder.accountId()) + .build())) + .build()) + .build()); + var stateChanges = StateChanges.newBuilder().addAllStateChanges(changes).build(); var transactionOutput = TransactionOutput.newBuilder() diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index dc9842207a9..0802da0ffff 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -883,11 +883,26 @@ public Builder tokenAirdrop() { .build()); var nftPendingAirdrop = PendingAirdropRecord.newBuilder().setPendingAirdropId(nftPendingAirdropId); - return new Builder<>(TransactionType.TOKENAIRDROP, TokenAirdropTransactionBody.newBuilder()) - .record(r -> r.addTokenTransferLists(tokenTransferList) - .addTokenTransferLists(nftTransferList) - .addNewPendingAirdrops(fungiblePendingAirdrop) - .addNewPendingAirdrops(nftPendingAirdrop)); + var body = TokenAirdropTransactionBody.newBuilder(); + var builder = new Builder<>(TransactionType.TOKENAIRDROP, body); + var tokenTransfers = TokenTransferList.newBuilder() + .setToken(fungibleTokenId) + .addTransfers(accountAmount(sender, -100)) + .addTransfers(accountAmount(pendingReceiver, 100)); + body.addTokenTransfers(tokenTransfers); + + var nftTransfers = TokenTransferList.newBuilder() + .setToken(nftTokenId) + .addNftTransfers(NftTransfer.newBuilder() + .setSenderAccountID(sender) + .setReceiverAccountID(pendingReceiver) + .setSerialNumber(1)); + body.addTokenTransfers(nftTransfers); + + return builder.record(r -> r.addTokenTransferLists(tokenTransferList) + .addTokenTransferLists(nftTransferList) + .addNewPendingAirdrops(fungiblePendingAirdrop) + .addNewPendingAirdrops(nftPendingAirdrop)); } public Builder tokenCancelAirdrop() { From 7de24fe389094430192cf5401e7a278501147d7c Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 13 Feb 2025 13:08:17 -0500 Subject: [PATCH 13/18] Update new total supply transformer handling Signed-off-by: Edwin Greene --- .../transformer/AbstractTokenTransformer.java | 27 ++-- .../transformer/TokenMintTransformer.java | 42 ++--- .../parser/domain/BlockItemBuilder.java | 144 +++++------------- 3 files changed, 68 insertions(+), 145 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java index 3822881ce80..472f0535466 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java @@ -16,9 +16,9 @@ package com.hedera.mirror.importer.downloader.block.transformer; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; +import com.hedera.hapi.block.stream.output.protoc.StateChange; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @@ -33,20 +33,23 @@ void updateTotalSupply(BlockItem blockItem, TransactionRecord.Builder transactio for (var stateChanges : blockItem.stateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { - if ((stateChange.getStateId() == STATE_ID_TOKENS.getNumber() - || stateChange.getStateId() == STATE_ID_NFTS.getNumber()) - && stateChange.hasMapUpdate() - && stateChange.getMapUpdate().hasValue() - && stateChange.getMapUpdate().getValue().hasTokenValue()) { - var value = stateChange - .getMapUpdate() - .getValue() - .getTokenValue() - .getTotalSupply(); - transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(value); + if (hasSupplyUpdate(stateChange)) { + updateTotalSupply(stateChange, transactionRecordBuilder); return; } } } } + + boolean hasSupplyUpdate(StateChange stateChange) { + return stateChange.getStateId() == STATE_ID_TOKENS.getNumber() + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().hasValue() + && stateChange.getMapUpdate().getValue().hasTokenValue(); + } + + void updateTotalSupply(StateChange stateChange, TransactionRecord.Builder transactionRecordBuilder) { + var value = stateChange.getMapUpdate().getValue().getTokenValue().getTotalSupply(); + transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(value); + } } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java index 1142d03e0c0..9dec0f31914 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -17,20 +17,17 @@ package com.hedera.mirror.importer.downloader.block.transformer; import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; -import com.hedera.hapi.block.stream.output.protoc.MapUpdateChange; +import com.hedera.hapi.block.stream.output.protoc.StateChange; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; import com.hederahashgraph.api.proto.java.TransactionBody; -import com.hederahashgraph.api.proto.java.TransactionReceipt; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; @Named -final class TokenMintTransformer extends AbstractBlockItemTransformer { +final class TokenMintTransformer extends AbstractTokenTransformer { - @SuppressWarnings("java:S3776") @Override protected void updateTransactionRecord( BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { @@ -38,37 +35,24 @@ protected void updateTransactionRecord( return; } - var receiptBuilder = transactionRecordBuilder.getReceiptBuilder(); for (var stateChanges : blockItem.stateChanges()) { for (var stateChange : stateChanges.getStateChangesList()) { - if (stateChange.hasMapUpdate()) { - var mapUpdate = stateChange.getMapUpdate(); - if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber()) { - if (setSupply(mapUpdate, receiptBuilder)) { - return; - } - } else if (stateChange.getStateId() == STATE_ID_NFTS.getNumber()) { - var key = mapUpdate.getKey(); - if (key.hasNftIdKey()) { - receiptBuilder.addSerialNumbers(key.getNftIdKey().getSerialNumber()); - setSupply(mapUpdate, receiptBuilder); - } - } + if (hasSupplyUpdate(stateChange)) { + updateTotalSupply(stateChange, transactionRecordBuilder); + } else if (hasSerialUpdate(stateChange)) { + var serialNumber = + stateChange.getMapUpdate().getKey().getNftIdKey().getSerialNumber(); + transactionRecordBuilder.getReceiptBuilder().addSerialNumbers(serialNumber); } } } } - private boolean setSupply(MapUpdateChange mapUpdate, TransactionReceipt.Builder receiptBuilder) { - if (mapUpdate.hasValue()) { - var value = mapUpdate.getValue(); - if (value.hasTokenValue()) { - receiptBuilder.setNewTotalSupply(value.getTokenValue().getTotalSupply()); - return true; - } - } - - return false; + private boolean hasSerialUpdate(StateChange stateChange) { + return stateChange.getStateId() == STATE_ID_NFTS.getNumber() + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().hasKey() + && stateChange.getMapUpdate().getKey().hasNftIdKey(); } @Override diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java index 4425bf81ae7..9ebbe740f0f 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/BlockItemBuilder.java @@ -47,6 +47,7 @@ import com.hederahashgraph.api.proto.java.Schedule; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Token; +import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.Topic; import com.hederahashgraph.api.proto.java.TopicID; import com.hederahashgraph.api.proto.java.Transaction; @@ -321,23 +322,7 @@ public Builder tokenAirdrop(RecordItem recordItem) { } public Builder tokenBurn(RecordItem recordItem) { - var transactionRecord = recordItem.getTransactionRecord(); - var receipt = transactionRecord.getReceipt(); - var tokenId = receipt.getTokenID(); - var supply = receipt.getNewTotalSupply(); - var stateChange = StateChange.newBuilder() - .setStateId(STATE_ID_TOKENS.getNumber()) - .setMapUpdate(MapUpdateChange.newBuilder() - .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) - .setValue(MapChangeValue.newBuilder() - .setTokenValue(Token.newBuilder().setTotalSupply(supply))) - .build()) - .build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), - transactionResult(recordItem), - Collections.emptyList(), - List.of(StateChanges.newBuilder().addStateChanges(stateChange).build())); + return tokenSupplyStateChanges(recordItem, List.of()); } public Builder tokenCreate(RecordItem recordItem) { @@ -358,97 +343,21 @@ public Builder tokenCreate(RecordItem recordItem) { } public Builder tokenMint(RecordItem recordItem) { - var transactionRecord = recordItem.getTransactionRecord(); - var receipt = transactionRecord.getReceipt(); - var tokenId = receipt.getTokenID(); - var serialNumbers = receipt.getSerialNumbersList(); - var supply = receipt.getNewTotalSupply(); - if (serialNumbers.isEmpty()) { - var stateChange = StateChange.newBuilder() - .setStateId(STATE_ID_TOKENS.getNumber()) - .setMapUpdate(MapUpdateChange.newBuilder() - .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) - .setValue(MapChangeValue.newBuilder() - .setTokenValue(Token.newBuilder().setTotalSupply(supply))) - .build()) - .build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), - transactionResult(recordItem), - Collections.emptyList(), - List.of(StateChanges.newBuilder() - .addStateChanges(stateChange) - .build())); - } else { - var stateChangesBuilder = StateChanges.newBuilder(); - for (int i = 0; i < serialNumbers.size(); i++) { - var mapUpdate = MapUpdateChange.newBuilder() - .setKey(MapChangeKey.newBuilder() - .setNftIdKey(NftID.newBuilder() - .setTokenID(tokenId) - .setSerialNumber(serialNumbers.get(i)) - .build())); - - if (i == serialNumbers.size() - 1) { - mapUpdate.setValue(MapChangeValue.newBuilder() - .setTokenValue(Token.newBuilder().setTotalSupply(supply)) - .build()); - } - stateChangesBuilder.addStateChanges(StateChange.newBuilder() - .setStateId(STATE_ID_NFTS.getNumber()) - .setMapUpdate(mapUpdate.build())); - } - - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), - transactionResult(recordItem), - Collections.emptyList(), - List.of(stateChangesBuilder.build())); - } + var serialNumbers = recordItem.getTransactionRecord().getReceipt().getSerialNumbersList(); + return tokenSupplyStateChanges(recordItem, serialNumbers); } public Builder tokenWipe(RecordItem recordItem) { - var transactionRecord = recordItem.getTransactionRecord(); - var transactionBody = recordItem.getTransactionBody(); - var receipt = transactionRecord.getReceipt(); - var tokenId = receipt.getTokenID(); - var supply = receipt.getNewTotalSupply(); - - var serialNumbers = transactionBody.getTokenWipe().getSerialNumbersList(); - if (serialNumbers.isEmpty()) { - var stateChange = StateChange.newBuilder() - .setStateId(STATE_ID_TOKENS.getNumber()) - .setMapUpdate(MapUpdateChange.newBuilder() - .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) - .setValue(MapChangeValue.newBuilder() - .setTokenValue(Token.newBuilder().setTotalSupply(supply))) - .build()) - .build(); - return new BlockItemBuilder.Builder( - recordItem.getTransaction(), - transactionResult(recordItem), - Collections.emptyList(), - List.of(StateChanges.newBuilder() - .addStateChanges(stateChange) - .build())); - } - - var stateChangesBuilder = StateChanges.newBuilder(); - for (var serialNumber : serialNumbers) { - var mapUpdate = MapUpdateChange.newBuilder() - .setKey(MapChangeKey.newBuilder() - .setNftIdKey(NftID.newBuilder() - .setTokenID(tokenId) - .setSerialNumber(serialNumber) - .build())) - .setValue(MapChangeValue.newBuilder() - .setTokenValue(Token.newBuilder().setTotalSupply(supply))) - .build(); - stateChangesBuilder.addStateChanges(StateChange.newBuilder() - .setStateId(STATE_ID_NFTS.getNumber()) - .setMapUpdate(mapUpdate)); - } + var serialNumbers = recordItem.getTransactionBody().getTokenWipe().getSerialNumbersList(); + return tokenSupplyStateChanges(recordItem, serialNumbers); + } + private Builder tokenSupplyStateChanges(RecordItem recordItem, List serialNumbers) { + var receipt = recordItem.getTransactionRecord().getReceipt(); + var tokenId = receipt.getTokenID(); + var stateChangesBuilder = + StateChanges.newBuilder().addStateChanges(getNewSupplyState(tokenId, receipt.getNewTotalSupply())); + stateChangesBuilder.addAllStateChanges(getSerialNumbersStateChanges(serialNumbers, tokenId)); return new BlockItemBuilder.Builder( recordItem.getTransaction(), transactionResult(recordItem), @@ -456,6 +365,33 @@ public Builder tokenWipe(RecordItem recordItem) { List.of(stateChangesBuilder.build())); } + private List getSerialNumbersStateChanges(List serialNumbers, TokenID tokenId) { + return serialNumbers.stream() + .map(serialNumber -> MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder() + .setNftIdKey(NftID.newBuilder() + .setTokenID(tokenId) + .setSerialNumber(serialNumber) + .build())) + .build()) + .map(mapUpdate -> StateChange.newBuilder() + .setStateId(STATE_ID_NFTS.getNumber()) + .setMapUpdate(mapUpdate) + .build()) + .toList(); + } + + private StateChange getNewSupplyState(TokenID tokenId, long newTotalSupply) { + return StateChange.newBuilder() + .setStateId(STATE_ID_TOKENS.getNumber()) + .setMapUpdate(MapUpdateChange.newBuilder() + .setKey(MapChangeKey.newBuilder().setTokenIdKey(tokenId)) + .setValue(MapChangeValue.newBuilder() + .setTokenValue(Token.newBuilder().setTotalSupply(newTotalSupply))) + .build()) + .build(); + } + private static StateChanges buildFileIdStateChanges(RecordItem recordItem) { var id = recordItem.getTransactionRecord().getReceipt().getFileID().getFileNum(); var fileId = FileID.newBuilder().setFileNum(id).build(); From 1408067f8c0e8f46691a3ed525929569e5213753 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 13 Feb 2025 13:50:09 -0500 Subject: [PATCH 14/18] Fix sonar Signed-off-by: Edwin Greene --- .../downloader/block/BlockFileTransformerTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index a594de38224..60b666a31ca 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -57,7 +57,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.data.util.Version; @@ -720,13 +719,6 @@ void tokenAirdrop() { var blockItem = blockItemBuilder.tokenAirdrop(expectedRecordItem).build(); var expectedFees = blockItem.transactionOutput().getFirst().getTokenAirdrop().getAssessedCustomFeesList(); - var pendingList = blockItem.stateChanges().getFirst().getStateChangesList(); - var mapUpdate = pendingList.getFirst().getMapUpdate(); - var expectedFungibleKey = mapUpdate.getKey().getPendingAirdropIdKey(); - var expectedAmount = - mapUpdate.getValue().getAccountPendingAirdropValue().getPendingAirdropValue(); - var expectedNftKey = pendingList.get(1).getMapUpdate().getKey().getPendingAirdropIdKey(); - var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); // when @@ -886,7 +878,9 @@ void tokenCreateUnsuccessful() { } @ParameterizedTest - @EnumSource(value = TokenType.class, mode = Mode.EXCLUDE, names = "UNRECOGNIZED") + @EnumSource( + value = TokenType.class, + names = {"FUNGIBLE_COMMON", "NON_FUNGIBLE_UNIQUE"}) void tokenMint(TokenType type) { // given var expectedRecordItem = recordItemBuilder From 12cd6ea02903dc9265650df06df95de1308b9878 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 13 Feb 2025 15:51:05 -0500 Subject: [PATCH 15/18] Update serial number handling Signed-off-by: Edwin Greene --- .../transformer/AbstractTokenTransformer.java | 36 ++++++-------- .../transformer/TokenAirdropTransformer.java | 49 +++++++++---------- .../transformer/TokenBurnTransformer.java | 6 ++- .../transformer/TokenMintTransformer.java | 25 +++------- .../transformer/TokenWipeTransformer.java | 6 ++- .../parser/domain/RecordItemBuilder.java | 9 +++- 6 files changed, 60 insertions(+), 71 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java index 472f0535466..0cb34cadff6 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java @@ -18,38 +18,30 @@ import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_TOKENS; -import com.hedera.hapi.block.stream.output.protoc.StateChange; -import com.hedera.mirror.common.domain.transaction.BlockItem; +import com.hedera.hapi.block.stream.output.protoc.StateChanges; import com.hederahashgraph.api.proto.java.TransactionRecord; import jakarta.inject.Named; +import java.util.List; @Named abstract class AbstractTokenTransformer extends AbstractBlockItemTransformer { - void updateTotalSupply(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) { - if (!blockItem.successful()) { - return; - } - - for (var stateChanges : blockItem.stateChanges()) { + void updateTotalSupply(List stateChangesList, TransactionRecord.Builder transactionRecordBuilder) { + for (var stateChanges : stateChangesList) { for (var stateChange : stateChanges.getStateChangesList()) { - if (hasSupplyUpdate(stateChange)) { - updateTotalSupply(stateChange, transactionRecordBuilder); + if (stateChange.getStateId() == STATE_ID_TOKENS.getNumber() + && stateChange.hasMapUpdate() + && stateChange.getMapUpdate().hasValue() + && stateChange.getMapUpdate().getValue().hasTokenValue()) { + var value = stateChange + .getMapUpdate() + .getValue() + .getTokenValue() + .getTotalSupply(); + transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(value); return; } } } } - - boolean hasSupplyUpdate(StateChange stateChange) { - return stateChange.getStateId() == STATE_ID_TOKENS.getNumber() - && stateChange.hasMapUpdate() - && stateChange.getMapUpdate().hasValue() - && stateChange.getMapUpdate().getValue().hasTokenValue(); - } - - void updateTotalSupply(StateChange stateChange, TransactionRecord.Builder transactionRecordBuilder) { - var value = stateChange.getMapUpdate().getValue().getTokenValue().getTotalSupply(); - transactionRecordBuilder.getReceiptBuilder().setNewTotalSupply(value); - } } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java index 5491beb81c2..d6e4cdb7bf0 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java @@ -74,13 +74,12 @@ private Set pendingAirdropsInState(List stat for (var stateChange : stateChanges.getStateChangesList()) { if (stateChange.getStateId() == STATE_ID_PENDING_AIRDROPS.getNumber() && stateChange.hasMapUpdate() - && stateChange.getMapUpdate().hasKey() - && stateChange.getMapUpdate().getKey().hasPendingAirdropIdKey()) { + && stateChange.getMapUpdate().getKey().hasPendingAirdropIdKey() + && stateChange.getMapUpdate().getValue().hasAccountPendingAirdropValue()) { var mapUpdate = stateChange.getMapUpdate(); var pendingId = mapUpdate.getKey().getPendingAirdropIdKey(); var pendingAirdrop = PendingAirdropRecord.newBuilder().setPendingAirdropId(pendingId); if (pendingId.getTokenReferenceCase() == TokenReferenceCase.FUNGIBLE_TOKEN_TYPE - && mapUpdate.getValue().hasAccountPendingAirdropValue() && mapUpdate .getValue() .getAccountPendingAirdropValue() @@ -101,40 +100,36 @@ private Set eligibleAirdropIds(List tokenTr var eligibleAirdrops = new HashSet(); for (var transfer : tokenTransfers) { var tokenId = transfer.getToken(); - var accountAmounts = transfer.getTransfersList(); - if (!accountAmounts.isEmpty()) { - eligibleFungiblePendingAirdrops(accountAmounts, tokenId, eligibleAirdrops); - } - - var nftTransfers = transfer.getNftTransfersList(); - if (!nftTransfers.isEmpty()) { - eligibleNftPendingAirdrops(nftTransfers, tokenId, eligibleAirdrops); - } + eligibleFungiblePendingAirdrops(transfer.getTransfersList(), tokenId, eligibleAirdrops); + eligibleNftPendingAirdrops(transfer.getNftTransfersList(), tokenId, eligibleAirdrops); } return eligibleAirdrops; } + @SuppressWarnings("java:S3776") private void eligibleFungiblePendingAirdrops( List accountAmounts, TokenID tokenId, Set eligibleAirdrops) { - var builder = PendingAirdropId.newBuilder().setFungibleTokenType(tokenId); - var receivers = new HashSet(); - var senders = new HashSet(); - for (var accountAmount : accountAmounts) { - if (accountAmount.hasAccountID()) { - var accountId = accountAmount.getAccountID(); - if (accountAmount.getAmount() < 0) { - senders.add(accountId); - } else { - receivers.add(accountId); + if (!accountAmounts.isEmpty()) { + var builder = PendingAirdropId.newBuilder().setFungibleTokenType(tokenId); + var receivers = new HashSet(); + var senders = new HashSet(); + for (var accountAmount : accountAmounts) { + if (accountAmount.hasAccountID()) { + var accountId = accountAmount.getAccountID(); + if (accountAmount.getAmount() < 0) { + senders.add(accountId); + } else { + receivers.add(accountId); + } } } - } - for (var receiver : receivers) { - for (var sender : senders) { - eligibleAirdrops.add( - builder.setReceiverId(receiver).setSenderId(sender).build()); + for (var receiver : receivers) { + for (var sender : senders) { + eligibleAirdrops.add( + builder.setReceiverId(receiver).setSenderId(sender).build()); + } } } } diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java index 0049009f261..9dd1fea372f 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenBurnTransformer.java @@ -28,7 +28,11 @@ final class TokenBurnTransformer extends AbstractTokenTransformer { @Override protected void updateTransactionRecord( BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { - updateTotalSupply(blockItem, transactionRecordBuilder); + if (!blockItem.successful()) { + return; + } + + updateTotalSupply(blockItem.stateChanges(), transactionRecordBuilder); } @Override diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java index 9dec0f31914..014a4ec8993 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenMintTransformer.java @@ -16,9 +16,6 @@ package com.hedera.mirror.importer.downloader.block.transformer; -import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_NFTS; - -import com.hedera.hapi.block.stream.output.protoc.StateChange; import com.hedera.mirror.common.domain.transaction.BlockItem; import com.hedera.mirror.common.domain.transaction.TransactionType; import com.hederahashgraph.api.proto.java.TransactionBody; @@ -35,26 +32,16 @@ protected void updateTransactionRecord( return; } - for (var stateChanges : blockItem.stateChanges()) { - for (var stateChange : stateChanges.getStateChangesList()) { - if (hasSupplyUpdate(stateChange)) { - updateTotalSupply(stateChange, transactionRecordBuilder); - } else if (hasSerialUpdate(stateChange)) { - var serialNumber = - stateChange.getMapUpdate().getKey().getNftIdKey().getSerialNumber(); - transactionRecordBuilder.getReceiptBuilder().addSerialNumbers(serialNumber); - } + updateTotalSupply(blockItem.stateChanges(), transactionRecordBuilder); + + var tokenTransferLists = blockItem.transactionResult().getTokenTransferListsList(); + for (var tokenTransferList : tokenTransferLists) { + for (var nftTransfer : tokenTransferList.getNftTransfersList()) { + transactionRecordBuilder.getReceiptBuilder().addSerialNumbers(nftTransfer.getSerialNumber()); } } } - private boolean hasSerialUpdate(StateChange stateChange) { - return stateChange.getStateId() == STATE_ID_NFTS.getNumber() - && stateChange.hasMapUpdate() - && stateChange.getMapUpdate().hasKey() - && stateChange.getMapUpdate().getKey().hasNftIdKey(); - } - @Override public TransactionType getType() { return TransactionType.TOKENMINT; diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java index 0ff9a862966..cf0887f4523 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenWipeTransformer.java @@ -28,7 +28,11 @@ final class TokenWipeTransformer extends AbstractTokenTransformer { @Override protected void updateTransactionRecord( BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { - updateTotalSupply(blockItem, transactionRecordBuilder); + if (!blockItem.successful()) { + return; + } + + updateTotalSupply(blockItem.stateChanges(), transactionRecordBuilder); } @Override diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index 0802da0ffff..f8be3fcb24a 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -957,7 +957,8 @@ public Builder tokenMint() { } public Builder tokenMint(TokenType tokenType) { - var transactionBody = TokenMintTransactionBody.newBuilder().setToken(tokenId()); + var tokenId = tokenId(); + var transactionBody = TokenMintTransactionBody.newBuilder().setToken(tokenId); var builder = new Builder<>(TransactionType.TOKENMINT, transactionBody); if (tokenType == FUNGIBLE_COMMON) { @@ -966,6 +967,12 @@ public Builder tokenMint(TokenType tokenType) } else { transactionBody.addMetadata(bytes(16)).addMetadata(bytes(16)); builder.receipt(b -> b.addSerialNumbers(1L).addSerialNumbers(2L).setNewTotalSupply(3L)); + var tokenTransferList = TokenTransferList.newBuilder() + .setToken(tokenId) + .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(1L)) + .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(2L)) + .build(); + builder.record(r -> r.addAllTokenTransferLists(List.of(tokenTransferList))); } return builder; From 7a2f8dc97baaf62c25e6d7f681f71c2e1d53af9c Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 13 Feb 2025 17:04:08 -0500 Subject: [PATCH 16/18] Isolate token transfers lists change to single test Signed-off-by: Edwin Greene --- .../block/BlockFileTransformerTest.java | 19 +++++++++++++++---- .../parser/domain/RecordItemBuilder.java | 9 +-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java index d5c59d43391..60e372a0456 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/downloader/block/BlockFileTransformerTest.java @@ -38,11 +38,13 @@ import com.hedera.mirror.importer.parser.domain.RecordItemBuilder; import com.hedera.mirror.importer.parser.domain.RecordItemBuilder.TransferType; import com.hederahashgraph.api.proto.java.AccountID; +import com.hederahashgraph.api.proto.java.NftTransfer; import com.hederahashgraph.api.proto.java.PendingAirdropRecord; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.ScheduleID; import com.hederahashgraph.api.proto.java.SignedTransaction; import com.hederahashgraph.api.proto.java.TokenID; +import com.hederahashgraph.api.proto.java.TokenTransferList; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionID; @@ -963,10 +965,19 @@ void tokenCreateUnsuccessful() { names = {"FUNGIBLE_COMMON", "NON_FUNGIBLE_UNIQUE"}) void tokenMint(TokenType type) { // given - var expectedRecordItem = recordItemBuilder - .tokenMint(type) - .recordItem(r -> r.hapiVersion(HAPI_VERSION)) - .build(); + var builder = recordItemBuilder.tokenMint(type); + if (type.equals(TokenType.NON_FUNGIBLE_UNIQUE)) { + var tokenId = builder.build().getTransactionBody().getTokenMint().getToken(); + var tokenTransferList = TokenTransferList.newBuilder() + .setToken(tokenId) + .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(1L)) + .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(2L)) + .build(); + builder.record(r -> r.addAllTokenTransferLists(List.of(tokenTransferList))); + } + + var expectedRecordItem = + builder.recordItem(r -> r.hapiVersion(HAPI_VERSION)).build(); var expectedTransactionHash = getExpectedTransactionHash(expectedRecordItem); var blockItem = blockItemBuilder.tokenMint(expectedRecordItem).build(); var blockFile = blockFileBuilder.items(List.of(blockItem)).build(); diff --git a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java index 730931fbf13..087f73c221f 100644 --- a/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java +++ b/hedera-mirror-importer/src/test/java/com/hedera/mirror/importer/parser/domain/RecordItemBuilder.java @@ -970,8 +970,7 @@ public Builder tokenMint() { } public Builder tokenMint(TokenType tokenType) { - var tokenId = tokenId(); - var transactionBody = TokenMintTransactionBody.newBuilder().setToken(tokenId); + var transactionBody = TokenMintTransactionBody.newBuilder().setToken(tokenId()); var builder = new Builder<>(TransactionType.TOKENMINT, transactionBody); if (tokenType == FUNGIBLE_COMMON) { @@ -980,12 +979,6 @@ public Builder tokenMint(TokenType tokenType) } else { transactionBody.addMetadata(bytes(16)).addMetadata(bytes(16)); builder.receipt(b -> b.addSerialNumbers(1L).addSerialNumbers(2L).setNewTotalSupply(3L)); - var tokenTransferList = TokenTransferList.newBuilder() - .setToken(tokenId) - .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(1L)) - .addNftTransfers(NftTransfer.newBuilder().setSerialNumber(2L)) - .build(); - builder.record(r -> r.addAllTokenTransferLists(List.of(tokenTransferList))); } return builder; From 8cf87686aedcd34b17258a3234f1ac25fca39d37 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Fri, 14 Feb 2025 09:27:33 -0500 Subject: [PATCH 17/18] Update to remove unnecessary if clause Signed-off-by: Edwin Greene --- .../downloader/block/transformer/TokenAirdropTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java index d6e4cdb7bf0..f85921b425c 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/TokenAirdropTransformer.java @@ -43,7 +43,7 @@ final class TokenAirdropTransformer extends AbstractBlockItemTransformer { @Override protected void updateTransactionRecord( BlockItem blockItem, TransactionBody transactionBody, TransactionRecord.Builder transactionRecordBuilder) { - if (!blockItem.successful() || !transactionBody.hasTokenAirdrop()) { + if (!blockItem.successful()) { return; } From 3d263c824cdc6e62711a7cbbe365703f8f61c384 Mon Sep 17 00:00:00 2001 From: Edwin Greene Date: Thu, 20 Feb 2025 12:33:49 -0500 Subject: [PATCH 18/18] Remove unneeded annotation Signed-off-by: Edwin Greene --- .../downloader/block/transformer/AbstractTokenTransformer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java index 0cb34cadff6..7eb39ab97df 100644 --- a/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java +++ b/hedera-mirror-importer/src/main/java/com/hedera/mirror/importer/downloader/block/transformer/AbstractTokenTransformer.java @@ -20,10 +20,8 @@ import com.hedera.hapi.block.stream.output.protoc.StateChanges; import com.hederahashgraph.api.proto.java.TransactionRecord; -import jakarta.inject.Named; import java.util.List; -@Named abstract class AbstractTokenTransformer extends AbstractBlockItemTransformer { void updateTotalSupply(List stateChangesList, TransactionRecord.Builder transactionRecordBuilder) {