From dc5a103921d96e668036d520bacebb39e375c258 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Wed, 15 Jan 2025 12:51:27 +0200 Subject: [PATCH 01/11] Add HapiAtomicBatch to test clients TxnVerbs.java Signed-off-by: Zhivko Kelchev --- .../node/app/services/ServiceScopeLookup.java | 2 +- .../bdd/spec/transactions/TxnFactory.java | 5 + .../bdd/spec/transactions/TxnVerbs.java | 5 + .../transactions/util/HapiAtomicBatch.java | 103 ++++++++++++++++++ .../bdd/suites/hip551/AtomicBatchTest.java | 38 +++++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java index c7c069d3dd52..97e167ae64e5 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceScopeLookup.java @@ -106,7 +106,7 @@ public String getServiceName(@NonNull final TransactionBody txBody) { TOKEN_CANCEL_AIRDROP, TOKEN_REJECT -> TokenService.NAME; - case UTIL_PRNG -> UtilService.NAME; + case UTIL_PRNG, ATOMIC_BATCH -> UtilService.NAME; case SYSTEM_DELETE -> switch (txBody.systemDeleteOrThrow().id().kind()) { case CONTRACT_ID -> ContractService.NAME; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnFactory.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnFactory.java index 207e8a1348a0..68b96085049c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnFactory.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnFactory.java @@ -26,6 +26,7 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.utilops.mod.BodyMutation; +import com.hederahashgraph.api.proto.java.AtomicBatchTransactionBody; import com.hederahashgraph.api.proto.java.ConsensusCreateTopicTransactionBody; import com.hederahashgraph.api.proto.java.ConsensusDeleteTopicTransactionBody; import com.hederahashgraph.api.proto.java.ConsensusSubmitMessageTransactionBody; @@ -467,4 +468,8 @@ public Consumer defaultDefTokenClaimAi public Consumer defaultDefTokenAirdropTransactionBody() { return builder -> {}; } + + public Consumer defaultDefAtomicBatchTransactionBody() { + return builder -> {}; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java index f7035f217e84..b287affaae6c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java @@ -99,6 +99,7 @@ import com.hedera.services.bdd.spec.transactions.token.HapiTokenUpdateNfts; import com.hedera.services.bdd.spec.transactions.token.HapiTokenWipe; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; +import com.hedera.services.bdd.spec.transactions.util.HapiAtomicBatch; import com.hedera.services.bdd.spec.transactions.util.HapiUtilPrng; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; import com.hederahashgraph.api.proto.java.ContractCreateTransactionBody; @@ -770,4 +771,8 @@ public static HapiUtilPrng hapiPrng() { public static HapiUtilPrng hapiPrng(int range) { return new HapiUtilPrng(range); } + + public static HapiAtomicBatch atomicBatch(HapiTxnOp... ops) { + return new HapiAtomicBatch(ops); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java new file mode 100644 index 000000000000..85565739f0c2 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020-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.services.bdd.spec.transactions.util; + +import static com.hedera.services.bdd.spec.transactions.TxnUtils.suFrom; + +import com.google.common.base.MoreObjects; +import com.hedera.node.app.hapi.fees.usage.BaseTransactionMeta; +import com.hedera.node.app.hapi.fees.usage.crypto.CryptoCreateMeta; +import com.hedera.node.app.hapi.fees.usage.state.UsageAccumulator; +import com.hedera.node.app.hapi.utils.fee.SigValueObj; +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.fees.AdapterUtils; +import com.hedera.services.bdd.spec.transactions.HapiTxnOp; +import com.hederahashgraph.api.proto.java.AtomicBatchTransactionBody; +import com.hederahashgraph.api.proto.java.FeeData; +import com.hederahashgraph.api.proto.java.HederaFunctionality; +import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.Transaction; +import com.hederahashgraph.api.proto.java.TransactionBody; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class HapiAtomicBatch extends HapiTxnOp { + static final Logger log = LogManager.getLogger(HapiAtomicBatch.class); + + private List> operationsToBatch; + + public HapiAtomicBatch() {} + + public HapiAtomicBatch(HapiTxnOp... ops) { + this.operationsToBatch = Arrays.stream(ops).toList(); + } + + @Override + public HederaFunctionality type() { + return HederaFunctionality.AtomicBatch; + } + + @Override + protected HapiAtomicBatch self() { + return this; + } + + @Override + protected long feeFor(final HapiSpec spec, final Transaction txn, final int numPayerKeys) throws Throwable { + return 20_000_000L; // spec.fees().forActivityBasedOp(HederaFunctionality.AtomicBatch, this::usageEstimate, txn, + // numPayerKeys); + } + + private FeeData usageEstimate(final TransactionBody txn, final SigValueObj svo) { + // todo: check for correct estimation of the batch + final var baseMeta = new BaseTransactionMeta(txn.getMemoBytes().size(), 0); + final var opMeta = new CryptoCreateMeta(txn.getCryptoCreateAccount()); + final var accumulator = new UsageAccumulator(); + cryptoOpsUsage.cryptoCreateUsage(suFrom(svo), baseMeta, opMeta, accumulator); + return AdapterUtils.feeDataFrom(accumulator); + } + + @Override + protected Consumer opBodyDef(final HapiSpec spec) throws Throwable { + final AtomicBatchTransactionBody opBody = spec.txns() + .body( + AtomicBatchTransactionBody.class, b -> { + for (HapiTxnOp op : operationsToBatch) { + try { + b.addTransactions(op.signedTxnFor(spec)); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + }); + return b -> b.setAtomicBatch(opBody); + } + + @Override + protected List> defaultSigners() { + return Arrays.asList(spec -> spec.registry().getKey(effectivePayer(spec))); + } + + @Override + protected MoreObjects.ToStringHelper toStringHelper() { + return super.toStringHelper().add("range", operationsToBatch); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java new file mode 100644 index 000000000000..e3398adb0b53 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -0,0 +1,38 @@ +/* + * 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.services.bdd.suites.hip551; + +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.atomicBatch; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; + +@HapiTestLifecycle +public class AtomicBatchTest { + + @HapiTest + // just test that the batch is submitted + public Stream waitForExpiryIgnoredWhenLongTermDisabled() { + return hapiTest(atomicBatch( + cryptoCreate("PAYER").balance(ONE_HBAR), cryptoCreate("SENDER").balance(1L))); + } +} From 865603da6e81873f7c68e270a7f54316a476033c Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Wed, 15 Jan 2025 12:56:36 +0200 Subject: [PATCH 02/11] Add HapiAtomicBatch to test clients TxnVerbs.java Signed-off-by: Zhivko Kelchev --- .../services/bdd/spec/transactions/util/HapiAtomicBatch.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java index 85565739f0c2..a48c8ef69fd3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -62,12 +62,13 @@ protected HapiAtomicBatch self() { @Override protected long feeFor(final HapiSpec spec, final Transaction txn, final int numPayerKeys) throws Throwable { + // TODO: Implement proper estimate for AtomicBatch return 20_000_000L; // spec.fees().forActivityBasedOp(HederaFunctionality.AtomicBatch, this::usageEstimate, txn, // numPayerKeys); } private FeeData usageEstimate(final TransactionBody txn, final SigValueObj svo) { - // todo: check for correct estimation of the batch + // TODO: check for correct estimation of the batch final var baseMeta = new BaseTransactionMeta(txn.getMemoBytes().size(), 0); final var opMeta = new CryptoCreateMeta(txn.getCryptoCreateAccount()); final var accumulator = new UsageAccumulator(); From a8f4fbaa35035c5188ffc63f13b0d78814146aec Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Wed, 15 Jan 2025 14:38:54 +0200 Subject: [PATCH 03/11] Add HapiAtomicBatch to test clients TxnVerbs.java Signed-off-by: Zhivko Kelchev --- .../com/hedera/services/bdd/junit/hedera/utils/GrpcUtils.java | 4 +++- .../hedera/services/bdd/suites/hip551/AtomicBatchTest.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/GrpcUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/GrpcUtils.java index bbbdb2d26f84..6647ac2b7cb6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/GrpcUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/GrpcUtils.java @@ -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. @@ -220,6 +220,8 @@ public static TransactionResponse submit( .cancelAirdrop(transaction); case TokenClaimAirdrop -> clients.getTokenSvcStub(nodeAccountId, false, false) .claimAirdrop(transaction); + case AtomicBatch -> clients.getUtilSvcStub(nodeAccountId, false, false) + .atomicBatch(transaction); default -> throw new IllegalArgumentException(functionality + " is not a transaction"); }; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java index e3398adb0b53..3d90d204f66d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -31,7 +31,7 @@ public class AtomicBatchTest { @HapiTest // just test that the batch is submitted - public Stream waitForExpiryIgnoredWhenLongTermDisabled() { + public Stream simpleBatchTest() { return hapiTest(atomicBatch( cryptoCreate("PAYER").balance(ONE_HBAR), cryptoCreate("SENDER").balance(1L))); } From 290a50689be74422f7bdc04cf639400d165ca3f0 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Wed, 15 Jan 2025 16:55:04 +0200 Subject: [PATCH 04/11] Disable dummy hapi test Signed-off-by: Zhivko Kelchev --- .../com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java index 3d90d204f66d..ca08ba3706a2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -24,13 +24,16 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestLifecycle; import java.util.stream.Stream; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; @HapiTestLifecycle public class AtomicBatchTest { @HapiTest + @Disabled // just test that the batch is submitted + // disabled for now because there is no handler logic and streamValidation is failing in CI public Stream simpleBatchTest() { return hapiTest(atomicBatch( cryptoCreate("PAYER").balance(ONE_HBAR), cryptoCreate("SENDER").balance(1L))); From 0d52a66ea2dd5bcb4c5315eed15154910896dda8 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Fri, 17 Jan 2025 17:20:19 +0200 Subject: [PATCH 05/11] add atomicBatch operation with Transaction arguments Signed-off-by: Zhivko Kelchev --- .../bdd/spec/transactions/TxnVerbs.java | 5 +++ .../transactions/util/HapiAtomicBatch.java | 22 ++++++++++--- .../bdd/spec/utilops/BuildTransaction.java | 31 +++++++++++++++++++ .../bdd/suites/hip551/AtomicBatchTest.java | 17 ++++++++-- 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java index b287affaae6c..bbc7045d4a5f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java @@ -110,6 +110,7 @@ import com.hederahashgraph.api.proto.java.TokenReference; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TopicID; +import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransferList; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -775,4 +776,8 @@ public static HapiUtilPrng hapiPrng(int range) { public static HapiAtomicBatch atomicBatch(HapiTxnOp... ops) { return new HapiAtomicBatch(ops); } + + public static HapiAtomicBatch atomicBatch(Transaction... transactions) { + return new HapiAtomicBatch(transactions); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java index a48c8ef69fd3..341445708a56 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -44,12 +44,20 @@ public class HapiAtomicBatch extends HapiTxnOp { private List> operationsToBatch; + private boolean useRawTransactions = false; + private List transactionsToBatch; + public HapiAtomicBatch() {} public HapiAtomicBatch(HapiTxnOp... ops) { this.operationsToBatch = Arrays.stream(ops).toList(); } + public HapiAtomicBatch(Transaction... transactions) { + useRawTransactions = true; + this.transactionsToBatch = Arrays.stream(transactions).toList(); + } + @Override public HederaFunctionality type() { return HederaFunctionality.AtomicBatch; @@ -81,11 +89,15 @@ protected Consumer opBodyDef(final HapiSpec spec) throw final AtomicBatchTransactionBody opBody = spec.txns() .body( AtomicBatchTransactionBody.class, b -> { - for (HapiTxnOp op : operationsToBatch) { - try { - b.addTransactions(op.signedTxnFor(spec)); - } catch (Throwable e) { - throw new RuntimeException(e); + if (useRawTransactions) { + b.addAllTransactions(transactionsToBatch); + } else { + for (HapiTxnOp op : operationsToBatch) { + try { + b.addTransactions(op.signedTxnFor(spec)); + } catch (Throwable e) { + throw new RuntimeException(e); + } } } }); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java new file mode 100644 index 000000000000..9bd624aa1e7c --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java @@ -0,0 +1,31 @@ +/* + * 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.services.bdd.spec.utilops; + +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.transactions.HapiTxnOp; +import com.hederahashgraph.api.proto.java.Transaction; + +public class BuildTransaction { + public static Transaction buildTxnFrom(HapiSpec spec, HapiTxnOp op) { + try { + return op.signedTxnFor(spec); + } catch (Throwable t) { + throw new IllegalStateException("Failed to build transaction from " + op.toString(), t); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java index ca08ba3706a2..fc1f844ee8ac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -19,6 +19,9 @@ import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.atomicBatch; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.utilops.BuildTransaction.buildTxnFrom; +import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import com.hedera.services.bdd.junit.HapiTest; @@ -35,7 +38,17 @@ public class AtomicBatchTest { // just test that the batch is submitted // disabled for now because there is no handler logic and streamValidation is failing in CI public Stream simpleBatchTest() { - return hapiTest(atomicBatch( - cryptoCreate("PAYER").balance(ONE_HBAR), cryptoCreate("SENDER").balance(1L))); + return hapiTest( + // submit batch with HapiTxnOp + atomicBatch( + cryptoCreate("PAYER").balance(ONE_HBAR), + cryptoCreate("SENDER").balance(1L)), + + // submit batch with Transaction + withOpContext((spec, opLog) -> { + var txn = buildTxnFrom(spec, cryptoCreate("PAYER").balance(ONE_HBAR)); + var batch1 = atomicBatch(txn); + allRunFor(spec, batch1); + })); } } From bc7668288b6e1f75ee7d281658e25dc3e2d961bf Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 28 Jan 2025 12:53:44 +0200 Subject: [PATCH 06/11] HapiAtomicBatch add updateStateOf Signed-off-by: Zhivko Kelchev --- .../bdd/spec/transactions/HapiTxnOp.java | 7 +++ .../bdd/spec/transactions/TxnVerbs.java | 5 -- .../transactions/util/HapiAtomicBatch.java | 54 ++++++++++++------- .../bdd/suites/hip551/AtomicBatchTest.java | 33 +++++++----- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java index 388c7e9989a5..81ce4f714def 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java @@ -66,6 +66,7 @@ import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionGetReceiptResponse; import com.hederahashgraph.api.proto.java.TransactionReceipt; +import com.hederahashgraph.api.proto.java.TransactionRecord; import com.hederahashgraph.api.proto.java.TransactionResponse; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -865,4 +866,10 @@ public boolean hasActualStatus() { public ResponseCodeEnum getActualStatus() { return lastReceipt.getStatus(); } + + public void updateStateFromRecord(TransactionRecord record, HapiSpec spec) throws Throwable { + this.actualStatus = record.getReceipt().getStatus(); + this.lastReceipt = record.getReceipt(); + updateStateOf(spec); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java index bbc7045d4a5f..b287affaae6c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/TxnVerbs.java @@ -110,7 +110,6 @@ import com.hederahashgraph.api.proto.java.TokenReference; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TopicID; -import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransferList; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -776,8 +775,4 @@ public static HapiUtilPrng hapiPrng(int range) { public static HapiAtomicBatch atomicBatch(HapiTxnOp... ops) { return new HapiAtomicBatch(ops); } - - public static HapiAtomicBatch atomicBatch(Transaction... transactions) { - return new HapiAtomicBatch(transactions); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java index 341445708a56..c1d74b31d74c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -16,7 +16,10 @@ package com.hedera.services.bdd.spec.transactions.util; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnUtils.extractTxnId; import static com.hedera.services.bdd.spec.transactions.TxnUtils.suFrom; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.google.common.base.MoreObjects; import com.hedera.node.app.hapi.fees.usage.BaseTransactionMeta; @@ -25,6 +28,7 @@ import com.hedera.node.app.hapi.utils.fee.SigValueObj; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.fees.AdapterUtils; +import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.HapiTxnOp; import com.hederahashgraph.api.proto.java.AtomicBatchTransactionBody; import com.hederahashgraph.api.proto.java.FeeData; @@ -32,8 +36,12 @@ import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionBody; +import com.hederahashgraph.api.proto.java.TransactionID; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import org.apache.logging.log4j.LogManager; @@ -43,21 +51,12 @@ public class HapiAtomicBatch extends HapiTxnOp { static final Logger log = LogManager.getLogger(HapiAtomicBatch.class); private List> operationsToBatch; - - private boolean useRawTransactions = false; - private List transactionsToBatch; - - public HapiAtomicBatch() {} + private final Map> operationsMap = new HashMap<>(); public HapiAtomicBatch(HapiTxnOp... ops) { this.operationsToBatch = Arrays.stream(ops).toList(); } - public HapiAtomicBatch(Transaction... transactions) { - useRawTransactions = true; - this.transactionsToBatch = Arrays.stream(transactions).toList(); - } - @Override public HederaFunctionality type() { return HederaFunctionality.AtomicBatch; @@ -89,21 +88,38 @@ protected Consumer opBodyDef(final HapiSpec spec) throw final AtomicBatchTransactionBody opBody = spec.txns() .body( AtomicBatchTransactionBody.class, b -> { - if (useRawTransactions) { - b.addAllTransactions(transactionsToBatch); - } else { - for (HapiTxnOp op : operationsToBatch) { - try { - b.addTransactions(op.signedTxnFor(spec)); - } catch (Throwable e) { - throw new RuntimeException(e); - } + for (HapiTxnOp op : operationsToBatch) { + try { + final var transaction = op.signedTxnFor(spec); + final var txnId = extractTxnId(transaction); + operationsMap.put(txnId, op); + b.addTransactions(transaction); + } catch (Throwable e) { + throw new RuntimeException(e); } } }); return b -> b.setAtomicBatch(opBody); } + @Override + public void updateStateOf(HapiSpec spec) throws Throwable { + if (actualStatus == SUCCESS) { + for (Map.Entry> entry : operationsMap.entrySet()) { + TransactionID txnId = entry.getKey(); + HapiTxnOp op = entry.getValue(); + + final HapiGetTxnRecord recordQuery = + getTxnRecord(txnId).noLogging().assertingNothing(); + final Optional error = recordQuery.execFor(spec); + if (error.isPresent()) { + throw error.get(); + } + op.updateStateFromRecord(recordQuery.getResponseRecord(), spec); + } + } + } + @Override protected List> defaultSigners() { return Arrays.asList(spec -> spec.registry().getKey(effectivePayer(spec))); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java index fc1f844ee8ac..de6c3def20fd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -17,11 +17,11 @@ package com.hedera.services.bdd.suites.hip551; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.atomicBatch; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.utilops.BuildTransaction.buildTxnFrom; -import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import com.hedera.services.bdd.junit.HapiTest; @@ -38,17 +38,22 @@ public class AtomicBatchTest { // just test that the batch is submitted // disabled for now because there is no handler logic and streamValidation is failing in CI public Stream simpleBatchTest() { + final var innerTnxPayer = "innerPayer"; + final var innerTxnId = "innerId"; return hapiTest( - // submit batch with HapiTxnOp - atomicBatch( - cryptoCreate("PAYER").balance(ONE_HBAR), - cryptoCreate("SENDER").balance(1L)), - - // submit batch with Transaction - withOpContext((spec, opLog) -> { - var txn = buildTxnFrom(spec, cryptoCreate("PAYER").balance(ONE_HBAR)); - var batch1 = atomicBatch(txn); - allRunFor(spec, batch1); - })); + // create another payer for the inner txn + cryptoCreate(innerTnxPayer).balance(ONE_HBAR), + // use custom txn id so we can get the record + usableTxnIdNamed(innerTxnId).payerId(innerTnxPayer), + // create a batch txn + atomicBatch(cryptoCreate("foo") + .txnId(innerTxnId) + .balance(ONE_HBAR) + .payingWith(innerTnxPayer)) + .via("batchTxn"), + // get and log inner txn record + getTxnRecord(innerTxnId).assertingNothingAboutHashes().logged(), + // validate the batch txn result + getAccountBalance("foo").hasTinyBars(ONE_HBAR)); } } From 97f6df104b462ff978bbad99edfd709fb4d78445 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 28 Jan 2025 12:57:27 +0200 Subject: [PATCH 07/11] remove unused BuildTransaction Signed-off-by: Zhivko Kelchev --- .../bdd/spec/utilops/BuildTransaction.java | 31 ------------------- 1 file changed, 31 deletions(-) delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java deleted file mode 100644 index 9bd624aa1e7c..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/BuildTransaction.java +++ /dev/null @@ -1,31 +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.services.bdd.spec.utilops; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.transactions.HapiTxnOp; -import com.hederahashgraph.api.proto.java.Transaction; - -public class BuildTransaction { - public static Transaction buildTxnFrom(HapiSpec spec, HapiTxnOp op) { - try { - return op.signedTxnFor(spec); - } catch (Throwable t) { - throw new IllegalStateException("Failed to build transaction from " + op.toString(), t); - } - } -} From c4739a0b437bd51e7274284f213cb14031addb2e Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 28 Jan 2025 14:30:57 +0200 Subject: [PATCH 08/11] Add batch key in HapiTxnOp Signed-off-by: Zhivko Kelchev --- .../java/com/hedera/services/bdd/spec/HapiSpecOperation.java | 4 +++- .../com/hedera/services/bdd/spec/transactions/HapiTxnOp.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java index 9b5b6b7d510a..16d03ad96bda 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpecOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2020-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. @@ -127,6 +127,7 @@ public abstract class HapiSpecOperation implements SpecOperation { protected Optional controlOverrides = Optional.empty(); protected Map overrides = Collections.EMPTY_MAP; + protected Optional> batchKey = Optional.empty(); protected Optional fee = Optional.empty(); protected Optional validDurationSecs = Optional.empty(); protected Optional customTxnId = Optional.empty(); @@ -289,6 +290,7 @@ protected Consumer bodyDef(final HapiSpec spec) { Duration.newBuilder().setSeconds(s).build())); genRecord.ifPresent(builder::setGenerateRecord); memo.ifPresent(builder::setMemo); + batchKey.ifPresent(k -> builder.setBatchKey(k.apply(spec))); }; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java index 81ce4f714def..e3a17eb73e93 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java @@ -872,4 +872,9 @@ public void updateStateFromRecord(TransactionRecord record, HapiSpec spec) throw this.lastReceipt = record.getReceipt(); updateStateOf(spec); } + + public T batchKey(String key) { + batchKey = Optional.of(spec -> spec.registry().getKey(key)); + return self(); + } } From 522b7f78fe108be9612e59854254ddd5cec02c84 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 28 Jan 2025 14:56:49 +0200 Subject: [PATCH 09/11] Update initial test Signed-off-by: Zhivko Kelchev --- .../bdd/suites/hip551/AtomicBatchTest.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java index de6c3def20fd..fdd43e2d7c12 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip551/AtomicBatchTest.java @@ -38,19 +38,29 @@ public class AtomicBatchTest { // just test that the batch is submitted // disabled for now because there is no handler logic and streamValidation is failing in CI public Stream simpleBatchTest() { + final var batchOperator = "batchOperator"; final var innerTnxPayer = "innerPayer"; final var innerTxnId = "innerId"; + + // create inner txn with: + // - custom txn id -> for getting the record + // - batch key -> for batch operator to sign + // - payer -> for paying the fee + final var innerTxn = cryptoCreate("foo") + .balance(ONE_HBAR) + .txnId(innerTxnId) + .batchKey(batchOperator) + .payingWith(innerTnxPayer); + return hapiTest( + // create batch operator + cryptoCreate(batchOperator).balance(ONE_HBAR), // create another payer for the inner txn cryptoCreate(innerTnxPayer).balance(ONE_HBAR), // use custom txn id so we can get the record usableTxnIdNamed(innerTxnId).payerId(innerTnxPayer), // create a batch txn - atomicBatch(cryptoCreate("foo") - .txnId(innerTxnId) - .balance(ONE_HBAR) - .payingWith(innerTnxPayer)) - .via("batchTxn"), + atomicBatch(innerTxn).payingWith(batchOperator), // get and log inner txn record getTxnRecord(innerTxnId).assertingNothingAboutHashes().logged(), // validate the batch txn result From 4c31759676f72c6ff2a455ece1ee78c0520e7557 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Tue, 28 Jan 2025 15:30:52 +0200 Subject: [PATCH 10/11] Add default node id Signed-off-by: Zhivko Kelchev --- .../hedera/services/bdd/spec/transactions/HapiTxnOp.java | 4 ++++ .../bdd/spec/transactions/util/HapiAtomicBatch.java | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java index e3a17eb73e93..9144486fb957 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java @@ -877,4 +877,8 @@ public T batchKey(String key) { batchKey = Optional.of(spec -> spec.registry().getKey(key)); return self(); } + + public Optional getNode() { + return node; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java index c1d74b31d74c..33b1581902a3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -50,6 +50,7 @@ public class HapiAtomicBatch extends HapiTxnOp { static final Logger log = LogManager.getLogger(HapiAtomicBatch.class); + private static final String DEFAULT_NODE_ID = "0.0.0"; private List> operationsToBatch; private final Map> operationsMap = new HashMap<>(); @@ -90,9 +91,16 @@ protected Consumer opBodyDef(final HapiSpec spec) throw AtomicBatchTransactionBody.class, b -> { for (HapiTxnOp op : operationsToBatch) { try { + // set node id to 0.0.0 if not set + if (op.getNode().isEmpty()) { + op.setNode(DEFAULT_NODE_ID); + } + // create a transaction for each operation final var transaction = op.signedTxnFor(spec); + // save transaction id final var txnId = extractTxnId(transaction); operationsMap.put(txnId, op); + // add the transaction to the batch b.addTransactions(transaction); } catch (Throwable e) { throw new RuntimeException(e); From 454315995895bf03ec12bca4b6a1e8ff0ddc7228 Mon Sep 17 00:00:00 2001 From: Zhivko Kelchev Date: Wed, 29 Jan 2025 13:34:24 +0200 Subject: [PATCH 11/11] fix Signed-off-by: Zhivko Kelchev --- .../bdd/spec/transactions/util/HapiAtomicBatch.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java index 33b1581902a3..c020fe614796 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/util/HapiAtomicBatch.java @@ -50,7 +50,7 @@ public class HapiAtomicBatch extends HapiTxnOp { static final Logger log = LogManager.getLogger(HapiAtomicBatch.class); - private static final String DEFAULT_NODE_ID = "0.0.0"; + private static final String DEFAULT_NODE_ACCOUNT_ID = "0.0.0"; private List> operationsToBatch; private final Map> operationsMap = new HashMap<>(); @@ -91,9 +91,9 @@ protected Consumer opBodyDef(final HapiSpec spec) throw AtomicBatchTransactionBody.class, b -> { for (HapiTxnOp op : operationsToBatch) { try { - // set node id to 0.0.0 if not set + // set node account id to 0.0.0 if not set if (op.getNode().isEmpty()) { - op.setNode(DEFAULT_NODE_ID); + op.setNode(DEFAULT_NODE_ACCOUNT_ID); } // create a transaction for each operation final var transaction = op.signedTxnFor(spec);