Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIP-1056 Account BlockItem to RecordItem transformers #10249

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.mirror.importer.downloader.block.transformer;

import com.hedera.mirror.common.domain.transaction.TransactionType;
import jakarta.inject.Named;

@Named
final class CryptoAddLiveHashTransformer extends AbstractBlockItemTransformer {
steven-sheehy marked this conversation as resolved.
Show resolved Hide resolved

@Override
public TransactionType getType() {
return TransactionType.CRYPTOADDLIVEHASH;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.mirror.importer.downloader.block.transformer;

import com.hedera.mirror.common.domain.transaction.TransactionType;
import com.hederahashgraph.api.proto.java.*;
steven-sheehy marked this conversation as resolved.
Show resolved Hide resolved
import jakarta.inject.Named;

@Named
final class CryptoApproveAllowanceTransformer extends AbstractBlockItemTransformer {

@Override
public TransactionType getType() {
return TransactionType.CRYPTOAPPROVEALLOWANCE;
}
}
Original file line number Diff line number Diff line change
@@ -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_ACCOUNTS;
import static com.hedera.hapi.block.stream.output.protoc.StateIdentifier.STATE_ID_ALIASES;
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 CryptoCreateTransformer extends AbstractBlockItemTransformer {

@Override
protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) {
if (blockItem.transactionResult().getStatus() != SUCCESS) {
Copy link
Member

Choose a reason for hiding this comment

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

We'll want to update this with the new blockItem.successful() method from #10248 once it's merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will update it once the PR is merged.

return;
}

for (var stateChanges : blockItem.stateChanges()) {
for (var stateChange : stateChanges.getStateChangesList()) {
if (stateChange.getStateId() == STATE_ID_ACCOUNTS.getNumber() && stateChange.hasMapUpdate()) {
var value = stateChange.getMapUpdate().getValue();
if (value.hasAccountIdValue()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The key issue here is any account with state changes will be here, with exact same state id, change case, key case, and value case.

It's very common for any transaction having at least the following account map update in state changes

  • node account id
  • fee collector account id, 0.0.98
  • staking reward account 0.0.800
  • node reward account 0.0.801

There isn't a way for us to confidently identify the new account. Using the key in the transaction body can work in most cases however the network allows many accounts to use the same key so it's not a 100% reliable method.

We may have to ask for a change to add the new account id to CreateAccountOutput

Copy link
Member

Choose a reason for hiding this comment

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

@edwin-greene Please chase this down with the relevant teams

Copy link
Member

Choose a reason for hiding this comment

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

I agree that this is a miss in the protobuf definitions.
Suggested resolution:

  1. CreateAccountOutput should be added to TransactionOutput
  2. The created AccountID should be added to CreateAccountOutput
  3. The Account ID newly assigned for any account that is created (via CryptoCreate, EVM CREATE2, or similar) should be recorded in the CreateAccountOutput.

Copy link
Member

Choose a reason for hiding this comment

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

To be complete.
This is, I believe, specific to account. In most cases the state changes (which immediately follow the triggering transaction) are more than sufficient to identify the created entity.

Unfortunately, as noted above, Account state changes follow every transaction, and it is unusually difficult to separate the create from other account updates; particularly if an account is created as a result of an EVM transaction.

transactionRecordBuilder.getReceiptBuilder().setAccountID(value.getAccountIdValue());
}
}
if (stateChange.getStateId() == STATE_ID_ALIASES.getNumber() && stateChange.hasMapUpdate()) {
var value = stateChange.getMapUpdate().getValue();
if (value.hasAccountValue()) {
transactionRecordBuilder.setAlias(
value.getAccountValue().getAlias());
}
}
}
}
}

@Override
public TransactionType getType() {
return TransactionType.CRYPTOCREATEACCOUNT;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.mirror.importer.downloader.block.transformer;

import com.hedera.mirror.common.domain.transaction.TransactionType;
import jakarta.inject.Named;

@Named
final class CryptoDeleteAllowanceTransformer extends AbstractBlockItemTransformer {

@Override
public TransactionType getType() {
return TransactionType.CRYPTODELETEALLOWANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.mirror.importer.downloader.block.transformer;

import com.hedera.mirror.common.domain.transaction.TransactionType;
import jakarta.inject.Named;

@Named
final class CryptoDeleteLiveHashTransformer extends AbstractBlockItemTransformer {

@Override
public TransactionType getType() {
return TransactionType.CRYPTODELETELIVEHASH;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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_ACCOUNTS;
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 CryptoDeleteTransformer 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_ACCOUNTS.getNumber() && stateChange.hasMapUpdate()) {
var value = stateChange.getMapUpdate().getValue();
if (value.hasAccountValue()) {
var accountDelete = value.getAccountValue();
if (accountDelete.getDeleted()) {
transactionRecordBuilder.getReceiptBuilder().setAccountID(accountDelete.getAccountId());
return;
}
}
}
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

receipt.account_id shouldn't be set for crypto delete transaction

Suggested change
@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_ACCOUNTS.getNumber() && stateChange.hasMapUpdate()) {
var value = stateChange.getMapUpdate().getValue();
if (value.hasAccountValue()) {
var accountDelete = value.getAccountValue();
if (accountDelete.getDeleted()) {
transactionRecordBuilder.getReceiptBuilder().setAccountID(accountDelete.getAccountId());
return;
}
}
}
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated


@Override
public TransactionType getType() {
return TransactionType.CRYPTODELETE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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_ACCOUNTS;

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 CryptoUpdateTransformer extends AbstractBlockItemTransformer {

@Override
protected void updateTransactionRecord(BlockItem blockItem, TransactionRecord.Builder transactionRecordBuilder) {
for (var stateChanges : blockItem.stateChanges()) {
for (var stateChange : stateChanges.getStateChangesList()) {
if (stateChange.getStateId() == STATE_ID_ACCOUNTS.getNumber() && stateChange.hasMapUpdate()) {
var value = stateChange.getMapUpdate().getValue();
if (value.hasAccountIdValue()) {
transactionRecordBuilder.getReceiptBuilder().setAccountID(value.getAccountIdValue());
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

weird enough, receipt.account_id for crypto update transaction is set in record stream files.

again, the logic shares the same problem in the comment for crypto create transaction.

Think we can choose not to set it at all, or set it to the value from crypto update transaction body

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Temporarily removing the updateTransactionRecord method until we identify a better solution.

}
}
}

@Override
public TransactionType getType() {
return TransactionType.CRYPTOUPDATEACCOUNT;
}
}