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

feat: HIP-551 atomic batch transactions #17333

Draft
wants to merge 16 commits into
base: 17360-avoid-circular-dependency
Choose a base branch
from
Draft
5 changes: 5 additions & 0 deletions hapi/hedera-protobufs/services/basic_types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,11 @@ enum HederaFunctionality {
* Submit a signature of a state root hash gossiped to other nodes
*/
StateSignatureTransaction = 100;

/**
* Submit a batch of transactions to run atomically
*/
AtomicBatch = 101;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions hapi/hedera-protobufs/services/response_code.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1640,4 +1640,19 @@ enum ResponseCodeEnum {
* approximately four million times with typical network configuration.)
*/
RECURSIVE_SCHEDULING_LIMIT_REACHED = 374;

/**
* The list of batch transactions is empty
*/
BATCH_LIST_EMPTY = 375;

/**
* The list of batch transactions contains duplicated transactions
*/
BATCH_LIST_CONTAINS_DUPLICATES = 376;

/**
* The list of batch transactions contains null values
*/
BATCH_LIST_CONTAINS_NULL_VALUES = 377;
}
16 changes: 15 additions & 1 deletion hapi/hedera-protobufs/services/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,12 @@ message TransactionBody {
* (default 100) bytes when encoded as UTF-8.
*/
string memo = 6;

/**
* The <b>entire public key</b> of the trusted batch assembler.
*
* Only Ed25519 and ECDSA(secp256k1) keys and hence signatures are currently supported.
*/
Key batchKey = 66;
// The fields here are ordered in strictly ascending field ordinal
// order due to limitations in PBJ.
oneof data {
Expand Down Expand Up @@ -606,5 +611,14 @@ message TransactionBody {
* A transaction body for signature of a state root hash gossiped to other nodes
*/
com.hedera.hapi.platform.event.StateSignatureTransaction state_signature_transaction = 65;

AtomicBatchTransactionBody atomicBatch = 67;
}
}

/**
* Create an atomic batch.
*/
message AtomicBatchTransactionBody {
repeated Transaction transactions = 1;
}
10 changes: 10 additions & 0 deletions hapi/hedera-protobufs/services/util_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,14 @@ service UtilService {
* [UtilPrngTransactionBody](#proto.UtilPrngTransactionBody)
*/
rpc prng (Transaction) returns (TransactionResponse);


/**
* Execute a batch of transactions atomically.
* <p>
* All transactions in the batch will be executed in order, and if any
* transaction fails, the entire batch will fail.
// TODO: Add more details about the batch transaction
*/
rpc atomicBatch (Transaction) returns (TransactionResponse);
}
19 changes: 10 additions & 9 deletions hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@

public static HederaFunctionality functionOf(final TransactionBody txn) throws UnknownHederaFunctionality {
return switch (txn.data().kind()) {
case ATOMIC_BATCH -> HederaFunctionality.ATOMIC_BATCH;

Check warning on line 182 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L182

Added line #L182 was not covered by tests
case CONSENSUS_CREATE_TOPIC -> HederaFunctionality.CONSENSUS_CREATE_TOPIC;
case CONSENSUS_UPDATE_TOPIC -> HederaFunctionality.CONSENSUS_UPDATE_TOPIC;
case CONSENSUS_DELETE_TOPIC -> HederaFunctionality.CONSENSUS_DELETE_TOPIC;
Expand All @@ -201,14 +202,21 @@
case FILE_UPDATE -> HederaFunctionality.FILE_UPDATE;
case FILE_DELETE -> HederaFunctionality.FILE_DELETE;
case FREEZE -> HederaFunctionality.FREEZE;
case NODE_CREATE -> HederaFunctionality.NODE_CREATE;
case NODE_DELETE -> HederaFunctionality.NODE_DELETE;

Check warning on line 206 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L206

Added line #L206 was not covered by tests
case NODE_STAKE_UPDATE -> HederaFunctionality.NODE_STAKE_UPDATE;
case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE;
case SCHEDULE_CREATE -> HederaFunctionality.SCHEDULE_CREATE;
case SCHEDULE_SIGN -> HederaFunctionality.SCHEDULE_SIGN;
case SCHEDULE_DELETE -> HederaFunctionality.SCHEDULE_DELETE;
case STATE_SIGNATURE_TRANSACTION -> HederaFunctionality.STATE_SIGNATURE_TRANSACTION;

Check warning on line 212 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L212

Added line #L212 was not covered by tests
case SYSTEM_DELETE -> HederaFunctionality.SYSTEM_DELETE;
case SYSTEM_UNDELETE -> HederaFunctionality.SYSTEM_UNDELETE;
case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP;

Check warning on line 215 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L215

Added line #L215 was not covered by tests
case TOKEN_ASSOCIATE -> HederaFunctionality.TOKEN_ASSOCIATE_TO_ACCOUNT;
case TOKEN_BURN -> HederaFunctionality.TOKEN_BURN;
case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP;
case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP;

Check warning on line 219 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L218-L219

Added lines #L218 - L219 were not covered by tests
case TOKEN_CREATION -> HederaFunctionality.TOKEN_CREATE;
case TOKEN_DELETION -> HederaFunctionality.TOKEN_DELETE;
case TOKEN_DISSOCIATE -> HederaFunctionality.TOKEN_DISSOCIATE_FROM_ACCOUNT;
Expand All @@ -217,22 +225,15 @@
case TOKEN_GRANT_KYC -> HederaFunctionality.TOKEN_GRANT_KYC_TO_ACCOUNT;
case TOKEN_MINT -> HederaFunctionality.TOKEN_MINT;
case TOKEN_PAUSE -> HederaFunctionality.TOKEN_PAUSE;
case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT;

Check warning on line 228 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L228

Added line #L228 was not covered by tests
case TOKEN_REVOKE_KYC -> HederaFunctionality.TOKEN_REVOKE_KYC_FROM_ACCOUNT;
case TOKEN_UNFREEZE -> HederaFunctionality.TOKEN_UNFREEZE_ACCOUNT;
case TOKEN_UNPAUSE -> HederaFunctionality.TOKEN_UNPAUSE;
case TOKEN_UPDATE -> HederaFunctionality.TOKEN_UPDATE;
case TOKEN_UPDATE_NFTS -> HederaFunctionality.TOKEN_UPDATE_NFTS;
case TOKEN_WIPE -> HederaFunctionality.TOKEN_ACCOUNT_WIPE;
case UTIL_PRNG -> HederaFunctionality.UTIL_PRNG;
case UNCHECKED_SUBMIT -> HederaFunctionality.UNCHECKED_SUBMIT;
case NODE_CREATE -> HederaFunctionality.NODE_CREATE;
case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE;
case NODE_DELETE -> HederaFunctionality.NODE_DELETE;
case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT;
case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP;
case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP;
case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP;
case STATE_SIGNATURE_TRANSACTION -> HederaFunctionality.STATE_SIGNATURE_TRANSACTION;
case UTIL_PRNG -> HederaFunctionality.UTIL_PRNG;
case UNSET -> throw new UnknownHederaFunctionality();
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
* Copyright (C) 2024-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,6 +38,7 @@
import com.hedera.node.config.converter.SemanticVersionConverter;
import com.hedera.node.config.data.AccountsConfig;
import com.hedera.node.config.data.ApiPermissionConfig;
import com.hedera.node.config.data.AtomicBatchConfig;
import com.hedera.node.config.data.AutoCreationConfig;
import com.hedera.node.config.data.AutoRenew2Config;
import com.hedera.node.config.data.AutoRenewConfig;
Expand Down Expand Up @@ -127,6 +128,7 @@ public Set<Class<? extends Record>> getConfigDataTypes() {
TopicsConfig.class,
TraceabilityConfig.class,
UtilPrngConfig.class,
AtomicBatchConfig.class,
VersionConfig.class,
TssConfig.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
case TOKEN_CANCEL_AIRDROP -> handlers.tokenCancelAirdropHandler();

case UTIL_PRNG -> handlers.utilPrngHandler();
case ATOMIC_BATCH -> handlers.atomicBatchHandler();

Check warning on line 213 in hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/dispatcher/TransactionDispatcher.java#L213

Added line #L213 was not covered by tests

case SYSTEM_DELETE -> switch (txBody.systemDeleteOrThrow().id().kind()) {
case CONTRACT_ID -> handlers.contractSystemDeleteHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import com.hedera.node.app.service.token.impl.handlers.TokenUnpauseHandler;
import com.hedera.node.app.service.token.impl.handlers.TokenUpdateHandler;
import com.hedera.node.app.service.token.impl.handlers.TokenUpdateNftsHandler;
import com.hedera.node.app.service.util.impl.handlers.AtomicBatchHandler;
import com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler;
import edu.umd.cs.findbugs.annotations.NonNull;

Expand Down Expand Up @@ -130,4 +131,5 @@ public record TransactionHandlers(
@NonNull NodeUpdateHandler nodeUpdateHandler,
@NonNull NodeDeleteHandler nodeDeleteHandler,
@NonNull TokenClaimAirdropHandler tokenClaimAirdropHandler,
@NonNull UtilPrngHandler utilPrngHandler) {}
@NonNull UtilPrngHandler utilPrngHandler,
@NonNull AtomicBatchHandler atomicBatchHandler) {}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ static TransactionHandlers provideTransactionHandlers(
addressBookHandlers.nodeUpdateHandler(),
addressBookHandlers.nodeDeleteHandler(),
tokenHandlers.tokenClaimAirdropHandler(),
utilHandlers.prngHandler());
utilHandlers.prngHandler(),
utilHandlers.atomicBatchHandler());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.hedera.node.config.data;

import static com.hedera.hapi.node.base.HederaFunctionality.ATOMIC_BATCH;
import static com.hedera.hapi.node.base.HederaFunctionality.CONSENSUS_CREATE_TOPIC;
import static com.hedera.hapi.node.base.HederaFunctionality.CONSENSUS_DELETE_TOPIC;
import static com.hedera.hapi.node.base.HederaFunctionality.CONSENSUS_GET_TOPIC_INFO;
Expand Down Expand Up @@ -119,6 +120,7 @@
* @param deleteAllowances the permission for {@link HederaFunctionality#CRYPTO_DELETE_ALLOWANCE}
* functionality
* @param utilPrng the permission for {@link HederaFunctionality#UTIL_PRNG} functionality
* @param atomicBatch the permission for {@link HederaFunctionality#ATOMIC_BATCH} functionality
* @param createFile the permission for {@link HederaFunctionality#FILE_CREATE} functionality
* @param updateFile the permission for {@link HederaFunctionality#FILE_UPDATE} functionality
* @param deleteFile the permission for {@link HederaFunctionality#FILE_DELETE} functionality
Expand Down Expand Up @@ -204,6 +206,7 @@ public record ApiPermissionConfig(
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange approveAllowances,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange deleteAllowances,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange utilPrng,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange atomicBatch,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange createFile,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange updateFile,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange deleteFile,
Expand Down Expand Up @@ -340,6 +343,7 @@ public record ApiPermissionConfig(
permissionKeys.put(TOKEN_GET_ACCOUNT_NFT_INFOS, c -> c.tokenGetAccountNftInfos);
permissionKeys.put(TOKEN_FEE_SCHEDULE_UPDATE, c -> c.tokenFeeScheduleUpdate);
permissionKeys.put(UTIL_PRNG, c -> c.utilPrng);
permissionKeys.put(ATOMIC_BATCH, c -> c.atomicBatch);
permissionKeys.put(NODE_CREATE, c -> c.createNode);
permissionKeys.put(NODE_UPDATE, c -> c.updateNode);
permissionKeys.put(NODE_DELETE, c -> c.deleteNode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.node.config.data;

import com.hedera.node.config.NetworkProperty;
import com.swirlds.config.api.ConfigData;
import com.swirlds.config.api.ConfigProperty;

@ConfigData("atomicBatch")
public record AtomicBatchConfig(@ConfigProperty(defaultValue = "true") @NetworkProperty boolean isEnabled) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2022-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.node.app.service.util.impl.handlers;

import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.TransactionBody;
import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.spi.workflows.PreHandleContext;
import com.hedera.node.app.spi.workflows.TransactionHandler;
import com.hedera.node.config.data.AtomicBatchConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
* This class contains all workflow-related functionality regarding {@link HederaFunctionality#ATOMIC_BATCH}.
*/
@Singleton
public class AtomicBatchHandler implements TransactionHandler {
/**
* Constructs a {@link AtomicBatchHandler}
*/
@Inject
public AtomicBatchHandler() {
// exists for Dagger injection
}

/**
* Performs checks independent of state or context.
*
* @param txn the transaction to check
*/
@Override
public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException {
// TODO
}

/**
* This method is called during the pre-handle workflow.
*
* @param context the {@link PreHandleContext} which collects all information
* @throws PreCheckException if any issue happens on the pre handle level
*/
@Override
public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException {
requireNonNull(context);
// TODO
}

@Override
public void handle(@NonNull final HandleContext handleContext) throws HandleException {
requireNonNull(handleContext);
// TODO
if (!handleContext
.configuration()
.getConfigData(AtomicBatchConfig.class)
.isEnabled()) {
return;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2024 Hedera Hashgraph, LLC
* Copyright (C) 2023-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -25,13 +25,20 @@
public class UtilHandlers {

private final UtilPrngHandler prngHandler;
private final AtomicBatchHandler atomicBatchHandler;

@Inject
public UtilHandlers(@NonNull final UtilPrngHandler prngHandler) {
public UtilHandlers(
@NonNull final UtilPrngHandler prngHandler, @NonNull final AtomicBatchHandler atomicBatchHandler) {
this.prngHandler = Objects.requireNonNull(prngHandler, "prngHandler must not be null");
this.atomicBatchHandler = Objects.requireNonNull(atomicBatchHandler, "atomicBatchHandler must not be null");
}

public UtilPrngHandler prngHandler() {
return prngHandler;
}

public AtomicBatchHandler atomicBatchHandler() {
return atomicBatchHandler;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
* Copyright (C) 2024-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,24 +19,31 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;

import com.hedera.node.app.service.util.impl.handlers.AtomicBatchHandler;
import com.hedera.node.app.service.util.impl.handlers.UtilHandlers;
import com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class UtilHandlersTest {
private UtilPrngHandler prngHandler;
private AtomicBatchHandler atomicBatchHandler;

private UtilHandlers utilHandlers;

@BeforeEach
public void setUp() {
prngHandler = mock(UtilPrngHandler.class);
utilHandlers = new UtilHandlers(prngHandler);
atomicBatchHandler = mock(AtomicBatchHandler.class);
utilHandlers = new UtilHandlers(prngHandler, atomicBatchHandler);
}

@Test
void prngHandlerReturnsCorrectInstance() {
assertEquals(prngHandler, utilHandlers.prngHandler(), "prngHandler does not return correct instance");
assertEquals(
atomicBatchHandler,
utilHandlers.atomicBatchHandler(),
"atomicBatchHandler does not return correct instance");
}
}
Loading
Loading