From 30bab441a1bf6fb7ba0685b03f91395fcb3d610c Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Wed, 8 May 2024 19:27:46 +0300 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20add=20createTokenWithEthereumContrac?= =?UTF-8?q?tCallSignedWithSECP256K1=20hapi=E2=80=A6=20(#13119)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ivo Yankov Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../java/contract/AbstractContractXTest.java | 4 +- .../contract/impl/exec/TransactionModule.java | 26 + .../scope/EitherOrVerificationStrategy.java | 31 +- .../scope/HandleHederaNativeOperations.java | 9 +- .../scope/HandleSystemContractOperations.java | 9 +- .../SpecificCryptoVerificationStrategy.java | 55 ++ .../impl/exec/scope/VerificationStrategy.java | 15 +- .../hts/create/ClassicCreatesCall.java | 39 +- .../contract/impl/hevm/HydratedEthTxData.java | 4 + .../impl/infra/HevmTransactionFactory.java | 2 +- .../impl/test/exec/TransactionModuleTest.java | 29 + ...ctiveContractVerificationStrategyTest.java | 48 +- .../EitherOrVerificationStrategyTest.java | 15 +- .../HandleHederaNativeOperationsTest.java | 7 +- .../HandleSystemContractOperationsTest.java | 5 +- ...pecificCryptoVerificationStrategyTest.java | 53 ++ .../ops/token/RandomTokenUnfreeze.java | 5 +- .../queries/meta/AccountCreationDetails.java | 22 + .../spec/queries/meta/HapiGetTxnRecord.java | 29 +- .../services/bdd/spec/utilops/UtilVerbs.java | 6 + .../spec/utilops/inventory/NewSpecKey.java | 16 + .../utilops/mod/QueryModificationsOp.java | 4 + .../utilops/mod/SubmitModificationsOp.java | 9 +- .../ethereum/HelloWorldEthereumSuite.java | 99 ++- .../file/positive/SysDelSysUndelSpec.java | 6 +- .../TestTokenCreateContract.bin | 1 + .../TestTokenCreateContract.json | 651 ++++++++++++++++++ .../TestTokenCreateContract.sol | 339 +++++++++ 28 files changed, 1481 insertions(+), 57 deletions(-) create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java index 4594729661bb..79bcda7622de 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java @@ -273,8 +273,8 @@ private void runHtsCallAndExpect( component.config().getConfigData(HederaConfig.class), HederaFunctionality.CONTRACT_CALL, new PendingCreationMetadataRef()), - new HandleHederaNativeOperations(context), - new HandleSystemContractOperations(context)); + new HandleHederaNativeOperations(context, null), + new HandleSystemContractOperations(context, null)); given(proxyUpdater.enhancement()).willReturn(enhancement); given(frame.getWorldUpdater()).willReturn(proxyUpdater); given(frame.getSenderAddress()).willReturn(sender); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java index cc173712a8dc..c01feeaf16f1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.node.app.service.contract.impl.annotations.ChildTransactionResourcePrices; @@ -42,6 +43,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; +import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; @@ -55,6 +57,7 @@ import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.config.data.HederaConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -127,6 +130,29 @@ static HydratedEthTxData maybeProvideHydratedEthTxData( : null; } + /** + * If the top-level transaction is an {@code EthereumTransaction}, provides an ECDSA {@link Key} with + * the public key of the sender address; otherwise returns {@code null}. + * + * @param ethTxSigsCache the cache of Ethereum transaction signatures + * @param hydratedEthTxData the hydrated Ethereum transaction data, if this is an {@code EthereumTransaction} + * @return the ECDSA {@link Key} with the public key of the sender address, or {@code null} + */ + @Provides + @Nullable + @TransactionScope + static Key provideSenderEcdsaKey( + @NonNull final EthTxSigsCache ethTxSigsCache, @Nullable final HydratedEthTxData hydratedEthTxData) { + if (hydratedEthTxData != null && hydratedEthTxData.isAvailable()) { + final var ethTxSigs = ethTxSigsCache.computeIfAbsent(hydratedEthTxData.ethTxDataOrThrow()); + return Key.newBuilder() + .ecdsaSecp256k1(Bytes.wrap(ethTxSigs.publicKey())) + .build(); + } else { + return null; + } + } + @Provides @TransactionScope static ActionSidecarContentTracer provideActionSidecarContentTracer() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java index 424c0c067655..3fe0bea0956b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java @@ -16,6 +16,10 @@ package com.hedera.node.app.service.contract.impl.exec.scope; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.VALID; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.Key; import edu.umd.cs.findbugs.annotations.NonNull; @@ -35,11 +39,30 @@ public EitherOrVerificationStrategy( this.secondStrategy = secondStrategy; } + /** + * {@inheritDoc} + * + *

Returns {@link VerificationStrategy.Decision#INVALID} if both strategies agree the + * key's signature is invalid; {@link VerificationStrategy.Decision#VALID} if either strategy + * says the key is valid; and {@link VerificationStrategy.Decision#DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION} + * otherwise. (I.e., if one strategy says the key's signature is invalid but the other allows + * us to delegate.) + * + * @param key the key whose signature is to be verified + * @return the decision of the strategy + */ @Override public Decision decideForPrimitive(@NonNull final Key key) { - return firstStrategy.decideForPrimitive(key) == Decision.VALID - || secondStrategy.decideForPrimitive(key) == Decision.VALID - ? Decision.VALID - : Decision.INVALID; + requireNonNull(key); + final var firstDecision = firstStrategy.decideForPrimitive(key); + if (firstDecision == VALID) { + return VALID; + } else { + final var secondDecision = secondStrategy.decideForPrimitive(key); + if (secondDecision == VALID) { + return VALID; + } + return secondDecision == INVALID ? firstDecision : secondDecision; + } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index ea70bf658aa0..47c6f9a5a43f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -42,6 +42,7 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -52,9 +53,13 @@ public class HandleHederaNativeOperations implements HederaNativeOperations { private final HandleContext context; + @Nullable + private final Key maybeEthSenderKey; + @Inject - public HandleHederaNativeOperations(@NonNull final HandleContext context) { + public HandleHederaNativeOperations(@NonNull final HandleContext context, @Nullable final Key maybeEthSenderKey) { this.context = requireNonNull(context); + this.maybeEthSenderKey = maybeEthSenderKey; } /** @@ -154,7 +159,7 @@ public void finalizeHollowAccountAsContract(@NonNull final Bytes evmAddress) { final AccountID toEntityId, @NonNull final VerificationStrategy strategy) { final var to = requireNonNull(getAccount(toEntityId)); - final var signatureTest = strategy.asSignatureTestIn(context); + final var signatureTest = strategy.asSignatureTestIn(context, maybeEthSenderKey); if (to.receiverSigRequired() && !signatureTest.test(to.keyOrThrow())) { return INVALID_SIGNATURE; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java index 8789b0e7f09c..9c1aaa64f256 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java @@ -35,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.spi.workflows.HandleContext; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.function.Predicate; import javax.inject.Inject; import org.apache.tuweni.bytes.Bytes; @@ -50,9 +51,13 @@ public class HandleSystemContractOperations implements SystemContractOperations { private final HandleContext context; + @Nullable + private final Key maybeEthSenderKey; + @Inject - public HandleSystemContractOperations(@NonNull final HandleContext context) { + public HandleSystemContractOperations(@NonNull final HandleContext context, @Nullable Key maybeEthSenderKey) { this.context = requireNonNull(context); + this.maybeEthSenderKey = maybeEthSenderKey; } /** @@ -60,7 +65,7 @@ public HandleSystemContractOperations(@NonNull final HandleContext context) { */ @Override public @NonNull Predicate activeSignatureTestWith(@NonNull final VerificationStrategy strategy) { - return strategy.asSignatureTestIn(context); + return strategy.asSignatureTestIn(context, maybeEthSenderKey); } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java new file mode 100644 index 000000000000..7d543bce71e8 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SpecificCryptoVerificationStrategy.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 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.contract.impl.exec.scope; + +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.Key; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A {@link VerificationStrategy} that will delegate verification of a specific key's signature + * to the top-level cryptographic verification strategy; and mark all other keys invalid. + */ +public class SpecificCryptoVerificationStrategy implements VerificationStrategy { + private final Key qualifyingKey; + + public SpecificCryptoVerificationStrategy(@NonNull final Key qualifyingKey) { + requireNonNull(qualifyingKey); + if (!qualifyingKey.hasEd25519() && !qualifyingKey.hasEcdsaSecp256k1()) { + throw new IllegalArgumentException("Qualifying key must be a cryptographic key"); + } + this.qualifyingKey = qualifyingKey; + } + + /** + * {@inheritDoc} + * + *

Returns {@link VerificationStrategy.Decision#DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION} if the key + * matches the qualifying key; {@link VerificationStrategy.Decision#INVALID} otherwise. + * + * @param key the key whose signature is to be verified + * @return the decision of the strategy + */ + @Override + public Decision decideForPrimitive(@NonNull final Key key) { + requireNonNull(key); + return qualifyingKey.equals(key) ? DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION : INVALID; + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java index 46f60be8ff36..ebdeab24cdf1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategy.java @@ -22,6 +22,8 @@ import com.hedera.hapi.node.base.KeyList; import com.hedera.node.app.spi.workflows.HandleContext; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; import java.util.function.Predicate; /** @@ -70,12 +72,16 @@ enum Decision { * given this strategy within the given {@link HandleContext}. * * @param context the context in which this strategy will be used + * @param maybeEthSenderKey the key that is the sender of the EVM message, if known * @return a predicate that tests whether a given key is a valid signature for a given key */ - default Predicate asSignatureTestIn(@NonNull final HandleContext context) { + default Predicate asSignatureTestIn( + @NonNull final HandleContext context, @Nullable final Key maybeEthSenderKey) { + requireNonNull(context); return new Predicate<>() { @Override - public boolean test(Key key) { + public boolean test(@NonNull final Key key) { + requireNonNull(key); return switch (key.key().kind()) { case KEY_LIST -> { final var keys = key.keyListOrThrow().keys(); @@ -108,8 +114,9 @@ public boolean test(Key key) { yield switch (decideForPrimitive(key)) { case VALID -> true; case INVALID -> false; - case DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION -> context.verificationFor(key) - .passed(); + // Note the EthereumTransaction sender's key has necessarily signed + case DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION -> Objects.equals(key, maybeEthSenderKey) + || context.verificationFor(key).passed(); }; } yield false; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 4e453590aa7f..bf13838d7167 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -47,11 +47,13 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TransactionID; +import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; @@ -77,7 +79,6 @@ public class ClassicCreatesCall extends AbstractCall { private final VerificationStrategy verificationStrategy; private final AccountID spenderId; - private long nonGasCost; public ClassicCreatesCall( @NonNull final SystemContractGasCalculator systemContractGasCalculator, @@ -115,7 +116,7 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu .build(); final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreateWithId, spenderId); // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas - this.nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); + long nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); if (frame.getValue().lessThan(Wei.of(nonGasCost))) { return completionWith( FIXED_GAS_COST, @@ -123,17 +124,19 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu RC_AND_ADDRESS_ENCODER.encodeElements((long) INSUFFICIENT_TX_FEE.protoOrdinal(), ZERO_ADDRESS)); } else { operations().collectFee(spenderId, nonGasCost); - // (future) remove after differential testing + // (FUTURE) remove after differential testing, mono-service used the entire + // value in the frame for the non-gas cost even if it was only a portion nonGasCost = frame.getValue().toLong(); } - final var validity = validityOfSynthOp(); + final var op = syntheticCreate.tokenCreationOrThrow(); + final var validity = validityOfSynth(op); if (validity != OK) { return gasOnly(revertResult(validity, FIXED_GAS_COST), validity, true); } // Choose a dispatch verification strategy based on whether the legacy activation address is active - final var dispatchVerificationStrategy = verificationStrategyFor(frame); + final var dispatchVerificationStrategy = verificationStrategyFor(frame, op); final var recordBuilder = systemContractOperations() .dispatch(syntheticCreate, dispatchVerificationStrategy, spenderId, ContractCallRecordBuilder.class); recordBuilder.status(standardized(recordBuilder.status())); @@ -143,7 +146,6 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu return gasPlus(revertResult(recordBuilder, FIXED_GAS_COST), status, false, nonGasCost); } else { ByteBuffer encodedOutput; - final var op = syntheticCreate.tokenCreationOrThrow(); final var customFees = op.customFees(); if (op.tokenType() == FUNGIBLE_COMMON) { if (customFees.isEmpty()) { @@ -170,8 +172,7 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu } } - private ResponseCodeEnum validityOfSynthOp() { - final var op = syntheticCreate.tokenCreationOrThrow(); + private ResponseCodeEnum validityOfSynth(@NonNull final TokenCreateTransactionBody op) { if (op.symbol().isEmpty()) { return MISSING_TOKEN_SYMBOL; } @@ -185,14 +186,21 @@ private ResponseCodeEnum validityOfSynthOp() { return OK; } - private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame frame) { + private VerificationStrategy verificationStrategyFor( + @NonNull final MessageFrame frame, @NonNull final TokenCreateTransactionBody op) { final var legacyActivation = legacyActivationIn(frame); - // Choose a dispatch verification strategy based on whether the legacy - // activation address is active (somewhere on the stack) + // If there is a crypto admin key, we need an either/or strategy to let it + // be activated by a top-level signature with that key + final var baseVerificationStrategy = hasCryptoAdminKey(op) + ? new EitherOrVerificationStrategy( + verificationStrategy, new SpecificCryptoVerificationStrategy(op.adminKeyOrThrow())) + : verificationStrategy; + // And our final dispatch verification strategy must very depending on if + // a legacy activation address is active (somewhere on the stack) return stackIncludesActiveAddress(frame, legacyActivation.besuAddress()) ? new EitherOrVerificationStrategy( - verificationStrategy, + baseVerificationStrategy, new ActiveContractVerificationStrategy( ContractID.newBuilder() .contractNum(legacyActivation.contractNum()) @@ -200,7 +208,12 @@ private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame legacyActivation.pbjAddress(), false, UseTopLevelSigs.NO)) - : verificationStrategy; + : baseVerificationStrategy; + } + + private boolean hasCryptoAdminKey(@NonNull final TokenCreateTransactionBody op) { + return op.hasAdminKey() + && (op.adminKeyOrThrow().hasEd25519() || op.adminKeyOrThrow().hasEcdsaSecp256k1()); } private LegacyActivation legacyActivationIn(@NonNull final MessageFrame frame) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java index dac423c4ff7e..ae559bf47797 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HydratedEthTxData.java @@ -43,4 +43,8 @@ public static HydratedEthTxData failureFrom(@NonNull final ResponseCodeEnum stat public boolean isAvailable() { return ethTxData != null; } + + public @NonNull EthTxData ethTxDataOrThrow() { + return requireNonNull(ethTxData); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java index 3b69ea86dd07..cb565c3763ae 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java @@ -126,7 +126,7 @@ public HevmTransactionFactory( this.tokenServiceApi = requireNonNull(tokenServiceApi); this.expiryValidator = requireNonNull(expiryValidator); this.attributeValidator = requireNonNull(attributeValidator); - this.ethereumSignatures = ethereumSignatures; + this.ethereumSignatures = requireNonNull(ethereumSignatures); this.hederaEvmContext = requireNonNull(hederaEvmContext); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java index eed488b53a6c..4af12e9ed15a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java @@ -16,10 +16,14 @@ package com.hedera.node.app.service.contract.impl.test.exec; +import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideActionSidecarContentTracer; import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideHederaEvmContext; +import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideSenderEcdsaKey; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_HEDERA_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_CALL_DATA; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -33,6 +37,7 @@ import com.hedera.hapi.node.contract.ContractCallTransactionBody; import com.hedera.hapi.node.contract.EthereumTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.node.app.service.contract.impl.exec.EvmActionTracer; import com.hedera.node.app.service.contract.impl.exec.TransactionModule; import com.hedera.node.app.service.contract.impl.exec.gas.CanonicalDispatchPrices; @@ -46,6 +51,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; +import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; @@ -96,6 +102,9 @@ class TransactionModuleTest { @Mock private EthereumCallDataHydration hydration; + @Mock + private EthTxSigsCache ethTxSigsCache; + @Mock private ReadableFileStore fileStore; @@ -117,6 +126,26 @@ void feesOnlyUpdaterIsProxyUpdater() { verify(hederaOperations).begin(); } + @Test + void providesNullSenderEcdsaKeyWithoutHydratedEthTxData() { + assertNull(provideSenderEcdsaKey(ethTxSigsCache, null)); + } + + @Test + void providesNullSenderEcdsaKeyWithUnavailableEthTxData() { + final var failedHydration = HydratedEthTxData.failureFrom(ACCOUNT_DELETED); + assertNull(provideSenderEcdsaKey(ethTxSigsCache, failedHydration)); + } + + @Test + void providesCorrespondingKeyForAvailableEthTxData() { + final var hydration = HydratedEthTxData.successFrom(ETH_DATA_WITH_CALL_DATA); + given(ethTxSigsCache.computeIfAbsent(ETH_DATA_WITH_CALL_DATA)) + .willReturn( + new EthTxSigs(A_SECP256K1_KEY.ecdsaSecp256k1OrThrow().toByteArray(), new byte[0])); + assertThat(provideSenderEcdsaKey(ethTxSigsCache, hydration)).isEqualTo(A_SECP256K1_KEY); + } + @Test void providesExpectedEvmContext() { final var recordBuilder = mock(ContractOperationRecordBuilder.class); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java index ac51bee7f262..90a33380dc6a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/ActiveContractVerificationStrategyTest.java @@ -18,6 +18,8 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ANOTHER_ED25519_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_SECP256K1_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.YET_ANOTHER_ED25519_KEY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.pbj.runtime.io.buffer.Bytes; import org.junit.jupiter.api.Test; @@ -135,15 +138,40 @@ void validatesKeysAsExpectedWhenDelegatePermissionRequiredAndNotUsingTopLevelSig assertEquals(VerificationStrategy.Decision.INVALID, subject.decideForPrimitive(CRYPTO_KEY)); } + @Test + void signatureTestApprovesEthSenderKeyWhenDelegating() { + final var subject = mock(VerificationStrategy.class); + doCallRealMethod().when(subject).asSignatureTestIn(context, A_SECP256K1_KEY); + given(subject.decideForPrimitive(A_SECP256K1_KEY)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + + final var test = subject.asSignatureTestIn(context, A_SECP256K1_KEY); + assertTrue(test.test(A_SECP256K1_KEY)); + } + + @Test + void signatureTestUsesContextVerificationWhenNotEthSenderKey() { + final var verification = mock(SignatureVerification.class); + final var subject = mock(VerificationStrategy.class); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); + given(verification.passed()).willReturn(true); + given(context.verificationFor(B_SECP256K1_KEY)).willReturn(verification); + given(subject.decideForPrimitive(B_SECP256K1_KEY)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + + final var test = subject.asSignatureTestIn(context, null); + assertTrue(test.test(B_SECP256K1_KEY)); + } + @Test void signatureTestApprovesAllValidKeyLists() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .keyList(KeyList.newBuilder().keys(AN_ED25519_KEY, ANOTHER_ED25519_KEY, YET_ANOTHER_ED25519_KEY)) .build(); @@ -153,11 +181,11 @@ void signatureTestApprovesAllValidKeyLists() { @Test void signatureTestRejectsIncompleteKeyLists() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .keyList(KeyList.newBuilder().keys(AN_ED25519_KEY, ANOTHER_ED25519_KEY, YET_ANOTHER_ED25519_KEY)) .build(); @@ -167,12 +195,12 @@ void signatureTestRejectsIncompleteKeyLists() { @Test void signatureTestApprovesSufficientThresholdKeys() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .thresholdKey(ThresholdKey.newBuilder() .threshold(2) @@ -185,12 +213,12 @@ void signatureTestApprovesSufficientThresholdKeys() { @Test void signatureTestRejectsInsufficientThresholdKeys() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); given(subject.decideForPrimitive(AN_ED25519_KEY)).willReturn(VerificationStrategy.Decision.VALID); given(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); given(subject.decideForPrimitive(YET_ANOTHER_ED25519_KEY)).willReturn(VerificationStrategy.Decision.INVALID); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); final var key = Key.newBuilder() .thresholdKey(ThresholdKey.newBuilder() .threshold(2) @@ -203,11 +231,11 @@ void signatureTestRejectsInsufficientThresholdKeys() { @Test void unsupportedKeyTypesAreNotPrimitive() { final var subject = mock(VerificationStrategy.class); - doCallRealMethod().when(subject).asSignatureTestIn(context); + doCallRealMethod().when(subject).asSignatureTestIn(context, null); final var aRsa3072Key = Key.newBuilder().rsa3072(Bytes.wrap("NONSENSE")).build(); - final var test = subject.asSignatureTestIn(context); + final var test = subject.asSignatureTestIn(context, null); assertFalse(test.test(aRsa3072Key)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java index 99308c7e5555..b595d1bf22da 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java @@ -51,15 +51,26 @@ void firstStrategyValidSuffices() { @Test void secondStrategyValidSuffices() { - given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(firstStrategy.decideForPrimitive(Key.DEFAULT)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.VALID); assertSame(VerificationStrategy.Decision.VALID, subject.decideForPrimitive(Key.DEFAULT)); } @Test - void oneStrategyMustBeValid() { + void invalidIfNeitherStrategyValid() { given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); assertSame(VerificationStrategy.Decision.INVALID, subject.decideForPrimitive(Key.DEFAULT)); } + + @Test + void delegatesIfPossibleAndNotAlreadyValid() { + given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(secondStrategy.decideForPrimitive(Key.DEFAULT)) + .willReturn(VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertSame( + VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION, + subject.decideForPrimitive(Key.DEFAULT)); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index 2376225b887f..e4620156ef70 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -23,6 +23,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_FUNGIBLE_RELATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CANONICAL_ALIAS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CIVILIAN_OWNED_NFT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; @@ -121,7 +122,7 @@ class HandleHederaNativeOperationsTest { @BeforeEach void setUp() { - subject = new HandleHederaNativeOperations(context); + subject = new HandleHederaNativeOperations(context, A_SECP256K1_KEY); deletedAccount = AccountID.newBuilder().accountNum(1L).build(); beneficiaryAccount = AccountID.newBuilder().accountNum(2L).build(); } @@ -251,7 +252,7 @@ void transferWithReceiverSigCheckUsesApi() { .accountNum(NON_SYSTEM_CONTRACT_ID.contractNumOrThrow()) .build(); given(accountStore.getAccountById(contractAccountId)).willReturn(PARANOID_SOMEBODY); - given(verificationStrategy.asSignatureTestIn(context)).willReturn(signatureTest); + given(verificationStrategy.asSignatureTestIn(context, A_SECP256K1_KEY)).willReturn(signatureTest); given(signatureTest.test(PARANOID_SOMEBODY.keyOrThrow())).willReturn(true); final var result = subject.transferWithReceiverSigCheck( @@ -272,7 +273,7 @@ void transferWithReceiverSigCheckReturnsInvalidSigIfAppropriate() { .accountNum(NON_SYSTEM_CONTRACT_ID.contractNumOrThrow()) .build(); given(accountStore.getAccountById(contractAccountId)).willReturn(PARANOID_SOMEBODY); - given(verificationStrategy.asSignatureTestIn(context)).willReturn(signatureTest); + given(verificationStrategy.asSignatureTestIn(context, A_SECP256K1_KEY)).willReturn(signatureTest); given(signatureTest.test(PARANOID_SOMEBODY.keyOrThrow())).willReturn(false); final var result = subject.transferWithReceiverSigCheck( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java index c596aee56865..1d5e5ae83a19 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java @@ -18,6 +18,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -82,7 +83,7 @@ class HandleSystemContractOperationsTest { @BeforeEach void setUp() { - subject = new HandleSystemContractOperations(context); + subject = new HandleSystemContractOperations(context, A_SECP256K1_KEY); } @Test @@ -97,7 +98,7 @@ void dispatchesRespectingGivenStrategy() { given(passed.passed()).willReturn(true); given(context.verificationFor(AN_ED25519_KEY)).willReturn(passed); given(context.verificationFor(TestHelpers.B_SECP256K1_KEY)).willReturn(failed); - doCallRealMethod().when(strategy).asSignatureTestIn(context); + doCallRealMethod().when(strategy).asSignatureTestIn(context, A_SECP256K1_KEY); subject.dispatch(TransactionBody.DEFAULT, strategy, A_NEW_ACCOUNT_ID, CryptoTransferRecordBuilder.class); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java new file mode 100644 index 000000000000..0a5b65155ce5 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/SpecificCryptoVerificationStrategyTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.contract.impl.test.exec.scope; + +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION; +import static com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision.INVALID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ANOTHER_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.AN_ED25519_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_CONTRACT_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_SECP256K1_KEY; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_SECP256K1_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.hedera.node.app.service.contract.impl.exec.scope.SpecificCryptoVerificationStrategy; +import org.junit.jupiter.api.Test; + +class SpecificCryptoVerificationStrategyTest { + @Test + void delegatesVerificationForSpecificEd25519KeyOnly() { + final var subject = new SpecificCryptoVerificationStrategy(AN_ED25519_KEY); + + assertThat(subject.decideForPrimitive(AN_ED25519_KEY)).isSameAs(DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertThat(subject.decideForPrimitive(ANOTHER_ED25519_KEY)).isSameAs(INVALID); + } + + @Test + void delegatesVerificationForSpecificECDSAKeyOnly() { + final var subject = new SpecificCryptoVerificationStrategy(A_SECP256K1_KEY); + + assertThat(subject.decideForPrimitive(A_SECP256K1_KEY)).isSameAs(DELEGATE_TO_CRYPTOGRAPHIC_VERIFICATION); + assertThat(subject.decideForPrimitive(B_SECP256K1_KEY)).isSameAs(INVALID); + } + + @Test + void failsToConstructWithoutCryptoKey() { + assertThrows(IllegalArgumentException.class, () -> new SpecificCryptoVerificationStrategy(A_CONTRACT_KEY)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java index 0a56f7213c96..64a269fc1c0a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/token/RandomTokenUnfreeze.java @@ -18,9 +18,9 @@ import static com.hedera.services.bdd.spec.infrastructure.providers.ops.token.RandomTokenDissociation.explicit; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_WAS_DELETED; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.infrastructure.OpProvider; @@ -33,9 +33,8 @@ public class RandomTokenUnfreeze implements OpProvider { private final RegistrySourcedNameProvider tokenRels; private final ResponseCodeEnum[] permissibleOutcomes = - standardOutcomesAnd(TOKEN_HAS_NO_FREEZE_KEY, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT); + standardOutcomesAnd(TOKEN_HAS_NO_FREEZE_KEY, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT, TOKEN_WAS_DELETED); private final ResponseCodeEnum[] customOutcomes; - private final ResponseCodeEnum[] customPrechecks = new ResponseCodeEnum[] {INVALID_SIGNATURE}; public RandomTokenUnfreeze( RegistrySourcedNameProvider tokenRels, ResponseCodeEnum[] customOutcomes) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java new file mode 100644 index 000000000000..576392e3a183 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/AccountCreationDetails.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.queries.meta; + +import com.esaulpaugh.headlong.abi.Address; +import com.hederahashgraph.api.proto.java.AccountID; + +public record AccountCreationDetails(AccountID createdId, Address evmAddress) {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java index 5ce9c240cf64..1e55e611d007 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java @@ -25,6 +25,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnUtils.asIdForKeyLookUp; import static com.hedera.services.bdd.spec.transactions.TxnUtils.asTokenId; import static com.hedera.services.bdd.spec.transactions.TxnUtils.isEndOfStakingPeriodRecord; +import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.schedule.HapiScheduleCreate.correspondingScheduledTxnId; import static com.hedera.services.bdd.suites.HapiSuite.HBAR_TOKEN_SENTINEL; import static com.hedera.services.bdd.suites.crypto.CryptoTransferSuite.sdec; @@ -144,6 +145,9 @@ public class HapiGetTxnRecord extends HapiQueryOp { @Nullable private Consumer> createdIdsObserver = null; + @Nullable + private Consumer> creationDetailsObserver = null; + @Nullable private Consumer> createdTokenIdsObserver = null; @@ -214,9 +218,14 @@ public HapiGetTxnRecord exposingCreationsTo(final Consumer> creatio return this; } + public HapiGetTxnRecord exposingCreationDetailsTo(final Consumer> observer) { + creationDetailsObserver = observer; + return andAllChildRecords(); + } + public HapiGetTxnRecord exposingTokenCreationsTo(final Consumer> createdTokenIdsObserver) { this.createdTokenIdsObserver = createdTokenIdsObserver; - return this; + return andAllChildRecords(); } public HapiGetTxnRecord exposingFilteredCallResultVia( @@ -903,11 +912,20 @@ protected void submitWith(final HapiSpec spec, final Transaction payment) throws allRecordsObserver.accept(allRecords); } final List creations = (createdIdsObserver != null) ? new ArrayList<>() : null; + final List creationDetails = + (creationDetailsObserver != null) ? new ArrayList<>() : null; final List tokenCreations = (createdTokenIdsObserver != null) ? new ArrayList<>() : null; for (final var rec : childRecords) { - if (rec.getReceipt().hasAccountID() && creations != null) { - creations.add( - HapiPropertySource.asAccountString(rec.getReceipt().getAccountID())); + if (rec.getReceipt().hasAccountID()) { + if (creations != null) { + creations.add( + HapiPropertySource.asAccountString(rec.getReceipt().getAccountID())); + } + if (creationDetails != null) { + creationDetails.add(new AccountCreationDetails( + rec.getReceipt().getAccountID(), + asHeadlongAddress(rec.getEvmAddress().toByteArray()))); + } } if (rec.getReceipt().hasTokenID() && tokenCreations != null) { tokenCreations.add(rec.getReceipt().getTokenID()); @@ -936,6 +954,9 @@ protected void submitWith(final HapiSpec spec, final Transaction payment) throws if (createdTokenIdsObserver != null) { createdTokenIdsObserver.accept(tokenCreations); } + if (creationDetailsObserver != null) { + creationDetailsObserver.accept(creationDetails); + } if (loggingOnlyFee && spec.ratesProvider().hasRateSet()) { final var priceInUsd = sdec(spec.ratesProvider().toUsdWithActiveRates(rcd.getTransactionFee()), 5); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java index a4c40fcad79b..2fd2482cf20e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java @@ -292,6 +292,12 @@ public static SubmitModificationsOp submitModified( return new SubmitModificationsOp(txnOpSupplier, modificationsFn); } + public static SubmitModificationsOp submitModifiedWithFixedPayer( + @NonNull final Function> modificationsFn, + @NonNull final Supplier> txnOpSupplier) { + return new SubmitModificationsOp(false, txnOpSupplier, modificationsFn); + } + /** * Returns an operation that repeatedly sends a query from the given * supplier, but each time after modifying the query with one of the diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java index 04f781223efd..65e4124252e0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/inventory/NewSpecKey.java @@ -19,6 +19,7 @@ import static com.hedera.services.bdd.spec.keys.DefaultKeyGen.DEFAULT_KEY_GEN; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType; import static com.swirlds.common.utility.CommonUtils.hex; +import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; import com.google.protobuf.ByteString; @@ -31,10 +32,13 @@ import com.hedera.services.bdd.spec.persistence.SpecKey; import com.hedera.services.bdd.spec.utilops.UtilOp; import com.hederahashgraph.api.proto.java.Key; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Optional; +import java.util.function.Consumer; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -46,6 +50,10 @@ public class NewSpecKey extends UtilOp { private boolean verboseLoggingOn = false; private boolean exportEd25519Mnemonic = false; private final String name; + + @Nullable + private Consumer keyObserver; + private Optional immediateExportLoc = Optional.empty(); private Optional immediateExportPass = Optional.empty(); private Optional type = Optional.empty(); @@ -63,6 +71,11 @@ public NewSpecKey exportingTo(String loc, String pass) { return this; } + public NewSpecKey exposingKeyTo(@NonNull final Consumer observer) { + keyObserver = requireNonNull(observer); + return this; + } + public NewSpecKey includingEd25519Mnemonic() { exportEd25519Mnemonic = true; return this; @@ -138,6 +151,9 @@ protected boolean submitOp(HapiSpec spec) throws Throwable { key = spec.keys().generate(spec, type.orElse(KeyType.SIMPLE), keyGen); } spec.registry().saveKey(name, key); + if (keyObserver != null) { + keyObserver.accept(key); + } if (immediateExportLoc.isPresent() && immediateExportPass.isPresent()) { final var exportLoc = immediateExportLoc.get(); final var exportPass = immediateExportPass.get(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java index de4cff7d2d54..a99885da9ed5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/QueryModificationsOp.java @@ -35,6 +35,10 @@ import java.util.stream.Stream; public class QueryModificationsOp extends UtilOp { + // An account to create as part of the send-with-modifications process + // to ensure fees are charged (the default payer is a superuser account + // that is never charged fees); this ensures we cover calculateFees() + // code paths in handlers private static final String MODIFIED_CIVILIAN_PAYER = "modifiedCivilianPayer"; private final boolean useCivilianPayer; private final Supplier> queryOpSupplier; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java index b03274975f29..3b1c0fe07493 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/SubmitModificationsOp.java @@ -54,7 +54,14 @@ public class SubmitModificationsOp extends UtilOp { public SubmitModificationsOp( @NonNull final Supplier> txnOpSupplier, @NonNull final Function> modificationsFn) { - this.useCivilianPayer = true; + this(true, txnOpSupplier, modificationsFn); + } + + public SubmitModificationsOp( + final boolean useCivilianPayer, + @NonNull final Supplier> txnOpSupplier, + @NonNull final Function> modificationsFn) { + this.useCivilianPayer = useCivilianPayer; this.txnOpSupplier = txnOpSupplier; this.modificationsFn = modificationsFn; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java index 9cdbf2e84c6f..bc7b34bbfd31 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java @@ -29,12 +29,19 @@ import static com.hedera.services.bdd.spec.assertions.ContractLogAsserts.logWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; +import static com.hedera.services.bdd.spec.keys.KeyShape.CONTRACT; +import static com.hedera.services.bdd.spec.keys.KeyShape.PREDEFINED_SHAPE; +import static com.hedera.services.bdd.spec.keys.KeyShape.sigs; +import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf; +import static com.hedera.services.bdd.spec.keys.SigControl.SECP256K1_ON; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumContractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCryptoTransferToExplicit; @@ -43,6 +50,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCodeWithConstructorArguments; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromToWithAlias; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; @@ -69,12 +77,14 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.junit.jupiter.api.Assertions.assertFalse; import com.google.protobuf.ByteString; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.queries.meta.AccountCreationDetails; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -121,7 +131,8 @@ List ethereumCalls() { topLevelSendToReceiverSigRequiredAccountReverts(), internalBurnToZeroAddressReverts(), ethereumCallWithCalldataBiggerThanMaxSucceeds(), - createWithSelfDestructInConstructorHasSaneRecord()); + createWithSelfDestructInConstructorHasSaneRecord(), + canCreateTokenWithCryptoAdminKeyOnlyIfHasTopLevelSig()); } List ethereumCreates() { @@ -132,6 +143,92 @@ List ethereumCreates() { doesNotCreateChildRecordIfEthereumContractCreateFails()); } + @HapiTest + HapiSpec canCreateTokenWithCryptoAdminKeyOnlyIfHasTopLevelSig() { + final var cryptoKey = "cryptoKey"; + final var thresholdKey = "thresholdKey"; + final String contract = "TestTokenCreateContract"; + final AtomicReference adminKey = new AtomicReference<>(); + final AtomicReference creationDetails = new AtomicReference<>(); + + return defaultHapiSpec("canCreateTokenWithCryptoAdminKeyOnlyIfHasTopLevelSig") + .given( + // Deploy our test contract + uploadInitCode(contract), + contractCreate(contract).gas(5_000_000L), + + // Create an ECDSA key + newKeyNamed(cryptoKey) + .shape(SECP256K1_ON) + .exposingKeyTo( + k -> adminKey.set(k.getECDSASecp256K1().toByteArray())), + // Create an account with an EVM address derived from this key + cryptoTransfer(tinyBarsFromToWithAlias(DEFAULT_PAYER, cryptoKey, 2 * ONE_HUNDRED_HBARS)) + .via("creation"), + // Get its EVM address for later use in the contract call + getTxnRecord("creation") + .exposingCreationDetailsTo(allDetails -> creationDetails.set(allDetails.getFirst())), + // Update key to a threshold key authorizing our contract use this account as a token treasury + newKeyNamed(thresholdKey) + .shape(threshOf(1, PREDEFINED_SHAPE, CONTRACT).signedWith(sigs(cryptoKey, contract))), + sourcing(() -> cryptoUpdate( + asAccountString(creationDetails.get().createdId())) + .key(thresholdKey) + .signedBy(DEFAULT_PAYER, cryptoKey))) + .when( + // First verify we fail to create without the admin key's top-level signature + sourcing(() -> contractCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + // Treasury is the EVM address + creationDetails.get().evmAddress(), + // Admin key is the ECDSA key + adminKey.get()) + .via("creationWithoutTopLevelSig") + .gas(5_000_000L) + .sending(100 * ONE_HBAR) + .hasKnownStatus(CONTRACT_REVERT_EXECUTED)), + // Next verify we succeed when using the top-level SignatureMap to + // sign with the admin key + sourcing(() -> contractCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + creationDetails.get().evmAddress(), + adminKey.get()) + .via("creationActivatingAdminKeyViaSigMap") + .gas(5_000_000L) + .sending(100 * ONE_HBAR) + // This is the important change, include a top-level signature with the admin key + .alsoSigningWithFullPrefix(cryptoKey)), + // Finally confirm we ALSO succeed when providing the admin key's + // signature via an EthereumTransaction signature + cryptoCreate(RELAYER).balance(10 * THOUSAND_HBAR), + sourcing(() -> ethereumCall( + contract, + "createFungibleTokenWithSECP256K1AdminKeyPublic", + creationDetails.get().evmAddress(), + adminKey.get()) + .type(EthTxData.EthTransactionType.EIP1559) + .nonce(0) + .signingWith(cryptoKey) + .payingWith(RELAYER) + .sending(50 * ONE_HBAR) + .maxGasAllowance(ONE_HBAR * 10) + .gasLimit(5_000_000L) + .via("creationActivatingAdminKeyViaEthTxSig"))) + .then( + childRecordsCheck( + "creationWithoutTopLevelSig", + CONTRACT_REVERT_EXECUTED, + recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE)), + getTxnRecord("creationActivatingAdminKeyViaSigMap") + .exposingTokenCreationsTo(createdIds -> + assertFalse(createdIds.isEmpty(), "Top-level sig map creation failed")), + getTxnRecord("creationActivatingAdminKeyViaEthTxSig") + .exposingTokenCreationsTo( + createdIds -> assertFalse(createdIds.isEmpty(), "EthTx sig creation failed"))); + } + @HapiTest HapiSpec badRelayClient() { final var adminKey = "adminKey"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java index 17ba8fc42aa3..ea9c8cf64fa7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/positive/SysDelSysUndelSpec.java @@ -22,7 +22,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileUndelete; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModifiedWithFixedPayer; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ENTITY_NOT_ALLOWED_TO_DELETE; @@ -69,7 +69,7 @@ public HapiSpec sysDelIdVariantsTreatedAsExpected() { return defaultHapiSpec("sysDelIdVariantsTreatedAsExpected") .given(fileCreate("misc").contents(ORIG_FILE)) .when() - .then(submitModified(withSuccessivelyVariedBodyIds(), () -> systemFileDelete("misc") + .then(submitModifiedWithFixedPayer(withSuccessivelyVariedBodyIds(), () -> systemFileDelete("misc") .payingWith(SYSTEM_DELETE_ADMIN))); } @@ -78,7 +78,7 @@ public HapiSpec sysUndelIdVariantsTreatedAsExpected() { return defaultHapiSpec("sysUndelIdVariantsTreatedAsExpected") .given(fileCreate("misc").contents(ORIG_FILE)) .when(systemFileDelete("misc").payingWith(SYSTEM_DELETE_ADMIN)) - .then(submitModified(withSuccessivelyVariedBodyIds(), () -> systemFileUndelete("misc") + .then(submitModifiedWithFixedPayer(withSuccessivelyVariedBodyIds(), () -> systemFileUndelete("misc") .payingWith(SYSTEM_UNDELETE_ADMIN))); } diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin new file mode 100644 index 000000000000..31ac1047f785 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.bin @@ -0,0 +1 @@ +0x60c06040526009608090815268746f6b656e4e616d6560b81b60a0526002906200002a90826200026c565b5060408051808201909152600b81526a1d1bdad95b94de5b589bdb60aa1b60208201526003906200005c90826200026c565b50604080518082019091526004808252636d656d6f60e01b6020830152906200008690826200026c565b50600580546001600160a81b0319167008000000000000271000000000000003e8179055348015620000b757600080fd5b50600160208181527fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb499190915560027fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f5560047fd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f5560087f7dfe757ecd65cbd7922a9c0161e935dd7fdbcc0e999689c7d31633896b1fc60b5560107fedc95719e9a3b28dd8e80877cb5880a9be7de1a13fc8b05e7999683b6b567643557fe2689cd4a84e23ad2f564004f1c9013e9589d260bde6380aba3ca7e09e4df40c55600660005260407f8f331abe73332f95a25873e8b430885974c0409691f89d643119a11623a7924a5562000338565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620001f057607f821691505b6020821081036200021157634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000267576000816000526020600020601f850160051c81016020861015620002425750805b601f850160051c820191505b8181101562000263578281556001016200024e565b5050505b505050565b81516001600160401b03811115620002885762000288620001c5565b620002a081620002998454620001db565b8462000217565b602080601f831160018114620002d85760008415620002bf5750858301515b600019600386901b1c1916600185901b17855562000263565b600085815260208120601f198616915b828110156200030957888601518255948401946001909101908401620002e8565b5085821015620003285787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b613f6b80620003486000396000f3fe6080604052600436106101145760003560e01c80638ba74da0116100a0578063d85f74c111610064578063d85f74c1146102db578063de84a779146102ee578063e9f732261461030e578063f4a01e5b14610321578063f848fec41461034157600080fd5b80638ba74da0146102575780638f74a17b1461026a5780639b23d3d914610295578063b35d417a146102b5578063cc265af4146102c857600080fd5b806336348de3116100e757806336348de3146101b45780634b5c6687146101d45780634d1769d6146101e7578063618dc65e1461021657806371de37071461024457600080fd5b80630488c939146101195780630fd2601e1461012e578063118741e71461016657806315dacbea14610194575b600080fd5b61012c610127366004612f56565b610361565b005b34801561013a57600080fd5b5061014e610149366004612f56565b6108d4565b60405160079190910b81526020015b60405180910390f35b34801561017257600080fd5b5061018661018136600461306a565b610931565b60405190815260200161015d565b3480156101a057600080fd5b5061014e6101af36600461311e565b610985565b3480156101c057600080fd5b506101866101cf3660046133e4565b610a79565b61012c6101e236600461352c565b610a85565b3480156101f357600080fd5b50610207610202366004613549565b610e8d565b60405161015d93929190613660565b34801561022257600080fd5b5061023661023136600461368b565b610f4e565b60405161015d92919061372b565b61012c61025236600461374c565b61106c565b61012c610265366004612f56565b6111d1565b61027d61027836600461368b565b61163d565b6040516001600160a01b03909116815260200161015d565b3480156102a157600080fd5b5061014e6102b036600461311e565b6119ff565b61012c6102c336600461368b565b611a44565b61012c6102d636600461368b565b611e15565b61012c6102e936600461352c565b611eec565b3480156102fa57600080fd5b50610207610309366004613549565b612287565b61027d61031c36600461368b565b612321565b34801561032d57600080fd5b5061018661033c366004612f56565b6123fb565b34801561034d57600080fd5b5061018661035c3660046137b0565b612407565b60408051600580825260c08201909252600091816020015b610381612ed1565b8152602001906001900390816103795790505090506103b460006006600060405180602001604052806000815250612463565b816000815181106103c7576103c76137f1565b60200260200101819052506103ee600160006040518060200160405280600081525061249a565b81600181518110610401576104016137f1565b6020026020010181905250610428600260006040518060200160405280600081525061249a565b8160028151811061043b5761043b6137f1565b6020026020010181905250610462600460006040518060200160405280600081525061249a565b81600381518110610475576104756137f1565b602002602001018190525061049c600360006040518060200160405280600081525061249a565b816004815181106104af576104af6137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461050790613807565b80601f016020809104026020016040519081016040528092919081815260200182805461053390613807565b80156105805780601f1061055557610100808354040283529160200191610580565b820191906000526020600020905b81548152906001019060200180831161056357829003601f168201915b505050505081526020016003805461059790613807565b80601f01602080910402602001604051908101604052809291908181526020018280546105c390613807565b80156106105780601f106105e557610100808354040283529160200191610610565b820191906000526020600020905b8154815290600101906020018083116105f357829003601f168201915b50505050508152602001866001600160a01b031681526020016004805461063690613807565b80601f016020809104026020016040519081016040528092919081815260200182805461066290613807565b80156106af5780601f10610684576101008083540402835291602001916106af565b820191906000526020600020905b81548152906001019060200180831161069257829003601f168201915b5050509183525050600160208201819052600554600160401b810460070b6040840152600160a01b900460ff16151560608301526080820186905260a090910184905290915060009060405190808252806020026020018201604052801561075157816020015b6040805160a0810182526000808252602080830182905292820181905260608201819052608082015282526000199092019101816107165790505b506040805160a081018252600181526001600160a01b0380891660208301526000928201839052606082018390528916608082015282519293509183919061079b5761079b6137f1565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160c08101825260008082526020808301829052928201819052606082018190526080820181905260a082015282526000199092019101816107bd5790505090506040518060c00160405280600460070b8152602001600560070b8152602001600a60070b8152602001876001600160a01b03168152602001600015158152602001886001600160a01b031681525081600081518110610862576108626137f1565b602002602001018190525060008061087b8585856124cf565b90925090506016821461088d57600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1505050505050505050565b60006108e083836125f7565b604051600782900b81529091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a1600781900b60161461092b57600080fd5b92915050565b600061093d83836126da565b90507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8160405161097091815260200190565b60405180910390a16016811461092b57600080fd5b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790630aed65f560e11b9060a4015b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610a049190613841565b6000604051808303816000865af19150503d8060008114610a41576040519150601f19603f3d011682016040523d82523d6000602084013e610a46565b606091505b509150915081610a57576015610a6b565b80806020019051810190610a6b919061386f565b60030b979650505050505050565b600061093d8383612706565b60408051600580825260c08201909252600091816020015b610aa5612ed1565b815260200190600190039081610a9d579050509050610ad860006006600060405180602001604052806000815250612463565b81600081518110610aeb57610aeb6137f1565b6020026020010181905250610b12600160006040518060200160405280600081525061249a565b81600181518110610b2557610b256137f1565b6020026020010181905250610b4c600260006040518060200160405280600081525061249a565b81600281518110610b5f57610b5f6137f1565b6020026020010181905250610b86600360006040518060200160405280600081525061249a565b81600381518110610b9957610b996137f1565b6020026020010181905250610bc0600460006040518060200160405280600081525061249a565b81600481518110610bd357610bd36137f1565b602002602001018190525060006040518060600160405280600060070b8152602001846001600160a01b03168152602001627a120060070b8152509050600060405180610120016040528060028054610c2b90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610c5790613807565b8015610ca45780601f10610c7957610100808354040283529160200191610ca4565b820191906000526020600020905b815481529060010190602001808311610c8757829003601f168201915b5050505050815260200160038054610cbb90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610ce790613807565b8015610d345780601f10610d0957610100808354040283529160200191610d34565b820191906000526020600020905b815481529060010190602001808311610d1757829003601f168201915b50505050508152602001856001600160a01b0316815260200160048054610d5a90613807565b80601f0160208091040260200160405190810160405280929190818152602001828054610d8690613807565b8015610dd35780601f10610da857610100808354040283529160200191610dd3565b820191906000526020600020905b815481529060010190602001808311610db657829003601f168201915b505050918352505060016020820152600554600160401b8104600790810b604084015260ff600160a01b830416151560608401526080830187905260a09092018590529192506000918291610e3791859181900b90600160801b900460030b612732565b909250905060168214610e4957600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1505050505050565b6000806060610e9d86868661279f565b60405183815292955090935091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a160168314610ee557600080fd5b7ffc6b20023c4bac8ff1c48c1693e0cea5cd3c2163e9c2da41c58f17dd6d9f163d8282604051610f1692919061388a565b60405180910390a1610f4486303384600081518110610f3757610f376137f1565b602002602001015161289d565b5093509350939050565b600060606000806101676001600160a01b031663618dc65e60e01b8787604051602401610f7c9291906138a6565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610fba9190613841565b6000604051808303816000865af19150503d8060008114610ff7576040519150601f19603f3d011682016040523d82523d6000602084013e610ffc565b606091505b50915091507f4af4780e06fe8cb9df64b0794fa6f01399af979175bb988e35e0e57e594567bc82826040516110329291906138c8565b60405180910390a1816110565760156040518060200160405280600081525061105a565b6016815b60039190910b97909650945050505050565b604051638f74a17b60e01b81526000903090638f74a17b90349061109690889088906004016138a6565b60206040518083038185885af11580156110b4573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906110d991906138e3565b60405163f4a01e5b60e01b81523360048201526001600160a01b0382166024820152909150309063f4a01e5b906044016020604051808303816000875af1158015611128573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061114c9190613900565b506040516307e9300f60e11b81526001600160a01b03821660048201523360248201523090630fd2601e906044016020604051808303816000875af1158015611199573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111bd9190613919565b506111ca813033856128e5565b5050505050565b604080516001808252818301909252600091816020015b6111f0612ed1565b8152602001906001900390816111e8579050509050611222600080600060405180602001604052806000815250612463565b81600081518110611235576112356137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461128d90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546112b990613807565b80156113065780601f106112db57610100808354040283529160200191611306565b820191906000526020600020905b8154815290600101906020018083116112e957829003601f168201915b505050505081526020016003805461131d90613807565b80601f016020809104026020016040519081016040528092919081815260200182805461134990613807565b80156113965780601f1061136b57610100808354040283529160200191611396565b820191906000526020600020905b81548152906001019060200180831161137957829003601f168201915b50505050508152602001866001600160a01b03168152602001600480546113bc90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546113e890613807565b80156114355780601f1061140a57610100808354040283529160200191611435565b820191906000526020600020905b81548152906001019060200180831161141857829003601f168201915b5050509183525050600160208201819052600554600160401b900460070b6040808401919091526000606084018190526080840188905260a09093018690528051828152808201909152929350909190816020015b6040805160a08101825260008082526020808301829052928201819052606082018190526080820152825260001990920191018161148a5750506040805160a081018252600181526001600160a01b0388811660208301526000928201839052606082018390528916608082015282519293509183919061150d5761150d6137f1565b6020908102919091010152604080516001808252818301909252600091816020015b6040805160c08101825260008082526020808301829052928201819052606082018190526080820181905260a0820152825260001990920191018161152f5750506040805160c0810182526004815260056020820152600a91810191909152601e60608201526000608082018190526001600160a01b038a1660a0830152825192935090918391906115c3576115c36137f1565b602090810291909101015260055460009081906115f3908690600781900b90600160801b900460030b878761292d565b915091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8260405161162891815260200190565b60405180910390a16016821461088d57600080fd5b60408051600580825260c08201909252600091829190816020015b611660612ed1565b81526020019060019003908161165857905050905061168460006006600386612463565b81600081518110611697576116976137f1565b60200260200101819052506116af600160038561249a565b816001815181106116c2576116c26137f1565b60200260200101819052506116da600260038561249a565b816002815181106116ed576116ed6137f1565b6020026020010181905250611705600460038561249a565b81600381518110611718576117186137f1565b602002602001018190525061172f6003808561249a565b81600481518110611742576117426137f1565b602002602001018190525060006040518060600160405280600060070b8152602001866001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461179a90613807565b80601f01602080910402602001604051908101604052809291908181526020018280546117c690613807565b80156118135780601f106117e857610100808354040283529160200191611813565b820191906000526020600020905b8154815290600101906020018083116117f657829003601f168201915b505050505081526020016003805461182a90613807565b80601f016020809104026020016040519081016040528092919081815260200182805461185690613807565b80156118a35780601f10611878576101008083540402835291602001916118a3565b820191906000526020600020905b81548152906001019060200180831161188657829003601f168201915b50505050508152602001876001600160a01b03168152602001600480546118c990613807565b80601f01602080910402602001604051908101604052809291908181526020018280546118f590613807565b80156119425780601f1061191757610100808354040283529160200191611942565b820191906000526020600020905b81548152906001019060200180831161192557829003601f168201915b505050918352505060016020820152600554600160401b8104600790810b604084015260ff600160a01b830416151560608401526080830187905260a090920185905291925060009182916119a691859181900b90600160801b900460030b612732565b9092509050601682146119b857600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a1979650505050505050565b6040516001600160a01b038581166024830152848116604483015283166064820152608481018290526000908190819061016790639b23d3d960e01b9060a4016109c6565b60408051600680825260e08201909252600091816020015b611a64612ed1565b815260200190600190039081611a5c579050509050611a8860006006600385612463565b81600081518110611a9b57611a9b6137f1565b6020026020010181905250611ab3600160038461249a565b81600181518110611ac657611ac66137f1565b6020026020010181905250611ade600260038461249a565b81600281518110611af157611af16137f1565b6020026020010181905250611b09600460038461249a565b81600381518110611b1c57611b1c6137f1565b6020026020010181905250611b336003808461249a565b81600481518110611b4657611b466137f1565b6020026020010181905250611b5e6000600130612a5b565b81600581518110611b7157611b716137f1565b602002602001018190525060006040518060600160405280600060070b8152602001856001600160a01b03168152602001627a120060070b8152509050600060405180610120016040528060028054611bc990613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611bf590613807565b8015611c425780601f10611c1757610100808354040283529160200191611c42565b820191906000526020600020905b815481529060010190602001808311611c2557829003601f168201915b5050505050815260200160038054611c5990613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611c8590613807565b8015611cd25780601f10611ca757610100808354040283529160200191611cd2565b820191906000526020600020905b815481529060010190602001808311611cb557829003601f168201915b50505050508152602001866001600160a01b0316815260200160048054611cf890613807565b80601f0160208091040260200160405190810160405280929190818152602001828054611d2490613807565b8015611d715780601f10611d4657610100808354040283529160200191611d71565b820191906000526020600020905b815481529060010190602001808311611d5457829003601f168201915b505050918352505060016020820152600554600160401b810460070b6040830152600160a01b900460ff16151560608201526080810185905260a0018390529050600080611dbe83612a86565b909250905060168214611dd057600080fd5b6040516001600160a01b03821681527f7bb17726df1f3adee8aa00ba8e8bc5d6f182af3bbf77604639cb7f008dd3b4ed9060200160405180910390a150505050505050565b60408051600480825260a08201909252600091816020015b611e35612ed1565b815260200190600190039081611e2d579050509050611e5960006006600385612463565b81600081518110611e6c57611e6c6137f1565b6020026020010181905250611e84600260038461249a565b81600181518110611e9757611e976137f1565b6020026020010181905250611eaf600460038461249a565b81600281518110611ec257611ec26137f1565b6020026020010181905250611ed96003808461249a565b81600381518110611b7157611b716137f1565b60408051600580825260c08201909252600091816020015b611f0c612ed1565b815260200190600190039081611f04579050509050611f3f60006006600060405180602001604052806000815250612463565b81600081518110611f5257611f526137f1565b6020026020010181905250611f79600160006040518060200160405280600081525061249a565b81600181518110611f8c57611f8c6137f1565b6020026020010181905250611fb3600260006040518060200160405280600081525061249a565b81600281518110611fc657611fc66137f1565b6020026020010181905250611fed600460006040518060200160405280600081525061249a565b81600381518110612000576120006137f1565b6020026020010181905250612027600360006040518060200160405280600081525061249a565b8160048151811061203a5761203a6137f1565b602002602001018190525060006040518060600160405280600060070b8152602001846001600160a01b03168152602001627a120060070b815250905060006040518061012001604052806002805461209290613807565b80601f01602080910402602001604051908101604052809291908181526020018280546120be90613807565b801561210b5780601f106120e05761010080835404028352916020019161210b565b820191906000526020600020905b8154815290600101906020018083116120ee57829003601f168201915b505050505081526020016003805461212290613807565b80601f016020809104026020016040519081016040528092919081815260200182805461214e90613807565b801561219b5780601f106121705761010080835404028352916020019161219b565b820191906000526020600020905b81548152906001019060200180831161217e57829003601f168201915b50505050508152602001856001600160a01b03168152602001600480546121c190613807565b80601f01602080910402602001604051908101604052809291908181526020018280546121ed90613807565b801561223a5780601f1061220f5761010080835404028352916020019161223a565b820191906000526020600020905b81548152906001019060200180831161221d57829003601f168201915b505050918352505060016020820152600554600160401b810460070b6040830152600160a01b900460ff16151560608201526080810185905260a0018390529050600080610e3783612a86565b600080606061229786868661279f565b60405183815292955090935091507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa9060200160405180910390a1601683146122df57600080fd5b7ffc6b20023c4bac8ff1c48c1693e0cea5cd3c2163e9c2da41c58f17dd6d9f163d828260405161231092919061388a565b60405180910390a193509350939050565b60408051600480825260a08201909252600091829190816020015b612344612ed1565b81526020019060019003908161233c57905050905061236860006006600386612463565b8160008151811061237b5761237b6137f1565b6020026020010181905250612393600260038561249a565b816001815181106123a6576123a66137f1565b60200260200101819052506123be600460038561249a565b816002815181106123d1576123d16137f1565b60200260200101819052506123e86003808561249a565b81600381518110611742576117426137f1565b600061093d8383612b96565b6000612414848484612bcc565b90507f90a5cf4cffe88b4edbb041cfc7a8a812c48a5ec30b84640fb37690875168e3aa8160405161244791815260200190565b60405180910390a16016811461245c57600080fd5b9392505050565b61246b612ed1565b60405180604001604052806124808787612ca5565b815260200161248f8585612ce0565b905295945050505050565b6124a2612ed1565b60405180604001604052806124b686612df1565b81526020016124c58585612ce0565b9052949350505050565b600080848061010001516000015160070b60001480156124f957506101008101516040015160070b155b1561250f576101008101516276a7006040909101525b6000806101676001600160a01b03163463abb54eb560e01b8a8a8a60405160240161253c93929190613b6d565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161257a9190613841565b60006040518083038185875af1925050503d80600081146125b7576040519150601f19603f3d011682016040523d82523d6000602084013e6125bc565b606091505b5091509150816125cf57601560006125e3565b808060200190518101906125e39190613c26565b60039190910b999098509650505050505050565b6040516001600160a01b038381166024830152821660448201526000908190819061016790638f8d7f9960e01b906064015b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516126679190613841565b6000604051808303816000865af19150503d80600081146126a4576040519150601f19603f3d011682016040523d82523d6000602084013e6126a9565b606091505b5091509150816126ba5760156126ce565b808060200190518101906126ce919061386f565b60030b95945050505050565b60008060006101676001600160a01b0316632e63879b60e01b8686604051602401612629929190613c52565b60008060006101676001600160a01b0316637d305cfa60e01b8686604051602401612629929190613caf565b600080848061010001516000015160070b600014801561275c57506101008101516040015160070b155b15612772576101008101516276a7006040909101525b6000806101676001600160a01b031634630fb65bf360e01b8a8a8a60405160240161253c93929190613cd1565b60008060606000806101676001600160a01b031663e0f4059a60e01b8989896040516024016127d093929190613d00565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161280e9190613841565b6000604051808303816000865af19150503d806000811461284b576040519150601f19603f3d011682016040523d82523d6000602084013e612850565b606091505b509150915081612873576040805160008082526020820190925260159190612887565b808060200190518101906128879190613d7f565b60039290920b9a90995090975095505050505050565b6040516001600160a01b038581166024830152848116604483015283166064820152600782900b60848201526000908190819061016790635cfc901160e01b9060a4016109c6565b6040516001600160a01b038581166024830152848116604483015283166064820152600782900b6084820152600090819081906101679063eca3691760e01b9060a4016109c6565b600080868061010001516000015160070b600014801561295757506101008101516040015160070b155b1561296d576101008101516276a7006040909101525b6000806101676001600160a01b031634632af0c59a60e01b8c8c8c8c8c60405160240161299e959493929190613e3f565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925290516129dc9190613841565b60006040518083038185875af1925050503d8060008114612a19576040519150601f19603f3d011682016040523d82523d6000602084013e612a1e565b606091505b509150915081612a315760156000612a45565b80806020019051810190612a459190613c26565b60039190910b9b909a5098505050505050505050565b612a63612ed1565b6040518060400160405280612a7786612df1565b81526020016124c58585612e30565b600080828061010001516000015160070b6000148015612ab057506101008101516040015160070b155b15612ac6576101008101516276a7006040909101525b6000806101676001600160a01b03163463ea83f29360e01b88604051602401612aef9190613f0c565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051612b2d9190613841565b60006040518083038185875af1925050503d8060008114612b6a576040519150601f19603f3d011682016040523d82523d6000602084013e612b6f565b606091505b509150915081612b82576015600061105a565b8080602001905181019061105a9190613c26565b6040516001600160a01b03838116602483015282166044820152600090819081906101679063248a35ef60e11b90606401612629565b604080516001600160a01b03858116602483015284166044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b031663e1f21c6760e01b17905290516000918291829161016791612c319190613841565b6000604051808303816000865af19150503d8060008114612c6e576040519150601f19603f3d011682016040523d82523d6000602084013e612c73565b606091505b509150915081612c84576015612c98565b80806020019051810190612c98919061386f565b60030b9695505050505050565b6000612cc9836006811115612cbc57612cbc613f1f565b600160ff9091161b821790565b905061245c826006811115612cbc57612cbc613f1f565b612d266040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b6000836004811115612d3a57612d3a613f1f565b03612d48576001815261092b565b6001836004811115612d5c57612d5c613f1f565b03612d77576000546001600160a01b0316602082015261092b565b6002836004811115612d8b57612d8b613f1f565b03612d9c576040810182905261092b565b6003836004811115612db057612db0613f1f565b03612dc1576060810182905261092b565b6004836004811115612dd557612dd5613f1f565b0361092b576000546001600160a01b0316608082015292915050565b600060016000836006811115612e0957612e09613f1f565b6006811115612e1a57612e1a613f1f565b8152602001908152602001600020549050919050565b612e766040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b6001836004811115612e8a57612e8a613f1f565b03612ea3576001600160a01b038216602082015261092b565b6004836004811115612eb757612eb7613f1f565b0361092b576001600160a01b038216608082015292915050565b604051806040016040528060008152602001612f296040518060a0016040528060001515815260200160006001600160a01b03168152602001606081526020016060815260200160006001600160a01b031681525090565b905290565b6001600160a01b0381168114612f4357600080fd5b50565b8035612f5181612f2e565b919050565b60008060408385031215612f6957600080fd5b8235612f7481612f2e565b91506020830135612f8481612f2e565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b60405290565b60405160a0810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b604051610120810167ffffffffffffffff81118282101715612fc857612fc8612f8f565b604051601f8201601f1916810167ffffffffffffffff8111828210171561303e5761303e612f8f565b604052919050565b600067ffffffffffffffff82111561306057613060612f8f565b5060051b60200190565b6000806040838503121561307d57600080fd5b823561308881612f2e565b915060208381013567ffffffffffffffff8111156130a557600080fd5b8401601f810186136130b657600080fd5b80356130c96130c482613046565b613015565b81815260059190911b820183019083810190888311156130e857600080fd5b928401925b8284101561310f57833561310081612f2e565b825292840192908401906130ed565b80955050505050509250929050565b6000806000806080858703121561313457600080fd5b843561313f81612f2e565b9350602085013561314f81612f2e565b9250604085013561315f81612f2e565b9396929550929360600135925050565b600082601f83011261318057600080fd5b813567ffffffffffffffff81111561319a5761319a612f8f565b6131ad601f8201601f1916602001613015565b8181528460208386010111156131c257600080fd5b816020850160208301376000918101602001919091529392505050565b80358015158114612f5157600080fd5b8060070b8114612f4357600080fd5b8035612f51816131ef565b600082601f83011261321a57600080fd5b8135602061322a6130c483613046565b82815260059290921b8401810191818101908684111561324957600080fd5b8286015b8481101561336357803567ffffffffffffffff8082111561326d57600080fd5b908801906040601f19838c03810182131561328757600080fd5b61328f612fa5565b88850135815282850135848111156132a657600080fd5b949094019360a0858e03830112156132be5760008081fd5b6132c6612fce565b91506132d38986016131df565b8252828501356132e281612f2e565b828a0152606085810135858111156132fa5760008081fd5b6133088f8c838a010161316f565b85850152506080935083860135858111156133235760008081fd5b6133318f8c838a010161316f565b82850152505060a0850135945061334785612f2e565b918101939093528087019290925250835291830191830161324d565b509695505050505050565b60006060828403121561338057600080fd5b6040516060810181811067ffffffffffffffff821117156133a3576133a3612f8f565b60405290508082356133b4816131ef565b815260208301356133c481612f2e565b602082015260408301356133d7816131ef565b6040919091015292915050565b600080604083850312156133f757600080fd5b823561340281612f2e565b9150602083013567ffffffffffffffff8082111561341f57600080fd5b90840190610160828703121561343457600080fd5b61343c612ff1565b82358281111561344b57600080fd5b6134578882860161316f565b82525060208301358281111561346c57600080fd5b6134788882860161316f565b60208301525061348a60408401612f46565b60408201526060830135828111156134a157600080fd5b6134ad8882860161316f565b6060830152506134bf608084016131df565b60808201526134d060a084016131fe565b60a08201526134e160c084016131df565b60c082015260e0830135828111156134f857600080fd5b61350488828601613209565b60e083015250610100915061351b8783850161336e565b828201528093505050509250929050565b60006020828403121561353e57600080fd5b813561245c81612f2e565b60008060006060848603121561355e57600080fd5b833561356981612f2e565b925060208481013561357a816131ef565b9250604085013567ffffffffffffffff8082111561359757600080fd5b818701915087601f8301126135ab57600080fd5b81356135b96130c482613046565b81815260059190911b8301840190848101908a8311156135d857600080fd5b8585015b83811015613610578035858111156135f45760008081fd5b6136028d89838a010161316f565b8452509186019186016135dc565b508096505050505050509250925092565b60008151808452602080850194506020840160005b8381101561365557815160070b87529582019590820190600101613636565b509495945050505050565b8381528260070b60208201526060604082015260006136826060830184613621565b95945050505050565b6000806040838503121561369e57600080fd5b82356136a981612f2e565b9150602083013567ffffffffffffffff8111156136c557600080fd5b6136d18582860161316f565b9150509250929050565b60005b838110156136f65781810151838201526020016136de565b50506000910152565b600081518084526137178160208601602086016136db565b601f01601f19169290920160200192915050565b82815260406020820152600061374460408301846136ff565b949350505050565b60008060006060848603121561376157600080fd5b833561376c81612f2e565b9250602084013567ffffffffffffffff81111561378857600080fd5b6137948682870161316f565b92505060408401356137a5816131ef565b809150509250925092565b6000806000606084860312156137c557600080fd5b83356137d081612f2e565b925060208401356137e081612f2e565b929592945050506040919091013590565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061381b57607f821691505b60208210810361383b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516138538184602087016136db565b9190910192915050565b8051600381900b8114612f5157600080fd5b60006020828403121561388157600080fd5b61245c8261385d565b8260070b81526040602082015260006137446040830184613621565b6001600160a01b038316815260406020820152600061374460408301846136ff565b821515815260406020820152600061374460408301846136ff565b6000602082840312156138f557600080fd5b815161245c81612f2e565b60006020828403121561391257600080fd5b5051919050565b60006020828403121561392b57600080fd5b815161245c816131ef565b600082825180855260208086019550808260051b84010181860160005b848110156139f057601f198684030189528151604081518552858201519150808686015281511515818601528582015160606001600160a01b038083168289015283850151935060a09250608083818a01526139b260e08a01866136ff565b92860151898403603f1901858b01529294506139ce85846136ff565b9501511660c09790970196909652505098840198925090830190600101613953565b5090979650505050505050565b60006101608251818552613a13828601826136ff565b91505060208301518482036020860152613a2d82826136ff565b9150506040830151613a4a60408601826001600160a01b03169052565b5060608301518482036060860152613a6282826136ff565b9150506080830151613a78608086018215159052565b5060a0830151613a8d60a086018260070b9052565b5060c0830151613aa160c086018215159052565b5060e083015184820360e0860152613ab98282613936565b91505061010080840151613af582870182805160070b82526001600160a01b036020820151166020830152604081015160070b60408301525050565b5090949350505050565b60008151808452602080850194506020840160005b83811015613655578151805160070b8852838101516001600160a01b03908116858a01526040808301511515908a01526060808301511515908a0152608091820151169088015260a09096019590820190600101613b14565b60006060808352613b8160608401876139fd565b602084820381860152613b948288613aff565b9150604085830360408701528287518085528385019150838901945060005b81811015613c165785518051600790810b855286820151810b8786015285820151900b85850152878101516001600160a01b039081168986015260808083015115159086015260a09182015116908401529484019460c090920191600101613bb3565b50909a9950505050505050505050565b60008060408385031215613c3957600080fd5b613c428361385d565b91506020830151612f8481612f2e565b6000604082016001600160a01b03808616845260206040602086015282865180855260608701915060208801945060005b81811015613ca1578551851683529483019491830191600101613c83565b509098975050505050505050565b6001600160a01b038316815260406020820152600061374460408301846139fd565b606081526000613ce460608301866139fd565b90508360070b60208301528260030b6040830152949350505050565b6000606082016001600160a01b038616835260208560070b60208501526060604085015281855180845260808601915060808160051b87010193506020870160005b82811015613d7057607f19888703018452613d5e8683516136ff565b95509284019290840190600101613d42565b50939998505050505050505050565b600080600060608486031215613d9457600080fd5b613d9d8461385d565b9250602080850151613dae816131ef565b604086015190935067ffffffffffffffff811115613dcb57600080fd5b8501601f81018713613ddc57600080fd5b8051613dea6130c482613046565b81815260059190911b82018301908381019089831115613e0957600080fd5b928401925b82841015613e30578351613e21816131ef565b82529284019290840190613e0e565b80955050505050509250925092565b600060a0808352613e5360a08401896139fd565b602060078960070b8287015260408960030b604088015260608785036060890152613e7e858b613aff565b9450608088860360808a0152858a518088528688019150868c01975060005b81811015613ef75788518051880b845288810151880b8985015286810151880b8785015285810151880b86850152848101511515858501528a01516001600160a01b03168a8401529787019760c090920191600101613e9d565b50909f9e505050505050505050505050505050565b60208152600061245c60208301846139fd565b634e487b7160e01b600052602160045260246000fdfea264697066735822122086358a260252798c8ac40863c5ad0f37d3e9a55cf4ce9fb75f70d11b779b7b0a64736f6c63430008170033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json new file mode 100644 index 000000000000..846eb5c704a6 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.json @@ -0,0 +1,651 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "CallResponseEvent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "CreatedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "kycGranted", + "type": "bool" + } + ], + "name": "KycGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "name": "MintedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "name": "ResponseCode", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approvePublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "associateTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "associateTokensPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "fixedFeeTokenAddress", + "type": "address" + } + ], + "name": "createFungibleTokenWithCustomFeesPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyAssociateAndTransferToAddressPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyPublic", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createNonFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "fixedFeeTokenAddress", + "type": "address" + } + ], + "name": "createNonFungibleTokenWithCustomFeesPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createNonFungibleTokenWithSECP256K1AdminKeyPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "bytes", + "name": "adminKey", + "type": "bytes" + } + ], + "name": "createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantTokenKycPublic", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenToAddressPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunctionSelector", + "type": "bytes" + } + ], + "name": "redirectForToken", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serialNumber", + "type": "uint256" + } + ], + "name": "transferFromNFT", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.HederaToken", + "name": "tokenInfo", + "type": "tuple" + } + ], + "name": "updateTokenInfoPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol new file mode 100644 index 000000000000..1884183de360 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/TestTokenCreateContract/TestTokenCreateContract.sol @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "../../HederaTokenService.sol"; +import "../../ExpiryHelper.sol"; +import "../../KeyHelper.sol"; + +contract TestTokenCreateContract is HederaTokenService, ExpiryHelper, KeyHelper { + + string name = "tokenName"; + string symbol = "tokenSymbol"; + string memo = "memo"; + int64 initialTotalSupply = 1000; + int64 maxSupply = 10000; + int32 decimals = 8; + bool freezeDefaultStatus = false; + + event ResponseCode(int responseCode); + event CreatedToken(address tokenAddress); + event MintedToken(int64 newTotalSupply, int64[] serialNumbers); + event KycGranted(bool kycGranted); + + function createFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createFungibleTokenWithSECP256K1AdminKeyPublic( + address treasury, bytes memory adminKey + ) public payable returns (address) { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + + return tokenAddress; + } + + function createFungibleTokenWithSECP256K1AdminKeyAssociateAndTransferToAddressPublic(address treasury, bytes memory adminKey, int64 amount) public payable { + address tokenAddress = this.createFungibleTokenWithSECP256K1AdminKeyPublic{value : msg.value}(treasury, adminKey); + this.associateTokenPublic(msg.sender, tokenAddress); + this.grantTokenKycPublic(tokenAddress, msg.sender); + HederaTokenService.transferToken(tokenAddress, address(this), msg.sender, amount); + } + + function createFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic( + address treasury, bytes memory adminKey + ) public payable returns (address) { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](4); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + + return tokenAddress; + } + + function createFungibleTokenWithCustomFeesPublic( + address treasury, + address fixedFeeTokenAddress + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.ADMIN, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, false, keys, expiry + ); + + IHederaTokenService.FixedFee[] memory fixedFees = new IHederaTokenService.FixedFee[](1); + fixedFees[0] = IHederaTokenService.FixedFee(1, fixedFeeTokenAddress, false, false, treasury); + + IHederaTokenService.FractionalFee[] memory fractionalFees = new IHederaTokenService.FractionalFee[](1); + fractionalFees[0] = IHederaTokenService.FractionalFee(4, 5, 10, 30, false, treasury); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleTokenWithCustomFees(token, initialTotalSupply, decimals, fixedFees, fractionalFees); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithSECP256K1AdminKeyPublic( + address treasury, bytes memory adminKey + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYCPublic( + address treasury, bytes memory adminKey + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](4); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.SECP256K1, adminKey); + keys[1] = getSingleKey(KeyType.FREEZE, KeyValueType.SECP256K1, adminKey); + keys[2] = getSingleKey(KeyType.SUPPLY, KeyValueType.SECP256K1, adminKey); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.SECP256K1, adminKey); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function createNonFungibleTokenWithCustomFeesPublic( + address treasury, + address fixedFeeTokenAddress + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + IHederaTokenService.FixedFee[] memory fixedFees = new IHederaTokenService.FixedFee[](1); + fixedFees[0] = IHederaTokenService.FixedFee(1, fixedFeeTokenAddress, false, false, treasury); + + IHederaTokenService.RoyaltyFee[] memory royaltyFees = new IHederaTokenService.RoyaltyFee[](1); + royaltyFees[0] = IHederaTokenService.RoyaltyFee(4, 5, 10, fixedFeeTokenAddress, false, treasury); + + (int responseCode, address tokenAddress) = + HederaTokenService.createNonFungibleTokenWithCustomFees(token, fixedFees, royaltyFees); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function mintTokenPublic(address token, int64 amount, bytes[] memory metadata) public + returns (int responseCode, int64 newTotalSupply, int64[] memory serialNumbers) { + (responseCode, newTotalSupply, serialNumbers) = HederaTokenService.mintToken(token, amount, metadata); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + + emit MintedToken(newTotalSupply, serialNumbers); + } + + function mintTokenToAddressPublic(address token, int64 amount, bytes[] memory metadata) public + returns (int responseCode, int64 newTotalSupply, int64[] memory serialNumbers) { + (responseCode, newTotalSupply, serialNumbers) = HederaTokenService.mintToken(token, amount, metadata); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + + emit MintedToken(newTotalSupply, serialNumbers); + + HederaTokenService.transferNFT(token, address(this), msg.sender, serialNumbers[0]); + } + + function associateTokensPublic(address account, address[] memory tokens) external returns (int256 responseCode) { + (responseCode) = HederaTokenService.associateTokens(account, tokens); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } + + function associateTokenPublic(address account, address token) public returns (int responseCode) { + responseCode = HederaTokenService.associateToken(account, token); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } + + function grantTokenKycPublic(address token, address account) external returns (int64 responseCode) { + (responseCode) = HederaTokenService.grantTokenKyc(token, account); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + } + + //Adding this function to showcase token functionality in extended updateTokenInfo test suite + function updateTokenInfoPublic(address token, IHederaTokenService.HederaToken memory tokenInfo)external returns (int responseCode) { + (responseCode) = HederaTokenService.updateTokenInfo(token, tokenInfo); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert(); + } + } + + function approvePublic(address token, address spender, uint256 amount) public returns (int responseCode) { + responseCode = HederaTokenService.approve(token, spender, amount); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + } +} From 066f6130744507b9611688719d00647aadbcd5c7 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 8 May 2024 18:29:52 +0200 Subject: [PATCH 2/5] chore: Provide executor factory by PlatformContext (#12891) Signed-off-by: Hendrik Ebbers Co-authored-by: Cody Littley <56973212+cody-littley@users.noreply.github.com> --- .../cli/signedstate/SignedStateHolder.java | 8 +-- .../src/main/java/module-info.java | 1 - .../common/concurrent/ExecutorFactory.java | 31 +++++++++++ .../common/context/PlatformContext.java | 51 +++++++++++++++++++ .../DefaultPlatformContext.java | 44 ++++++++-------- .../PlatformUncaughtExceptionHandler.java | 34 +++++++++++++ .../src/main/java/module-info.java | 1 + .../internal/DefaultPlatformContextTest.java | 4 +- .../platform/TestPlatformContextBuilder.java | 15 ++++++ .../platform/builder/PlatformBuilder.java | 4 +- .../platform/cli/CompareStatesCommand.java | 7 +-- .../swirlds/platform/cli/DiagramCommand.java | 7 +-- .../platform/cli/DiagramLegendCommand.java | 7 +-- .../cli/EventStreamRecoverCommand.java | 7 +-- .../cli/GenesisPlatformStateCommand.java | 7 +-- .../cli/ValidateAddressBookStateCommand.java | 7 +-- .../swirlds/platform/gui/GuiEventStorage.java | 7 +-- .../recovery/internal/RecoveryPlatform.java | 9 +--- .../platform/state/editor/StateEditor.java | 7 +-- .../state/editor/StateEditorSave.java | 7 +-- .../platform/wiring/PlatformWiring.java | 2 +- .../preconsensus/PcesFileManagerTests.java | 9 ++-- .../preconsensus/PcesFileReaderTests.java | 18 ++++--- .../event/preconsensus/PcesWriterTests.java | 10 ++-- 24 files changed, 194 insertions(+), 110 deletions(-) rename platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/{ => internal}/DefaultPlatformContext.java (79%) create mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java index a783c1684d1a..700ceb0f8383 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java @@ -36,14 +36,11 @@ import com.hedera.node.app.service.mono.state.virtual.VirtualBlobValue; import com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf; import com.hedera.node.app.service.mono.utils.EntityNum; -import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.context.DefaultPlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.context.PlatformContext; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.config.extensions.sources.LegacyFileConfigSource; @@ -355,8 +352,7 @@ private Pair dehydrate(@NonNull final List

+ * The instance uses the static {@link Time#getCurrent()} call to get the time. + * + * @param configuration the configuration + * @param metrics the metrics + * @param cryptography the cryptography + * @return the platform context + */ + @NonNull + static PlatformContext create( + @NonNull final Configuration configuration, + @NonNull final Metrics metrics, + @NonNull final Cryptography cryptography) { + final Time time = Time.getCurrent(); + final UncaughtExceptionHandler handler = new PlatformUncaughtExceptionHandler(); + final ExecutorFactory executorFactory = ExecutorFactory.create("platform", null, handler); + return new DefaultPlatformContext(configuration, metrics, cryptography, time, executorFactory); + } + /** * Returns the {@link Configuration} instance for the platform * @@ -72,4 +116,11 @@ public interface PlatformContext { */ @NonNull FileSystemManager getFileSystemManager(); + + /** + * Returns the {@link ExecutorFactory} for this node + * + * @return the {@link ExecutorFactory} for this node + */ + ExecutorFactory getExecutorFactory(); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java similarity index 79% rename from platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java rename to platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java index fee1fb95b327..4216c352438e 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/DefaultPlatformContext.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/DefaultPlatformContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2024 Hedera Hashgraph, LLC + * Copyright (C) 2024 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. @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.swirlds.common.context; +package com.swirlds.common.context.internal; import com.swirlds.base.time.Time; +import com.swirlds.common.concurrent.ExecutorFactory; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.io.filesystem.FileSystemManager; import com.swirlds.common.io.filesystem.FileSystemManagerFactory; @@ -35,50 +37,47 @@ public final class DefaultPlatformContext implements PlatformContext { private final Metrics metrics; private final Cryptography cryptography; private final Time time; + + private final ExecutorFactory executorFactory; + private final FileSystemManager fileSystemManager; /** * Constructor. * - * @param configuration the configuration - * @param metrics the metrics - * @param cryptography the cryptography - * @param time the time + * @param configuration the configuration + * @param metrics the metrics + * @param cryptography the cryptography + * @param time the time + * @param executorFactory the executor factory */ public DefaultPlatformContext( @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Cryptography cryptography, - @NonNull final Time time) { - + @NonNull final Time time, + @NonNull final ExecutorFactory executorFactory) { this( configuration, metrics, cryptography, time, + executorFactory, FileSystemManagerFactory.getInstance().createFileSystemManager(configuration, metrics)); } - /** - * Constructor. - * - * @param configuration the configuration - * @param metrics the metrics - * @param cryptography the cryptography - * @param time the time - * @param fileSystemManager the fileSystemManager - */ public DefaultPlatformContext( @NonNull final Configuration configuration, @NonNull final Metrics metrics, @NonNull final Cryptography cryptography, @NonNull final Time time, + @NonNull final ExecutorFactory executorFactory, @NonNull final FileSystemManager fileSystemManager) { - this.configuration = Objects.requireNonNull(configuration); this.metrics = Objects.requireNonNull(metrics); this.cryptography = Objects.requireNonNull(cryptography); this.time = Objects.requireNonNull(time); + this.executorFactory = Objects.requireNonNull(executorFactory); this.fileSystemManager = Objects.requireNonNull(fileSystemManager); } @@ -118,12 +117,15 @@ public Time getTime() { return time; } - /** - * {@inheritDoc} - */ @NonNull @Override public FileSystemManager getFileSystemManager() { return fileSystemManager; } + + @Override + @NonNull + public ExecutorFactory getExecutorFactory() { + return executorFactory; + } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java new file mode 100644 index 000000000000..a8e564da9f28 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/context/internal/PlatformUncaughtExceptionHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 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.swirlds.common.context.internal; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Simple uncaught exception handler that logs the exception and rethrows it. + */ +public class PlatformUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + private static final Logger logger = LogManager.getLogger(PlatformUncaughtExceptionHandler.class); + + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("Uncaught exception in thread: " + t.getName(), e); + throw new RuntimeException("Uncaught exception", e); + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/module-info.java b/platform-sdk/swirlds-common/src/main/java/module-info.java index dce8a05d8e72..efed38815dba 100644 --- a/platform-sdk/swirlds-common/src/main/java/module-info.java +++ b/platform-sdk/swirlds-common/src/main/java/module-info.java @@ -143,6 +143,7 @@ exports com.swirlds.common.startup; exports com.swirlds.common.threading.atomic; exports com.swirlds.common.wiring.model.diagram; + exports com.swirlds.common.concurrent; requires transitive com.swirlds.base; requires transitive com.swirlds.config.api; diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java index a225855e5904..7ba819b4387f 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; +import com.swirlds.common.concurrent.ExecutorFactory; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.metrics.PlatformMetricsProvider; @@ -47,6 +47,7 @@ void testNoNullServices() { metricsProvider.createPlatformMetrics(nodeId), CryptographyHolder.get(), Time.getCurrent(), + ExecutorFactory.create("test", new PlatformUncaughtExceptionHandler()), new TestFileSystemManager(Path.of("/tmp/test"))); // then @@ -55,5 +56,6 @@ void testNoNullServices() { assertNotNull(context.getCryptography(), "Cryptography must not be null"); assertNotNull(context.getTime(), "Time must not be null"); assertNotNull(context.getFileSystemManager(), "FileSystemManager must not be null"); + assertNotNull(context.getExecutorFactory(), "ExecutorFactory must not be null"); } } diff --git a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java index c65eefefb587..1522b3d3073d 100644 --- a/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java +++ b/platform-sdk/swirlds-common/src/testFixtures/java/com/swirlds/common/test/fixtures/platform/TestPlatformContextBuilder.java @@ -17,8 +17,10 @@ package com.swirlds.common.test.fixtures.platform; import static com.swirlds.common.io.utility.FileUtils.rethrowIO; +import static org.junit.jupiter.api.Assertions.fail; import com.swirlds.base.time.Time; +import com.swirlds.common.concurrent.ExecutorFactory; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.CryptographyHolder; @@ -33,6 +35,7 @@ import com.swirlds.metrics.api.Metrics; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.lang.Thread.UncaughtExceptionHandler; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; @@ -185,6 +188,13 @@ public PlatformContext build() { fileSystemManager = getDefaultManager(); } + final ExecutorFactory executorFactory = ExecutorFactory.create("test", new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + fail("Uncaught exception in thread " + t.getName(), e); + } + }); + return new PlatformContext() { @NonNull @Override @@ -215,6 +225,11 @@ public Time getTime() { public FileSystemManager getFileSystemManager() { return fileSystemManager; } + + @Override + public ExecutorFactory getExecutorFactory() { + return executorFactory; + } }; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index df2259698e35..bfc67298722e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -30,8 +30,6 @@ import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; import static com.swirlds.platform.util.BootstrapUtils.detectSoftwareUpgrade; -import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.CryptographyFactory; @@ -372,7 +370,7 @@ public static PlatformContext buildPlatformContext( setupGlobalMetrics(configuration); final Metrics metrics = getMetricsProvider().createPlatformMetrics(selfId); - return new DefaultPlatformContext(configuration, metrics, cryptography, Time.getCurrent()); + return PlatformContext.create(configuration, metrics, cryptography); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java index 8d44fda9003a..c18c6fa78674 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java @@ -18,15 +18,11 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.logging.legacy.LogMarker; @@ -164,8 +160,7 @@ public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration( ConfigurationBuilder.create(), getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); try (final ReservedSignedState stateA = loadAndHashState(platformContext, stateAPath)) { try (final ReservedSignedState stateB = loadAndHashState(platformContext, stateBPath)) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java index 1a8cbf5fdb5e..f87c4ca4e4be 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java @@ -16,14 +16,10 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.PlatformCli; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.wiring.model.diagram.ModelEdgeSubstitution; import com.swirlds.common.wiring.model.diagram.ModelGroup; import com.swirlds.common.wiring.model.diagram.ModelManualLink; @@ -116,8 +112,7 @@ private void setLessMystery(final boolean lessMystery) { @Override public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); final PlatformWiring platformWiring = new PlatformWiring(platformContext, true, true); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java index 04403fd1e30e..3e2c598b4c35 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramLegendCommand.java @@ -16,13 +16,9 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.common.wiring.model.diagram.ModelEdgeSubstitution; @@ -58,8 +54,7 @@ public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); final WiringModel model = WiringModelBuilder.create(platformContext).build(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java index e9b7509e673e..83de30215324 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java @@ -19,14 +19,10 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.platform.recovery.EventRecoveryWorkflow.recoverState; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.EventStreamCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; @@ -128,8 +124,7 @@ private void setLoadSigningKeys(final boolean loadSigningKeys) { public Integer call() throws Exception { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration( ConfigurationBuilder.create(), getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); recoverState( platformContext, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index dc8113504929..2171191061d7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -19,15 +19,11 @@ import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; @@ -72,8 +68,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); System.out.printf("Reading from %s %n", statePath.toAbsolutePath()); final DeserializedSignedState deserializedSignedState = diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java index c3e9e6e68a53..6e211ddb0a56 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java @@ -16,14 +16,10 @@ package com.swirlds.platform.cli; -import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; @@ -72,8 +68,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); System.out.printf("Reading state from %s %n", statePath.toAbsolutePath()); final DeserializedSignedState deserializedSignedState = diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java index 38be1dede790..3002013be6ac 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gui/GuiEventStorage.java @@ -19,11 +19,7 @@ import static com.swirlds.platform.event.AncientMode.GENERATION_THRESHOLD; import static com.swirlds.platform.system.events.EventConstants.FIRST_GENERATION; -import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.platform.Consensus; import com.swirlds.platform.ConsensusImpl; @@ -61,8 +57,7 @@ public class GuiEventStorage { public GuiEventStorage(@NonNull final Configuration configuration, @NonNull final AddressBook addressBook) { this.configuration = Objects.requireNonNull(configuration); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); this.consensus = new ConsensusImpl(platformContext, new NoOpConsensusMetrics(), addressBook); // Future work: birth round compatibility for GUI diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java index 4832c640417d..d7fc22c322e0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java @@ -19,18 +19,13 @@ import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; -import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.crypto.Signature; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.config.api.Configuration; -import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.crypto.PlatformSigner; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -84,11 +79,9 @@ public RecoveryPlatform( keysAndCerts = null; } - final Metrics metrics = new NoOpMetrics(); - notificationEngine = NotificationEngine.buildEngine(getStaticThreadManager()); - context = new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); + context = PlatformContext.create(configuration); setLatestState(initialState); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java index 7e5407e91c87..fb710fae25af 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java @@ -19,18 +19,14 @@ import static com.swirlds.platform.state.editor.StateEditorUtils.formatNodeType; import static com.swirlds.platform.state.editor.StateEditorUtils.formatRoute; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.CommandBuilder; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.merkle.route.MerkleRoute; import com.swirlds.common.merkle.route.MerkleRouteFactory; import com.swirlds.common.merkle.route.MerkleRouteUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; @@ -67,8 +63,7 @@ public StateEditor(final Path statePath) throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + platformContext = PlatformContext.create(configuration); final DeserializedSignedState deserializedSignedState = SignedStateFileReader.readStateFile(platformContext, statePath); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java index e4c167ee3019..82fe2e1523a7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java @@ -21,13 +21,9 @@ import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; -import com.swirlds.base.time.Time; import com.swirlds.cli.utility.SubcommandOf; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.logging.legacy.LogMarker; @@ -77,8 +73,7 @@ public void run() { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(ConfigurationBuilder.create()); - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); + final PlatformContext platformContext = PlatformContext.create(configuration); try (final ReservedSignedState signedState = getStateEditor().getSignedStateCopy()) { writeSignedStateFilesToDirectory(platformContext, NO_NODE_ID, directory, signedState.get()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index 77df5b5a1784..3f3ad7f07e62 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -192,7 +192,7 @@ public PlatformWiring( final int coreCount = Runtime.getRuntime().availableProcessors(); final int parallelism = (int) Math.max(1, config.defaultPoolMultiplier() * coreCount + config.defaultPoolConstant()); - final ForkJoinPool defaultPool = new ForkJoinPool(parallelism); + final ForkJoinPool defaultPool = platformContext.getExecutorFactory().createForkJoinPool(parallelism); logger.info(STARTUP.getMarker(), "Default platform pool parallelism: {}", parallelism); model = WiringModelBuilder.create(platformContext) diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java index 8283cb841083..396cb8636c2f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileManagerTests.java @@ -27,13 +27,11 @@ import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.config.FileSystemManagerConfig_; import com.swirlds.common.io.utility.FileUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.common.utility.CompareTo; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; @@ -101,7 +99,10 @@ private PlatformContext buildContext(@NonNull final AncientMode ancientMode, @No .withValue(PcesConfig_.COMPACT_LAST_FILE_ON_STARTUP, false) .getOrCreateConfig(); - return new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get(), time); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withTime(time) + .build(); } @ParameterizedTest diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java index 3dcf98416ef3..93cfe05e07d1 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesFileReaderTests.java @@ -28,9 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.base.time.Time; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.io.utility.FileUtils; import com.swirlds.common.io.utility.RecycleBin; @@ -38,6 +36,7 @@ import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.test.fixtures.TestFileSystemManager; import com.swirlds.common.test.fixtures.TestRecycleBin; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.metrics.api.Metrics; @@ -119,8 +118,11 @@ private PlatformContext buildContext(final boolean permitGaps, @NonNull final An final TestFileSystemManager fileSystemManager = new TestFileSystemManager(testDirectory); fileSystemManager.setBin(TestRecycleBin.getInstance()); - return new DefaultPlatformContext( - configuration, metrics, CryptographyHolder.get(), Time.getCurrent(), fileSystemManager); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withMetrics(metrics) + .withFileSystemManager((m, t) -> fileSystemManager) + .build(); } private PlatformContext buildContext( @@ -139,8 +141,12 @@ private PlatformContext buildContext( final TestFileSystemManager fileSystemManager = new TestFileSystemManager(testDirectory); fileSystemManager.setBin(recycleBinProvider.apply(metrics, Time.getCurrent())); - return new DefaultPlatformContext( - configuration, metrics, CryptographyHolder.get(), Time.getCurrent(), fileSystemManager); + + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withMetrics(metrics) + .withFileSystemManager((m, t) -> fileSystemManager) + .build(); } protected static Stream buildArguments() { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java index 0c652f987ec5..721bbfb7aa72 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java @@ -32,17 +32,15 @@ import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.IOIterator; import com.swirlds.common.io.config.FileSystemManagerConfig_; import com.swirlds.common.io.utility.FileUtils; -import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.test.fixtures.TransactionGenerator; import com.swirlds.common.test.fixtures.io.FileManipulation; +import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.TransactionConfig_; @@ -272,8 +270,10 @@ private PlatformContext buildContext(@NonNull final AncientMode ancientMode) { .withValue(EventConfig_.USE_BIRTH_ROUND_ANCIENT_THRESHOLD, ancientMode == BIRTH_ROUND_THRESHOLD) .getOrCreateConfig(); - return new DefaultPlatformContext( - configuration, new NoOpMetrics(), CryptographyHolder.get(), new FakeTime(Duration.ofMillis(1))); + return TestPlatformContextBuilder.create() + .withConfiguration(configuration) + .withTime(new FakeTime(Duration.ofMillis(1))) + .build(); } /** From 95ca59c34d2c647e80d311dada12bc3ebea17967 Mon Sep 17 00:00:00 2001 From: Miroslav Gatsanoga Date: Wed, 8 May 2024 19:31:58 +0300 Subject: [PATCH 3/5] docs: add design document for HIP-904 token claim airdrop transaction (#12838) Signed-off-by: Miroslav Gatsanoga --- .../airdrops/token-claim-airdrop.md | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md diff --git a/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md b/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md new file mode 100644 index 000000000000..80d7f3b3e45a --- /dev/null +++ b/hedera-node/docs/design/services/token-service/airdrops/token-claim-airdrop.md @@ -0,0 +1,165 @@ +# Introduce TokenClaimAirdrop transaction +## Purpose + +We need to add a new functionality that would make it possible for an airdrop receiver to accept a pending airdrop transfer. This would be the only way for a receiver, which hasn't been associated to a given token to accept an airdropped token that has been in pending airdrops state. + +## Goals + +1. Define new `TokenClaimAirdrop` HAPI transaction +2. Implement token claim airdrop transaction handler logic + +## Non-Goals + +- Implement token claim airdrop in system contract functions + +## Architecture + +The implementation related to the new `TokenClaimAirdrop` transaction will be gated behind a `tokens.airdrops.claim.enabled` feature flag. + +### HAPI updates + +Create new transaction type as defined in the HIP: + +```protobuf +/** + * Token claim airdrop
+ * Complete one or more pending transfers on behalf of the recipient(s) for each airdrop.
+ * The sender MUST have sufficient balance to fulfill the airdrop at the time of claim. If the + * sender does not have sufficient balance, the claim SHALL fail. + * + * Each pending airdrop successfully claimed SHALL be removed from state and SHALL NOT be available + * to claim again. + * + * Each claim SHALL be represented in the transaction body and SHALL NOT be restated + * in the record file.
+ * All claims MUST succeed for this transaction to succeed. + */ +message TokenClaimAirdropTransactionBody { + /** + * A list of one or more pending airdrop identifiers.
+ * This transaction MUST be signed by the account referenced in the `receiver_id` for + * each entry in this list. + *

+ * This list MUST contain between 1 and 10 entries, inclusive.
+ * This list MUST NOT have any duplicate entries. + */ + repeated PendingAirdropId pending_airdrops = 1; +} + +/** + * A unique, composite, identifier for a pending airdrop. + * + * Each pending airdrop SHALL be uniquely identified by a PendingAirdropId. + * A PendingAirdropId SHALL be recorded when created and MUST be provided in any transaction + * that would modify that pending airdrop (such as a `claimAirdrop` or `cancelAirdrop`). + */ +message PendingAirdropId { + /** + * A sending account.
+ * This is the account that initiated, and SHALL fund, this pending airdrop.
+ * This field is REQUIRED. + */ + AccountID sender_id = 1; + + /** + * A receiving account.
+ * This is the ID of the account that SHALL receive the airdrop.
+ * This field is REQUIRED. + */ + AccountID receiver_id = 2; + + oneof token_reference { + /** + * A token ID.
+ * This is the type of token for a fungible/common token airdrop.
+ * This field is REQUIRED for a fungible/common token and MUST NOT be used for a + * non-fungible/unique token. + */ + TokenID fungible_token_type = 3; + + /** + * The id of a single NFT, consisting of a Token ID and serial number.
+ * This is the type of token for a non-fungible/unique token airdrop.
+ * This field is REQUIRED for a non-fungible/unique token and MUST NOT be used for a + * fungible/common token. + */ + NftID non_fungible_token = 4; + } +} +``` + +Add new RPC to `TokenService` : + +```protobuf +service TokenService { + +// ... + + /** + * Claim one or more pending airdrops.
+ * This transaction MUST be signed by _each_ account *receiving* an airdrop to be claimed.
+ * If a Sender lacks sufficient balance to fulfill the airdrop at the time the claim is made, + * that claim SHALL fail. + */ + rpc claimAirdrop (Transaction) returns (TransactionResponse); +} +``` + +### Fees + +The basic `TokenClaimAirdrop` fee should be proportional to the number of airdrops being claimed in the transaction. + +An update into the `feeSchedule` file would be needed to specify that. + +### Services updates + +- Update `TokenServiceDefinition` class to include the new RPC method definition for claiming airdrops +- Implement new `TokenClaimAirdropHandler` class which should be invoked when the gRPC server handles `TokenClaimAirdrop` transactions. The class should be responsible for: + - Pure checks: validation logic based only on the transaction body itself in order to verify if the transaction is valid one + - Verify that the pending airdrops list contains between 1 and 10 entries, inclusive + - Verify that the pending airdrops list does not have any duplicate entries + - Pre-handle: + - The transaction must be signed by the account referenced by a `receiver_id` for each entry in the pending airdrops list + - Confirm that for the given pending airdrops ids in the transaction there are corresponding pending transfers existing in state + - Check if the sender has sufficient balance to fulfill the airdrop + - Handle: + - Any additional validation depending on config or state i.e. semantics checks + - The business logic for claiming pending airdrops + - We need to create a token association between each `receiver_id` and `token_reference`, future rents for token association slot should be paid by `receiver_id` + - Since we would have the signature of the receiver, even if it's an account with `receiver_sig_required=true`, the claim would implicitly work properly + - Then we should transfer the claimed tokens to each `receiver_id` + - We can dispatch synthetic crypto transfer for this, but we must skip the assessment of custom fees + - In case of a fungible token claim + - Create a synthetic `CryptoTransfer` with the corresponding `sender`, `receiver`, `token` and `amount` based on the `PendingAirdropId` and the corresponding `PendingAirdropValue` + - Then delegate it to the `CryptoTransfer.handler()` to execute the transfer + - In case of an NFT claim + - Create a synthetic `CryptoTransfer` with the corresponding `sender`, `receiver`, `token` and `serial number` based on the based on the `PendingAirdropId` + - Then delegate it to `CryptoTransfer.handler()` to execute the transfer + - Token transfers and associations should be externalized using the `tokenTransferLists` and `automatic_token_associations` fields in the transaction record + - Fees calculation + +### Zero-Balance accounts + +An account with no open auto-association slots can receive airdrops but must send a `TokenClaimAirdrop` transaction, which requires a payer. If the account has zero hbars, then it can still claim the transfer if someone else is willing to pay for that transaction. For example, a Dapp could be the payer on the transaction. Both the Dapp and the account must sign the transaction. + +### Hollow accounts + +Any existing hollow accounts that were created before [HIP-904](https://hips.hedera.com/hip/hip-904) will have no or limited number of `maxAutoAssociations` depending on if they were created with HBAR or token transfer respectively. That means an airdrop of unassociated tokens to such accounts will result in a pending transfer. +Performing `TokenClaimAirdrop` for such hollow account will also complete the account by setting its key which will obtained from the required transaction signature. Completing the hollow account should not modify the `maxAutoAssociations` on the account. That should always be an explicit step by a user. + +## Acceptance Tests + +All of the expected behaviour described below should be present only if the new `TokenClaimAirdrop` feature flag is enabled. + +- Given existing pending airdrop in state when valid `TokenClaimAirdrop` transaction containing entry for the same pending airdrop is performed then the `TokenClaimAirdrop` should succeed resulting in: + - the tokens being claimed should be automatically associated with the `receiver_id` account + - the tokens being claimed should be transferred to the `receiver_id` account + - the pending airdrop should be removed from state +- Given a successful `TokenClaimAirdrop` transaction having a hollow account as `receiver_id` should also complete the account without modifying its `maxAutoAssociations` value +- Given successful `TokenClaimAirdrop` when another `TokenClaimAirdrop` for the same airdrop is performed then the second `TokenClaimAirdrop` should fail +- `TokenClaimAirdrop` transaction with no pending airdrops entries should fail +- `TokenClaimAirdrop` transaction with more than 10 pending airdrops entries should fail +- `TokenClaimAirdrop` transaction containing duplicate entries should fail +- `TokenClaimAirdrop` transaction containing pending airdrops entries which do not exist in state should fail +- `TokenClaimAirdrop` transaction not signed by the account referenced by a `receiver_id` for each entry in the pending airdrops list should fail +- `TokenClaimAirdrop` transaction with a `sender_id` account that does not have sufficient balance of the claimed token should fail From 4f2452d9d227df4ccecce3296dff54089a8c7d83 Mon Sep 17 00:00:00 2001 From: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> Date: Wed, 8 May 2024 20:32:25 +0100 Subject: [PATCH 4/5] chore: Increased test coverage for NetworkingStakingTranslator (#13116) Signed-off-by: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> --- .../codec/NetworkingStakingTranslator.java | 3 +- .../NetworkingStakingTranslatorTest.java | 53 ++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java index f74d4ebcf20d..7f009b6aeb59 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/codec/NetworkingStakingTranslator.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.token.NetworkStakingRewards; +import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import edu.umd.cs.findbugs.annotations.NonNull; public final class NetworkingStakingTranslator { @@ -34,7 +35,7 @@ private NetworkingStakingTranslator() { * @return the {@link NetworkStakingRewards} converted from the {@link com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext} */ public static NetworkStakingRewards networkStakingRewardsFromMerkleNetworkContext( - @NonNull final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext) { + @NonNull final MerkleNetworkContext merkleNetworkContext) { requireNonNull(merkleNetworkContext); return NetworkStakingRewards.newBuilder() .stakingRewardsActivated(merkleNetworkContext.areRewardsActivated()) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java index a7984b70ab40..ca5081ea5076 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/codec/NetworkingStakingTranslatorTest.java @@ -17,14 +17,20 @@ package com.hedera.node.app.service.token.impl.test.codec; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_NETWORK_REWARDS_KEY; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.state.token.NetworkStakingRewards; -import com.hedera.node.app.service.token.impl.ReadableNetworkStakingRewardsStoreImpl; +import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; +import com.hedera.node.app.service.mono.state.submerkle.ExchangeRates; +import com.hedera.node.app.service.mono.state.submerkle.SequenceNumber; import com.hedera.node.app.service.token.impl.codec.NetworkingStakingTranslator; import com.hedera.node.app.spi.state.ReadableSingletonState; import com.hedera.node.app.spi.state.ReadableStates; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -40,19 +46,51 @@ class NetworkingStakingTranslatorTest { @Mock(strictness = Mock.Strictness.LENIENT) private ReadableSingletonState stakingRewardsState; - private ReadableNetworkStakingRewardsStoreImpl subject; + @Test + void testCoverageForPrivateConstructor() + throws NoSuchMethodException, InstantiationException, IllegalAccessException { + final Constructor constructor = + NetworkingStakingTranslator.class.getDeclaredConstructor(); + constructor.setAccessible(true); + try { + constructor.newInstance(); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + assertEquals(UnsupportedOperationException.class, cause.getClass()); + } + } @BeforeEach void setUp() { given(states.getSingleton(STAKING_NETWORK_REWARDS_KEY)).willReturn(stakingRewardsState); given(stakingRewardsState.get()).willReturn(new NetworkStakingRewards(true, 1L, 2L, 3L)); - subject = new ReadableNetworkStakingRewardsStoreImpl(states); + } + + @Test + void testNetworkStakingRewardsFromMerkleNetworkContextWithNullInput() { + assertThrows(NullPointerException.class, () -> { + NetworkingStakingTranslator.networkStakingRewardsFromMerkleNetworkContext(null); + }); + } + + @Test + void testNetworkStakingRewardsFromMerkleNetworkContextWithInvalidInput() { + MerkleNetworkContext merkleNetworkContextInvalid = new MerkleNetworkContext(); + merkleNetworkContextInvalid.setStakingRewardsActivated(true); + merkleNetworkContextInvalid.setTotalStakedRewardStart(-1L); + merkleNetworkContextInvalid.setPendingRewards(100_000_000_001L); + merkleNetworkContextInvalid.setTotalStakedStart(-5L); + merkleNetworkContextInvalid.setSeqNo(new SequenceNumber(1001)); + merkleNetworkContextInvalid.setMidnightRates(new ExchangeRates()); + + assertDoesNotThrow(() -> { + NetworkingStakingTranslator.networkStakingRewardsFromMerkleNetworkContext(merkleNetworkContextInvalid); + }); } @Test void createNetworkStakingRewardsFromMerkleNetworkContext() { - final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext = - new com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext(); + final MerkleNetworkContext merkleNetworkContext = new MerkleNetworkContext(); merkleNetworkContext.setStakingRewardsActivated(true); merkleNetworkContext.setTotalStakedRewardStart(1L); @@ -65,9 +103,8 @@ void createNetworkStakingRewardsFromMerkleNetworkContext() { assertEquals(getExpectedNetworkStakingRewards(), convertedNetworkStakingRewards); } - private com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext getExpectedMerkleNetworkContext() { - final com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext merkleNetworkContext = - new com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext(); + private MerkleNetworkContext getExpectedMerkleNetworkContext() { + final MerkleNetworkContext merkleNetworkContext = new MerkleNetworkContext(); merkleNetworkContext.setStakingRewardsActivated(true); merkleNetworkContext.setTotalStakedRewardStart(1L); From 0ab8f3d9e2c53d9e154362b667476f591b121b3e Mon Sep 17 00:00:00 2001 From: anthony-swirldslabs <152534762+anthony-swirldslabs@users.noreply.github.com> Date: Wed, 8 May 2024 17:06:09 -0700 Subject: [PATCH 5/5] feat: 11425: level by level reconnect (#11755) Signed-off-by: Anthony Petrov --- .../internal/ConcurrentNodeStatusTracker.java | 26 ++++- .../reconnect/TeacherPushVirtualTreeView.java | 105 +++++++++++++++++- 2 files changed, 124 insertions(+), 7 deletions(-) diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java index 1b9152834585..21bc2111d034 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/ConcurrentNodeStatusTracker.java @@ -179,7 +179,7 @@ public void set(final long value, final Status status) { * * @param value * path of node to check - * @return status of aa node + * @return status of a node */ public Status getStatus(long value) { if (value < 0 || value >= capacity) { @@ -200,6 +200,30 @@ public Status getStatus(long value) { return status; } + /** + * Get the status of a node as reported by the learner, or return UNKNOWN. + *

+ * Unlike the getStatus(long value) method above, this method returns the actual + * status of the requested node without traversing the tree to its parents. + * If the learner hasn't reported a status for this particular node, this method + * returns UNKNOWN. + * + * @param value path of node to check + * @return status of the node, or UNKNOWN if its status has never been reported yet + */ + public Status getReportedStatus(long value) { + if (value < 0 || value >= capacity) { + throw new IllegalArgumentException( + String.format("Value can only be between [0, %d), %d is illegal", capacity, value)); + } + + final int index = getIndexInBitSetFor(value); + final long bitSetIndex = getBitSetIndexFor(value); + return statusBitSets + .computeIfAbsent(bitSetIndex, k -> new BitSetGroup()) + .getStatus(index); + } + /** * Atomically sets the status of a node represented by its value (path). * Currently, for the immediate use case, we are blocking for each diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java index 233423aad662..bc491c0fd6fa 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/reconnect/TeacherPushVirtualTreeView.java @@ -72,11 +72,57 @@ public final class TeacherPushVirtualTreeView + * It's volatile to avoid extra locks when accessing the value. It's totally + * okay if the value is updated after it's read. The worst that could happen + * is that we'll start sending the next level of nodes before receiving + * a response for the last node at the current level, but this is unlikely to happen + * because the code processing the queue is single-threaded, + * and it doesn't hurt from the correctness perspective even if it happens. + */ + private volatile Long lastNodeAwaitingReporting = null; + /** - * A queue of the nodes (by path) that we are about to handle. Note that ConcurrentBitSetQueue - * cleans up after itself in "chunks", such that we don't end up consuming a ton of memory. + * A node status may become implicitly reported by a response about its ancestors, + * in which case we can retest the condition and go ahead and send the next level + * without waiting for this particular node to report its status since it's already known/inferred. + * So we use a timeout to recheck the condition periodically. */ - private final ConcurrentBitSetQueue handleQueue = new ConcurrentBitSetQueue(); + private static final long AWAIT_FOR_REPORT_TIMEOUT_MILLIS = 1; + + /** + * The maximum total time for the `while (!hasReported) { wait() }` loop. + * This is used to protect the teacher from a dying learner. + */ + private static final long MAX_TOTAL_AWAIT_FOR_REPORT_TIMEOUT_MILLIS = 1000; /** * A queue of the nodes (by path) that we expect responses for. @@ -179,7 +225,7 @@ public Long getRoot() { public void addToHandleQueue(final Long node) { processed.incrementAndGet(); checkValidNode(node, reconnectState); - handleQueue.add(node); + accumulatingHandleQueue.add(node); } /** @@ -187,7 +233,38 @@ public void addToHandleQueue(final Long node) { */ @Override public Long getNextNodeToHandle() { - return handleQueue.remove(); + if (processingHandleQueue.isEmpty()) { + // We've just sent an entire level of the tree, and before we resume sending the next level + // which has been accumulated in the current accumulatingHandleQueue, we'll wait + // until the learner has reported the status of the lastNodeAwaitingReporting. + // Note that in case we're just starting, there hasn't been any nodes sent yet, + // so we have to flip w/o waiting in that case (when it's null.) + if (lastNodeAwaitingReporting != null) { + try { + synchronized (lastNodeAwaitingReporting) { + final long waitStartMillis = System.currentTimeMillis(); + while (!hasLearnerReportedFor(lastNodeAwaitingReporting) + && System.currentTimeMillis() - waitStartMillis + < MAX_TOTAL_AWAIT_FOR_REPORT_TIMEOUT_MILLIS) { + lastNodeAwaitingReporting.wait(AWAIT_FOR_REPORT_TIMEOUT_MILLIS); + } + } + } catch (InterruptedException ignore) { + // We can ignore this. In the worst case, we'll just go ahead + // and send the next level w/o awaiting a report from the learner. + } + } + // Exchange the accumulating queue with the processing queue: + flipQueues(); + // Note that we know the other queue isn't empty because this method has been called after + // the caller checked areThereNodesToHandle() which tests both the queues. + } + final long node = processingHandleQueue.remove(); + // Avoid waiting on a node which status has already been reported/inferred: + if (!hasLearnerReportedFor(node)) { + lastNodeAwaitingReporting = node; + } + return node; } /** @@ -195,7 +272,7 @@ public Long getNextNodeToHandle() { */ @Override public boolean areThereNodesToHandle() { - return !handleQueue.isEmpty(); + return !processingHandleQueue.isEmpty() || !accumulatingHandleQueue.isEmpty(); } /** @@ -233,6 +310,11 @@ public void registerResponseForNode(final Long node, final boolean learnerHasNod ? ConcurrentNodeStatusTracker.Status.KNOWN : ConcurrentNodeStatusTracker.Status.NOT_KNOWN; nodeStatusTracker.set(node, status); + if (node == lastNodeAwaitingReporting) { + synchronized (node) { + node.notifyAll(); + } + } } /** @@ -243,6 +325,17 @@ public boolean hasLearnerConfirmedFor(final Long node) { return nodeStatusTracker.getStatus(node) == ConcurrentNodeStatusTracker.Status.KNOWN; } + /** + * Determines if the status of the given node has been reported either directly, + * or indirectly by reporting the status of an ancestor of the node. + * + * @param node a node + * @return true if the status has been reported by the learner + */ + private boolean hasLearnerReportedFor(final Long node) { + return nodeStatusTracker.getReportedStatus(node) != ConcurrentNodeStatusTracker.Status.UNKNOWN; + } + /** * {@inheritDoc} */