From edbbfe2b53c6aa59ff7b6581566d17d8a40d27e1 Mon Sep 17 00:00:00 2001 From: gsstoykov <146725010+gsstoykov@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:33:42 +0300 Subject: [PATCH 1/3] 546: Project examples/ runs resulting in errors Signed-off-by: gsstoykov --- .gitignore | 6 +----- sdk/main/src/impl/Network.cc | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 64ea04fe9..148e0ef11 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,7 @@ *.bak ### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +.vscode/ *.code-workspace # Local History for Visual Studio Code diff --git a/sdk/main/src/impl/Network.cc b/sdk/main/src/impl/Network.cc index 73bebe27a..f090ea69b 100644 --- a/sdk/main/src/impl/Network.cc +++ b/sdk/main/src/impl/Network.cc @@ -27,6 +27,7 @@ #include #include #include +#include namespace Hedera::internal { @@ -189,7 +190,8 @@ NodeAddressBook Network::getAddressBookForLedgerId(const LedgerId& ledgerId) return {}; } - std::ifstream infile(ledgerId.toString() + ".pb", std::ios_base::binary); + std::string buildPath = std::filesystem::current_path().string() + "/addressbook/" + ledgerId.toString()+".pb"; + std::ifstream infile(buildPath, std::ios_base::binary); return NodeAddressBook::fromBytes({ std::istreambuf_iterator(infile), std::istreambuf_iterator() }); } From 899fa1e2b0a37855ea248813d9865fff38fb7440 Mon Sep 17 00:00:00 2001 From: Rob Walworth <110835868+rwalworth@users.noreply.github.com> Date: Fri, 20 Oct 2023 08:28:15 -0500 Subject: [PATCH 2/3] 543: Add `PrngExample` Signed-off-by: Rob Walworth Co-authored-by: gsstoykov <146725010+gsstoykov@users.noreply.github.com> --- sdk/examples/CMakeLists.txt | 4 + sdk/examples/PrngExample.cc | 48 ++++++ sdk/main/CMakeLists.txt | 1 + sdk/main/include/PrngTransaction.h | 141 ++++++++++++++++++ sdk/main/include/TransactionRecord.h | 11 ++ sdk/main/include/TransactionType.h | 1 + sdk/main/include/WrappedTransaction.h | 2 + sdk/main/include/impl/Node.h | 6 + sdk/main/src/Executable.cc | 2 + sdk/main/src/PrngTransaction.cc | 96 ++++++++++++ sdk/main/src/Transaction.cc | 4 + sdk/main/src/TransactionRecord.cc | 9 ++ sdk/main/src/WrappedTransaction.cc | 19 +++ sdk/main/src/impl/Node.cc | 18 ++- sdk/tests/integration/CMakeLists.txt | 1 + .../PrngTransactionIntegrationTests.cc | 66 ++++++++ sdk/tests/unit/CMakeLists.txt | 1 + sdk/tests/unit/PrngTransactionUnitTests.cc | 77 ++++++++++ sdk/tests/unit/TransactionTest.cc | 117 +++++++++++---- 19 files changed, 591 insertions(+), 33 deletions(-) create mode 100644 sdk/examples/PrngExample.cc create mode 100644 sdk/main/include/PrngTransaction.h create mode 100644 sdk/main/src/PrngTransaction.cc create mode 100644 sdk/tests/integration/PrngTransactionIntegrationTests.cc create mode 100644 sdk/tests/unit/PrngTransactionUnitTests.cc diff --git a/sdk/examples/CMakeLists.txt b/sdk/examples/CMakeLists.txt index d3dbd2d90..f35c7e890 100644 --- a/sdk/examples/CMakeLists.txt +++ b/sdk/examples/CMakeLists.txt @@ -28,6 +28,7 @@ set(GET_EXCHANGE_RATES_EXAMPLE_NAME ${PROJECT_NAME}-get-exchange-rates-example) set(GET_FILE_CONTENTS_EXAMPLE_NAME ${PROJECT_NAME}-get-file-contents-example) set(MULTI_APP_TRANSFER_EXAMPLE_NAME ${PROJECT_NAME}-multi-app-transfer-example) set(NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME ${PROJECT_NAME}-nft-add-remove-allowances-example) +set(PRNG_EXAMPLE_NAME ${PROJECT_NAME}-prng-example) set(SCHEDULE_EXAMPLE_NAME ${PROJECT_NAME}-schedule-example) set(SCHEDULE_IDENTICAL_TRANSACTION_EXAMPLE_NAME ${PROJECT_NAME}-schedule-identical-transaction-example) set(SCHEDULE_MULTI_SIG_TRANSACTION_EXAMPLE_NAME ${PROJECT_NAME}-schedule-multisig-transaction-example) @@ -69,6 +70,7 @@ add_executable(${GET_EXCHANGE_RATES_EXAMPLE_NAME} GetExchangeRatesExample.cc) add_executable(${GET_FILE_CONTENTS_EXAMPLE_NAME} GetFileContentsExample.cc) add_executable(${MULTI_APP_TRANSFER_EXAMPLE_NAME} MultiAppTransferExample.cc) add_executable(${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} NftAddRemoveAllowancesExample.cc) +add_executable(${PRNG_EXAMPLE_NAME} PrngExample.cc) add_executable(${SCHEDULE_EXAMPLE_NAME} ScheduleExample.cc) add_executable(${SCHEDULE_IDENTICAL_TRANSACTION_EXAMPLE_NAME} ScheduleIdenticalTransactionExample.cc) add_executable(${SCHEDULE_MULTI_SIG_TRANSACTION_EXAMPLE_NAME} ScheduleMultiSigTransactionExample.cc) @@ -130,6 +132,7 @@ target_link_libraries(${GET_EXCHANGE_RATES_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${GET_FILE_CONTENTS_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${MULTI_APP_TRANSFER_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) +target_link_libraries(${PRNG_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${SCHEDULE_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${SCHEDULE_IDENTICAL_TRANSACTION_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${SCHEDULE_MULTI_SIG_TRANSACTION_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) @@ -174,6 +177,7 @@ install(TARGETS ${GET_FILE_CONTENTS_EXAMPLE_NAME} ${MULTI_APP_TRANSFER_EXAMPLE_NAME} ${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} + ${PRNG_EXAMPLE_NAME} ${SCHEDULE_EXAMPLE_NAME} ${SCHEDULE_IDENTICAL_TRANSACTION_EXAMPLE_NAME} ${SCHEDULE_MULTI_SIG_TRANSACTION_EXAMPLE_NAME} diff --git a/sdk/examples/PrngExample.cc b/sdk/examples/PrngExample.cc new file mode 100644 index 000000000..71f511af7 --- /dev/null +++ b/sdk/examples/PrngExample.cc @@ -0,0 +1,48 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 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. + * + */ +#include "Client.h" +#include "ED25519PrivateKey.h" +#include "PrngTransaction.h" +#include "TransactionRecord.h" +#include "TransactionResponse.h" + +#include + +using namespace Hedera; + +int main(int argc, char** argv) +{ + if (argc < 3) + { + std::cout << "Please input account ID and private key" << std::endl; + return 1; + } + + // Get a client for the Hedera testnet, and set the operator account ID and key such that all generated transactions + // will be paid for by this account and be signed by this key. + Client client = Client::forTestnet(); + client.setOperator(AccountId::fromString(argv[1]), ED25519PrivateKey::fromString(argv[2])); + + // Get a random number between 0 and 100. + const TransactionRecord txRecord = PrngTransaction().setRange(100).execute(client).getRecord(client); + std::cout << "Randomly generated number: " << txRecord.mPrngNumber.value() << std::endl; + + return 0; +} diff --git a/sdk/main/CMakeLists.txt b/sdk/main/CMakeLists.txt index 4161b40f5..a51b327ea 100644 --- a/sdk/main/CMakeLists.txt +++ b/sdk/main/CMakeLists.txt @@ -75,6 +75,7 @@ add_library(${PROJECT_NAME} STATIC src/NodeAddress.cc src/NodeAddressBook.cc src/PrivateKey.cc + src/PrngTransaction.cc src/ProxyStaker.cc src/PublicKey.cc src/Query.cc diff --git a/sdk/main/include/PrngTransaction.h b/sdk/main/include/PrngTransaction.h new file mode 100644 index 000000000..520f4027b --- /dev/null +++ b/sdk/main/include/PrngTransaction.h @@ -0,0 +1,141 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 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. + * + */ +#ifndef HEDERA_SDK_CPP_PRNG_TRANSACTION_H_ +#define HEDERA_SDK_CPP_PRNG_TRANSACTION_H_ + +#include "Transaction.h" + +namespace proto +{ +class UtilPrngTransactionBody; +class TransactionBody; +} + +namespace Hedera +{ +/** + * A transaction that generates a pseudorandom number. When the pseudorandom number generate transaction executes, its + * transaction record will contain the 384-bit array of pseudorandom bytes. The transaction has an optional range + * parameter. If the parameter is given and is positive, then the record will contain a 32-bit pseudorandom integer r, + * where 0 <= r < range instead of containing the 384 pseudorandom bits. + * + * When the nth transaction needs a pseudorandom number, it is given the running hash of all records up to and including + * the record for transaction n-3. If it needs 384 bits, then it uses the entire hash. If it needs 256 bits, it uses the + * first 256 bits of the hash. If it needs a random number r that is in the range 0 <= r < range, then it lets x be the + * first 32 bits of the hash (interpreted as a signed integer). + * + * The choice of using the hash up to transaction n-3 rather than n-1 is to ensure the transactions can be processed + * quickly. Because the thread calculating the hash will have more time to complete it before it is needed. The use of + * n-3 rather than n-1000000 is to make it hard to predict the pseudorandom number in advance. + */ +class PrngTransaction : public Transaction +{ +public: + PrngTransaction() = default; + + /** + * Construct from a TransactionBody protobuf object. + * + * @param transactionBody The TransactionBody protobuf object from which to construct. + * @throws std::invalid_argument If the input TransactionBody does not represent a UtilPrng. + */ + explicit PrngTransaction(const proto::TransactionBody& transactionBody); + + /** + * Construct from a map of TransactionIds to node account IDs and their respective Transaction protobuf objects. + * + * @param transactions The map of TransactionIds to node account IDs and their respective Transaction protobuf + * objects. + */ + explicit PrngTransaction(const std::map>& transactions); + + /** + * Set the range of the pseudorandom number to generate. + * + * @param range The range of the pseudorandom number to generate. + * @return A reference to this PrngTransaction object with the newly-set range. + * @throws IllegalStateException If this PrngTransaction is frozen. + */ + PrngTransaction& setRange(int range); + + /** + * Get the range of the pseudorandom number to generate. + * + * @return The the range of the pseudorandom number to generate. + */ + [[nodiscard]] inline int getRange() const { return mRange; } + +private: + friend class WrappedTransaction; + + /** + * Derived from Executable. Submit a Transaction protobuf object which contains this PrngTransaction's data to a Node. + * + * @param request The Transaction protobuf object to submit. + * @param node The Node to which to submit the request. + * @param deadline The deadline for submitting the request. + * @param response Pointer to the ProtoResponseType object that gRPC should populate with the response information + * from the gRPC server. + * @return The gRPC status of the submission. + */ + [[nodiscard]] grpc::Status submitRequest(const proto::Transaction& request, + const std::shared_ptr& node, + const std::chrono::system_clock::time_point& deadline, + proto::TransactionResponse* response) const override; + + /** + * Derived from Transaction. Verify that all the checksums in this PrngTransaction are valid. + * + * @param client The Client that should be used to validate the checksums. + * @throws BadEntityException This PrngTransaction's checksums are not valid. + */ + void validateChecksums(const Client& client) const override; + + /** + * Derived from Transaction. Build and add the PrngTransaction protobuf representation to the Transaction protobuf + * object. + * + * @param body The TransactionBody protobuf object being built. + */ + void addToBody(proto::TransactionBody& body) const override; + + /** + * Initialize this PrngTransaction from its source TransactionBody protobuf object. + */ + void initFromSourceTransactionBody(); + + /** + * Build a UtilPrngTransactionBody protobuf object from this PrngTransaction object. + * + * @return A pointer to a UtilPrngTransactionBody protobuf object filled with this PrngTransaction object's data. + */ + [[nodiscard]] proto::UtilPrngTransactionBody* build() const; + + /** + * The range from which to return the pseudorandom number. If this is , a 384-bit pseudorandom number will be returned + * in the TransactionRecord. If this is set, a 32-bit pseudorandom number will be returned between 0 and the specified + * range. + */ + int mRange = 0; +}; + +} // namespace Hedera + +#endif // HEDERA_SDK_CPP_PRNG_TRANSACTION_H_ diff --git a/sdk/main/include/TransactionRecord.h b/sdk/main/include/TransactionRecord.h index 37b782d47..6b4672244 100644 --- a/sdk/main/include/TransactionRecord.h +++ b/sdk/main/include/TransactionRecord.h @@ -32,6 +32,7 @@ #include "TransactionId.h" #include "TransactionReceipt.h" +#include #include #include #include @@ -128,6 +129,16 @@ class TransactionRecord */ std::vector mAutomaticTokenAssociations; + /** + * In the record of a PrngTransaction with no range, a pseudorandom 384-bit string. + */ + std::vector mPrngBytes; + + /** + * In the record of a PrngTransaction with a range, the pseudorandom 32-bit number. + */ + std::optional mPrngNumber; + /** * The new default EVM address of the account created by transaction with which this TransactionRecord is * associated. This field is populated only when the EVM address is not specified in the related transaction body. diff --git a/sdk/main/include/TransactionType.h b/sdk/main/include/TransactionType.h index 488434a2b..313668101 100644 --- a/sdk/main/include/TransactionType.h +++ b/sdk/main/include/TransactionType.h @@ -43,6 +43,7 @@ enum TransactionType : int FILE_DELETE_TRANSACTION, FILE_UPDATE_TRANSACTION, FREEZE_TRANSACTION, + PRNG_TRANSACTION, SCHEDULE_CREATE_TRANSACTION, SCHEDULE_DELETE_TRANSACTION, SCHEDULE_SIGN_TRANSACTION, diff --git a/sdk/main/include/WrappedTransaction.h b/sdk/main/include/WrappedTransaction.h index 4feecdee9..6de139be0 100644 --- a/sdk/main/include/WrappedTransaction.h +++ b/sdk/main/include/WrappedTransaction.h @@ -35,6 +35,7 @@ #include "FileDeleteTransaction.h" #include "FileUpdateTransaction.h" #include "FreezeTransaction.h" +#include "PrngTransaction.h" #include "ScheduleCreateTransaction.h" #include "ScheduleDeleteTransaction.h" #include "ScheduleSignTransaction.h" @@ -96,6 +97,7 @@ class WrappedTransaction FileDeleteTransaction, FileUpdateTransaction, FreezeTransaction, + PrngTransaction, ScheduleCreateTransaction, ScheduleDeleteTransaction, ScheduleSignTransaction, diff --git a/sdk/main/include/impl/Node.h b/sdk/main/include/impl/Node.h index f08f93e8b..09ce7cf3c 100644 --- a/sdk/main/include/impl/Node.h +++ b/sdk/main/include/impl/Node.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "AccountId.h" #include "BaseNode.h" @@ -230,6 +231,11 @@ class Node : public BaseNode */ std::unique_ptr mTokenStub = nullptr; + /** + * Pointer to the gRPC stub used to communicate with the utility service living on the remote node. + */ + std::unique_ptr mUtilStub = nullptr; + /** * The AccountId that runs the remote node represented by this Node. */ diff --git a/sdk/main/src/Executable.cc b/sdk/main/src/Executable.cc index 8d136ea3f..18719ef28 100644 --- a/sdk/main/src/Executable.cc +++ b/sdk/main/src/Executable.cc @@ -51,6 +51,7 @@ #include "FreezeTransaction.h" #include "NetworkVersionInfo.h" #include "NetworkVersionInfoQuery.h" +#include "PrngTransaction.h" #include "ScheduleCreateTransaction.h" #include "ScheduleDeleteTransaction.h" #include "ScheduleInfo.h" @@ -444,6 +445,7 @@ template class Executable; template class Executable; template class Executable; +template class Executable; template class Executable +#include +#include +#include + +namespace Hedera +{ +//----- +PrngTransaction::PrngTransaction(const proto::TransactionBody& transactionBody) + : Transaction(transactionBody) +{ + initFromSourceTransactionBody(); +} + +//----- +PrngTransaction::PrngTransaction(const std::map>& transactions) + : Transaction(transactions) +{ + initFromSourceTransactionBody(); +} + +//----- +PrngTransaction& PrngTransaction::setRange(int range) +{ + requireNotFrozen(); + mRange = range; + return *this; +} + +//----- +grpc::Status PrngTransaction::submitRequest(const proto::Transaction& request, + const std::shared_ptr& node, + const std::chrono::system_clock::time_point& deadline, + proto::TransactionResponse* response) const +{ + return node->submitTransaction(proto::TransactionBody::DataCase::kUtilPrng, request, deadline, response); +} + +//----- +void PrngTransaction::validateChecksums(const Client& client) const +{ + // Nothing to be validated. +} + +//----- +void PrngTransaction::addToBody(proto::TransactionBody& body) const +{ + body.set_allocated_util_prng(build()); +} + +//----- +void PrngTransaction::initFromSourceTransactionBody() +{ + const proto::TransactionBody transactionBody = getSourceTransactionBody(); + + if (!transactionBody.has_util_prng()) + { + throw std::invalid_argument("Transaction body doesn't contain UtilPrng data"); + } + + const proto::UtilPrngTransactionBody& body = transactionBody.util_prng(); + + mRange = body.range(); +} + +//----- +proto::UtilPrngTransactionBody* PrngTransaction::build() const +{ + auto body = std::make_unique(); + body->set_range(mRange); + return body.release(); +} + +} // namespace Hedera \ No newline at end of file diff --git a/sdk/main/src/Transaction.cc b/sdk/main/src/Transaction.cc index 7f0276f27..dd0624c49 100644 --- a/sdk/main/src/Transaction.cc +++ b/sdk/main/src/Transaction.cc @@ -36,6 +36,7 @@ #include "FileUpdateTransaction.h" #include "FreezeTransaction.h" #include "PrivateKey.h" +#include "PrngTransaction.h" #include "PublicKey.h" #include "ScheduleCreateTransaction.h" #include "ScheduleDeleteTransaction.h" @@ -226,6 +227,8 @@ WrappedTransaction Transaction::fromBytes(const std::vector; template class Transaction; template class Transaction; template class Transaction; +template class Transaction; template class Transaction; template class Transaction; template class Transaction; diff --git a/sdk/main/src/TransactionRecord.cc b/sdk/main/src/TransactionRecord.cc index 29ab3e485..c22b2a76b 100644 --- a/sdk/main/src/TransactionRecord.cc +++ b/sdk/main/src/TransactionRecord.cc @@ -107,6 +107,15 @@ TransactionRecord TransactionRecord::fromProtobuf(const proto::TransactionRecord TokenAssociation::fromProtobuf(proto.automatic_token_associations(i))); } + if (proto.has_prng_bytes()) + { + transactionRecord.mPrngBytes = internal::Utilities::stringToByteVector(proto.prng_bytes()); + } + else if (proto.has_prng_number()) + { + transactionRecord.mPrngNumber = proto.prng_number(); + } + if (!proto.evm_address().empty()) { transactionRecord.mEvmAddress = EvmAddress::fromBytes(internal::Utilities::stringToByteVector(proto.evm_address())); diff --git a/sdk/main/src/WrappedTransaction.cc b/sdk/main/src/WrappedTransaction.cc index be2926a0f..d84d39d6f 100644 --- a/sdk/main/src/WrappedTransaction.cc +++ b/sdk/main/src/WrappedTransaction.cc @@ -94,6 +94,10 @@ WrappedTransaction WrappedTransaction::fromProtobuf(const proto::TransactionBody { return WrappedTransaction(FreezeTransaction(proto)); } + else if (proto.has_util_prng()) + { + return WrappedTransaction(PrngTransaction(proto)); + } else if (proto.has_schedulecreate()) { return WrappedTransaction(ScheduleCreateTransaction(proto)); @@ -273,6 +277,11 @@ WrappedTransaction WrappedTransaction::fromProtobuf(const proto::SchedulableTran *txBody.mutable_freeze() = proto.freeze(); return WrappedTransaction(FreezeTransaction(txBody)); } + else if (proto.has_util_prng()) + { + *txBody.mutable_util_prng() = proto.util_prng(); + return WrappedTransaction(PrngTransaction(txBody)); + } else if (proto.has_scheduledelete()) { *txBody.mutable_scheduledelete() = proto.scheduledelete(); @@ -484,6 +493,12 @@ std::unique_ptr WrappedTransaction::toProtobuf() const transaction->updateSourceTransactionBody(nullptr); return std::make_unique(transaction->getSourceTransactionBody()); } + case PRNG_TRANSACTION: + { + const auto transaction = getTransaction(); + transaction->updateSourceTransactionBody(nullptr); + return std::make_unique(transaction->getSourceTransactionBody()); + } case SCHEDULE_CREATE_TRANSACTION: { const auto transaction = getTransaction(); @@ -706,6 +721,10 @@ std::unique_ptr WrappedTransaction::toSchedul { schedulableTxBody->set_allocated_freeze(txBody.release_freeze()); } + else if (txBody.has_util_prng()) + { + schedulableTxBody->set_allocated_util_prng(txBody.release_util_prng()); + } else if (txBody.has_scheduledelete()) { schedulableTxBody->set_allocated_scheduledelete(txBody.release_scheduledelete()); diff --git a/sdk/main/src/impl/Node.cc b/sdk/main/src/impl/Node.cc index b60b87fa5..f5f75636c 100644 --- a/sdk/main/src/impl/Node.cc +++ b/sdk/main/src/impl/Node.cc @@ -189,6 +189,8 @@ grpc::Status Node::submitTransaction(proto::TransactionBody::DataCase funcEnum, return mTokenStub->updateToken(&context, transaction, response); case proto::TransactionBody::DataCase::kTokenWipe: return mTokenStub->wipeTokenAccount(&context, transaction, response); + case proto::TransactionBody::DataCase::kUtilPrng: + return mUtilStub->prng(&context, transaction, response); default: // This should never happen throw std::invalid_argument("Unrecognized gRPC transaction method case"); @@ -265,14 +267,15 @@ std::shared_ptr Node::getTlsChannelCredentials() const void Node::initializeStubs() { // clang-format off - if (!mConsensusStub) mConsensusStub = proto::ConsensusService::NewStub(getChannel()); - if (!mCryptoStub) mCryptoStub = proto::CryptoService::NewStub(getChannel()); - if (!mFileStub) mFileStub = proto::FileService::NewStub(getChannel()); - if (!mFreezeStub) mFreezeStub = proto::FreezeService::NewStub(getChannel()); - if (!mNetworkStub) mNetworkStub = proto::NetworkService::NewStub(getChannel()); - if (!mScheduleStub) mScheduleStub = proto::ScheduleService::NewStub(getChannel()); + if (!mConsensusStub) mConsensusStub = proto::ConsensusService::NewStub(getChannel()); + if (!mCryptoStub) mCryptoStub = proto::CryptoService::NewStub(getChannel()); + if (!mFileStub) mFileStub = proto::FileService::NewStub(getChannel()); + if (!mFreezeStub) mFreezeStub = proto::FreezeService::NewStub(getChannel()); + if (!mNetworkStub) mNetworkStub = proto::NetworkService::NewStub(getChannel()); + if (!mScheduleStub) mScheduleStub = proto::ScheduleService::NewStub(getChannel()); if (!mSmartContractStub) mSmartContractStub = proto::SmartContractService::NewStub(getChannel()); - if (!mTokenStub) mTokenStub = proto::TokenService::NewStub(getChannel()); + if (!mTokenStub) mTokenStub = proto::TokenService::NewStub(getChannel()); + if (!mUtilStub) mUtilStub = proto::UtilService::NewStub(getChannel()); // clang-format on } @@ -287,6 +290,7 @@ void Node::closeStubs() mScheduleStub = nullptr; mSmartContractStub = nullptr; mTokenStub = nullptr; + mUtilStub = nullptr; } } // namespace Hedera::internal diff --git a/sdk/tests/integration/CMakeLists.txt b/sdk/tests/integration/CMakeLists.txt index 361d6e120..af8e03500 100644 --- a/sdk/tests/integration/CMakeLists.txt +++ b/sdk/tests/integration/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(${TEST_PROJECT_NAME} FreezeTransactionIntegrationTest.cc JSONIntegrationTest.cc NetworkVersionInfoQueryIntegrationTests.cc + PrngTransactionIntegrationTests.cc ScheduleCreateTransactionIntegrationTests.cc ScheduleDeleteTransactionIntegrationTests.cc ScheduleInfoQueryIntegrationTests.cc diff --git a/sdk/tests/integration/PrngTransactionIntegrationTests.cc b/sdk/tests/integration/PrngTransactionIntegrationTests.cc new file mode 100644 index 000000000..f0efd9a64 --- /dev/null +++ b/sdk/tests/integration/PrngTransactionIntegrationTests.cc @@ -0,0 +1,66 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 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. + * + */ +#include "BaseIntegrationTest.h" +#include "PrngTransaction.h" +#include "TransactionRecord.h" +#include "TransactionResponse.h" + +#include + +using namespace Hedera; + +class PrngTransactionIntegrationTest : public BaseIntegrationTest +{ +}; + +//----- +TEST_F(PrngTransactionIntegrationTest, ExecutePrngTransactionNoRange) +{ + // Given / When + TransactionResponse txResponse; + ASSERT_NO_THROW(txResponse = PrngTransaction().execute(getTestClient())); + + // Then + TransactionRecord txRecord; + ASSERT_NO_THROW(txRecord = txResponse.getRecord(getTestClient())); + + EXPECT_FALSE(txRecord.mPrngBytes.empty()); + EXPECT_FALSE(txRecord.mPrngNumber.has_value()); +} + +//----- +TEST_F(PrngTransactionIntegrationTest, ExecutePrngTransactionRange) +{ + // Given + const int range = 100; + + // When + TransactionResponse txResponse; + ASSERT_NO_THROW(txResponse = PrngTransaction().setRange(range).execute(getTestClient())); + + // Then + TransactionRecord txRecord; + ASSERT_NO_THROW(txRecord = txResponse.getRecord(getTestClient())); + + EXPECT_TRUE(txRecord.mPrngBytes.empty()); + EXPECT_TRUE(txRecord.mPrngNumber.has_value()); + EXPECT_GE(txRecord.mPrngNumber.value(), 0); + EXPECT_LE(txRecord.mPrngNumber.value(), range); +} diff --git a/sdk/tests/unit/CMakeLists.txt b/sdk/tests/unit/CMakeLists.txt index 22bfaa62f..2f2f78e91 100644 --- a/sdk/tests/unit/CMakeLists.txt +++ b/sdk/tests/unit/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(${TEST_PROJECT_NAME} NetworkVersionInfoUnitTests.cc NftIdTest.cc NodeAddressTest.cc + PrngTransactionUnitTests.cc ProxyStakerUnitTests.cc ScheduleCreateTransactionUnitTests.cc ScheduleDeleteTransactionUnitTests.cc diff --git a/sdk/tests/unit/PrngTransactionUnitTests.cc b/sdk/tests/unit/PrngTransactionUnitTests.cc new file mode 100644 index 000000000..bb4e53c28 --- /dev/null +++ b/sdk/tests/unit/PrngTransactionUnitTests.cc @@ -0,0 +1,77 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 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. + * + */ +#include "PrngTransaction.h" +#include "exceptions/IllegalStateException.h" + +#include +#include + +using namespace Hedera; + +class PrngTransactionTest : public ::testing::Test +{ +protected: + [[nodiscard]] inline int getTestRange() const { return mTestRange; } + +private: + const int mTestRange = 1; +}; + +//----- +TEST_F(PrngTransactionTest, ConstructPrngTransactionFromTransactionBodyProtobuf) +{ + // Given + auto body = std::make_unique(); + body->set_range(getTestRange()); + + proto::TransactionBody txBody; + txBody.set_allocated_util_prng(body.release()); + + // When + const PrngTransaction prngTransaction(txBody); + + // Then + EXPECT_EQ(prngTransaction.getRange(), getTestRange()); +} + +//----- +TEST_F(PrngTransactionTest, GetSetRange) +{ + // Given + PrngTransaction transaction; + + // When + EXPECT_NO_THROW(transaction.setRange(getTestRange())); + + // Then + EXPECT_EQ(transaction.getRange(), getTestRange()); +} + +//----- +TEST_F(PrngTransactionTest, GetSetRangeFrozen) +{ + // Given + PrngTransaction transaction = + PrngTransaction().setNodeAccountIds({ AccountId(1ULL) }).setTransactionId(TransactionId::generate(AccountId(1ULL))); + ASSERT_NO_THROW(transaction.freeze()); + + // When / Then + EXPECT_THROW(transaction.setRange(getTestRange()), IllegalStateException); +} diff --git a/sdk/tests/unit/TransactionTest.cc b/sdk/tests/unit/TransactionTest.cc index 508c40f54..38a2cb308 100644 --- a/sdk/tests/unit/TransactionTest.cc +++ b/sdk/tests/unit/TransactionTest.cc @@ -755,7 +755,7 @@ TEST_F(TransactionTest, FileAppendTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::FILE_APPEND_TRANSACTION); @@ -950,7 +950,7 @@ TEST_F(TransactionTest, FileUpdateTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::FILE_UPDATE_TRANSACTION); @@ -1015,7 +1015,7 @@ TEST_F(TransactionTest, FreezeTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::FREEZE_TRANSACTION); @@ -1048,6 +1048,71 @@ TEST_F(TransactionTest, FreezeTransactionFromTransactionListBytes) EXPECT_NE(wrappedTx.getTransaction(), nullptr); } +//----- +TEST_F(TransactionTest, PrngTransactionFromTransactionBodyBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_util_prng(new proto::UtilPrngTransactionBody); + + // When + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + + // Then + ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::PRNG_TRANSACTION); + EXPECT_NE(wrappedTx.getTransaction(), nullptr); +} + +//----- +TEST_F(TransactionTest, PrngTransactionFromTransactionBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_util_prng(new proto::UtilPrngTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + proto::Transaction tx; + tx.set_signedtransactionbytes(signedTx.SerializeAsString()); + + // When + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); + + // Then + ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::PRNG_TRANSACTION); + EXPECT_NE(wrappedTx.getTransaction(), nullptr); +} + +//----- +TEST_F(TransactionTest, PrngTransactionFromTransactionListBytes) +{ + // Given + proto::TransactionBody txBody; + txBody.set_allocated_util_prng(new proto::UtilPrngTransactionBody); + + proto::SignedTransaction signedTx; + signedTx.set_bodybytes(txBody.SerializeAsString()); + // SignatureMap not required + + proto::Transaction tx; + tx.set_signedtransactionbytes(signedTx.SerializeAsString()); + + proto::TransactionList txList; + *txList.add_transaction_list() = tx; + + // When + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(txList.SerializeAsString())); + + // Then + ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::PRNG_TRANSACTION); + EXPECT_NE(wrappedTx.getTransaction(), nullptr); +} + //----- TEST_F(TransactionTest, ScheduleCreateTransactionTransactionFromTransactionBodyBytes) { @@ -1404,8 +1469,8 @@ TEST_F(TransactionTest, TokenAssociateTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_ASSOCIATE_TRANSACTION); @@ -1470,7 +1535,7 @@ TEST_F(TransactionTest, TokenBurnTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_BURN_TRANSACTION); @@ -1535,7 +1600,7 @@ TEST_F(TransactionTest, TokenCreateTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_CREATE_TRANSACTION); @@ -1600,7 +1665,7 @@ TEST_F(TransactionTest, TokenDeleteTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_DELETE_TRANSACTION); @@ -1664,8 +1729,8 @@ TEST_F(TransactionTest, TokenDissociateTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_DISSOCIATE_TRANSACTION); @@ -1730,7 +1795,7 @@ TEST_F(TransactionTest, TokenFeeScheduleUpdateTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_FEE_SCHEDULE_UPDATE_TRANSACTION); @@ -1795,7 +1860,7 @@ TEST_F(TransactionTest, TokenFreezeTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_FREEZE_TRANSACTION); @@ -1859,8 +1924,8 @@ TEST_F(TransactionTest, TokenGrantKycTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_GRANT_KYC_TRANSACTION); @@ -1925,7 +1990,7 @@ TEST_F(TransactionTest, TokenMintTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_MINT_TRANSACTION); @@ -1990,7 +2055,7 @@ TEST_F(TransactionTest, TokenPauseTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_PAUSE_TRANSACTION); @@ -2054,8 +2119,8 @@ TEST_F(TransactionTest, TokenRevokeKycTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_REVOKE_KYC_TRANSACTION); @@ -2119,8 +2184,8 @@ TEST_F(TransactionTest, TokenUnfreezeTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_UNFREEZE_TRANSACTION); @@ -2184,8 +2249,8 @@ TEST_F(TransactionTest, TokenUnpauseTransactionFromTransactionBytes) tx.set_signedtransactionbytes(signedTx.SerializeAsString()); // When - const WrappedTransaction wrappedTx = Transaction::fromBytes( - internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + const WrappedTransaction wrappedTx = + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_UNPAUSE_TRANSACTION); @@ -2250,7 +2315,7 @@ TEST_F(TransactionTest, TokenUpdateTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_UPDATE_TRANSACTION); @@ -2315,7 +2380,7 @@ TEST_F(TransactionTest, TokenWipeTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOKEN_WIPE_TRANSACTION); @@ -2380,7 +2445,7 @@ TEST_F(TransactionTest, TopicCreateTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOPIC_CREATE_TRANSACTION); @@ -2445,7 +2510,7 @@ TEST_F(TransactionTest, TopicDeleteTransactionFromTransactionBytes) // When const WrappedTransaction wrappedTx = - Transaction::fromBytes(internal::Utilities::stringToByteVector(txBody.SerializeAsString())); + Transaction::fromBytes(internal::Utilities::stringToByteVector(tx.SerializeAsString())); // Then ASSERT_EQ(wrappedTx.getTransactionType(), TransactionType::TOPIC_DELETE_TRANSACTION); From 9a3cdaae1a2f87262b79338099d48bbeddef1359 Mon Sep 17 00:00:00 2001 From: Rob Walworth <110835868+rwalworth@users.noreply.github.com> Date: Fri, 20 Oct 2023 08:56:44 -0500 Subject: [PATCH 3/3] 540: Add `MultiSigOfflineExample` Signed-off-by: Rob Walworth Co-authored-by: Deyan Zhekov --- sdk/examples/CMakeLists.txt | 4 + sdk/examples/MultiSigOfflineExample.cc | 94 ++++++++++++++++++ sdk/main/include/PrivateKey.h | 17 ++++ sdk/main/include/Transaction.h | 2 + sdk/main/src/PrivateKey.cc | 127 +++++++++++++++++++++++++ sdk/main/src/Transaction.cc | 3 +- 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 sdk/examples/MultiSigOfflineExample.cc diff --git a/sdk/examples/CMakeLists.txt b/sdk/examples/CMakeLists.txt index f35c7e890..2129f9a58 100644 --- a/sdk/examples/CMakeLists.txt +++ b/sdk/examples/CMakeLists.txt @@ -27,6 +27,7 @@ set(GET_ADDRESS_BOOK_EXAMPLE_NAME ${PROJECT_NAME}-get-address-book-example) set(GET_EXCHANGE_RATES_EXAMPLE_NAME ${PROJECT_NAME}-get-exchange-rates-example) set(GET_FILE_CONTENTS_EXAMPLE_NAME ${PROJECT_NAME}-get-file-contents-example) set(MULTI_APP_TRANSFER_EXAMPLE_NAME ${PROJECT_NAME}-multi-app-transfer-example) +set(MULTI_SIG_OFFLINE_EXAMPLE_NAME ${PROJECT_NAME}-multi-sig-offline-example) set(NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME ${PROJECT_NAME}-nft-add-remove-allowances-example) set(PRNG_EXAMPLE_NAME ${PROJECT_NAME}-prng-example) set(SCHEDULE_EXAMPLE_NAME ${PROJECT_NAME}-schedule-example) @@ -69,6 +70,7 @@ add_executable(${GET_ADDRESS_BOOK_EXAMPLE_NAME} GetAddressBookExample.cc) add_executable(${GET_EXCHANGE_RATES_EXAMPLE_NAME} GetExchangeRatesExample.cc) add_executable(${GET_FILE_CONTENTS_EXAMPLE_NAME} GetFileContentsExample.cc) add_executable(${MULTI_APP_TRANSFER_EXAMPLE_NAME} MultiAppTransferExample.cc) +add_executable(${MULTI_SIG_OFFLINE_EXAMPLE_NAME} MultiSigOfflineExample.cc) add_executable(${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} NftAddRemoveAllowancesExample.cc) add_executable(${PRNG_EXAMPLE_NAME} PrngExample.cc) add_executable(${SCHEDULE_EXAMPLE_NAME} ScheduleExample.cc) @@ -131,6 +133,7 @@ target_link_libraries(${GET_ADDRESS_BOOK_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${GET_EXCHANGE_RATES_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${GET_FILE_CONTENTS_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${MULTI_APP_TRANSFER_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) +target_link_libraries(${MULTI_SIG_OFFLINE_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${PRNG_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${SCHEDULE_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) @@ -176,6 +179,7 @@ install(TARGETS ${GET_EXCHANGE_RATES_EXAMPLE_NAME} ${GET_FILE_CONTENTS_EXAMPLE_NAME} ${MULTI_APP_TRANSFER_EXAMPLE_NAME} + ${MULTI_SIG_OFFLINE_EXAMPLE_NAME} ${NFT_ADD_REMOVE_ALLOWANCES_EXAMPLE_NAME} ${PRNG_EXAMPLE_NAME} ${SCHEDULE_EXAMPLE_NAME} diff --git a/sdk/examples/MultiSigOfflineExample.cc b/sdk/examples/MultiSigOfflineExample.cc new file mode 100644 index 000000000..e80306168 --- /dev/null +++ b/sdk/examples/MultiSigOfflineExample.cc @@ -0,0 +1,94 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 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. + * + */ +#include "AccountBalance.h" +#include "AccountBalanceQuery.h" +#include "AccountCreateTransaction.h" +#include "Client.h" +#include "ECDSAsecp256k1PrivateKey.h" +#include "ED25519PrivateKey.h" +#include "KeyList.h" +#include "Status.h" +#include "TransactionReceipt.h" +#include "TransactionResponse.h" +#include "TransferTransaction.h" +#include "WrappedTransaction.h" + +#include +#include +#include +#include + +using namespace Hedera; + +int main(int argc, char** argv) +{ + if (argc < 3) + { + std::cout << "Please input account ID and private key" << std::endl; + return 1; + } + + // Get a client for the Hedera testnet, and set the operator account ID and key such that all generated transactions + // will be paid for by this account and be signed by this key. + Client client = Client::forTestnet(); + client.setOperator(AccountId::fromString(argv[1]), ED25519PrivateKey::fromString(argv[2])); + + // Generate a multi-sig account. + const std::shared_ptr key1 = ED25519PrivateKey::generatePrivateKey(); + const std::shared_ptr key2 = ECDSAsecp256k1PrivateKey::generatePrivateKey(); + const AccountId accountId = AccountCreateTransaction() + .setKey(std::make_shared(KeyList::of({ key1, key2 }))) + .setInitialBalance(Hbar(5LL)) + .execute(client) + .getReceipt(client) + .mAccountId.value(); + std::cout << "Created multi-sig account with ID " << accountId.toString() << std::endl; + + // Create a transfer of 2 Hbar from the new account to the operator account. + TransferTransaction transferTransaction = TransferTransaction() + .setNodeAccountIds({ AccountId(3ULL) }) + .addHbarTransfer(accountId, Hbar(-2LL)) + .addHbarTransfer(client.getOperatorAccountId().value(), Hbar(2LL)) + .freezeWith(&client); + + // Serialize the transaction and "send" it to its signatories. + std::vector transferTransactionBytes = transferTransaction.toBytes(); + WrappedTransaction wrappedTransferTransaction = Transaction::fromBytes(transferTransactionBytes); + + // Users sign the transaction with their private keys. + const std::vector key1Signature = key1->signTransaction(wrappedTransferTransaction); + const std::vector key2Signature = key2->signTransaction(wrappedTransferTransaction); + + // Add the signatures. + transferTransaction = *wrappedTransferTransaction.getTransaction(); + transferTransaction.signWithOperator(client); + transferTransaction.addSignature(key1->getPublicKey(), key1Signature); + transferTransaction.addSignature(key2->getPublicKey(), key2Signature); + + // Execute the transaction with all signatures. + transferTransaction.execute(client).getReceipt(client); + + // Get the balance of the multi-sig account. + std::cout << "Balance of multi-sign account (should be 3 Hbar): " + << AccountBalanceQuery().setAccountId(accountId).execute(client).getBalance().toTinybars() + << HbarUnit::TINYBAR().getSymbol() << std::endl; + + return 0; +} \ No newline at end of file diff --git a/sdk/main/include/PrivateKey.h b/sdk/main/include/PrivateKey.h index 47a26cc07..98236670b 100644 --- a/sdk/main/include/PrivateKey.h +++ b/sdk/main/include/PrivateKey.h @@ -22,6 +22,7 @@ #include "Key.h" +#include #include #include #include @@ -29,7 +30,11 @@ namespace Hedera { +template +class Transaction; + class PublicKey; +class WrappedTransaction; } namespace Hedera::internal::OpenSSLUtils @@ -119,6 +124,18 @@ class PrivateKey : public Key */ [[nodiscard]] virtual std::vector toBytesRaw() const = 0; + /** + * Sign a Transaction with this PrivateKey. + * + * @param transaction The Transaction to sign. + * @return The generated signature. + * @throws IllegalStateException If there is not exactly one node account ID set for the Transaction or if the + * Transaction is not frozen and doesn't have a TransactionId set. + */ + template + std::vector signTransaction(Transaction& transaction) const; + std::vector signTransaction(WrappedTransaction& transaction) const; + /** * Get this PrivateKey's chain code. It is possible that the chain code could be empty. * diff --git a/sdk/main/include/Transaction.h b/sdk/main/include/Transaction.h index 418b994ee..bdfeaee75 100644 --- a/sdk/main/include/Transaction.h +++ b/sdk/main/include/Transaction.h @@ -440,6 +440,8 @@ class Transaction [[nodiscard]] virtual TransactionId getCurrentTransactionId() const; private: + friend class PrivateKey; + /** * Build and add the derived Transaction's protobuf representation to the Transaction protobuf object. * diff --git a/sdk/main/src/PrivateKey.cc b/sdk/main/src/PrivateKey.cc index 8a646a8d0..937016ae9 100644 --- a/sdk/main/src/PrivateKey.cc +++ b/sdk/main/src/PrivateKey.cc @@ -21,6 +21,8 @@ #include "ECDSAsecp256k1PrivateKey.h" #include "ED25519PrivateKey.h" #include "PublicKey.h" +#include "Transaction.h" +#include "WrappedTransaction.h" #include "exceptions/BadKeyException.h" #include "exceptions/OpenSSLException.h" #include "impl/HexConverter.h" @@ -29,6 +31,9 @@ #include "impl/openssl_utils/OpenSSLUtils.h" #include +#include +#include +#include namespace Hedera { @@ -69,6 +74,128 @@ std::unique_ptr PrivateKey::fromBytesDer(const std::vector +std::vector PrivateKey::signTransaction(Transaction& transaction) const +{ + // Verify that the Transaction is only going to one node. + transaction.requireOneNodeAccountId(); + + // Freeze the transaction if not already frozen. + if (!transaction.isFrozen()) + { + transaction.freeze(); + } + + // This PrivateKey is only able to grab the built Transaction protobuf object, so make sure it's built (index is + // guaranteed 0 since the one node account ID check has already passed). + transaction.buildTransaction(0U); + + // Generate the signature. + proto::SignedTransaction transactionToSign; + transactionToSign.ParseFromString(transaction.getTransactionProtobufObject(0U).signedtransactionbytes()); + const std::vector signature = sign(internal::Utilities::stringToByteVector(transactionToSign.bodybytes())); + + // Add the signature to the Transaction. + transaction.addSignature(getPublicKey(), signature); + + return signature; +} + +//----- +std::vector PrivateKey::signTransaction(WrappedTransaction& transaction) const +{ + switch (transaction.getTransactionType()) + { + case ACCOUNT_ALLOWANCE_APPROVE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case ACCOUNT_ALLOWANCE_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case ACCOUNT_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case ACCOUNT_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case ACCOUNT_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case CONTRACT_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case CONTRACT_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case CONTRACT_EXECUTE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case CONTRACT_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case ETHEREUM_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case FILE_APPEND_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case FILE_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case FILE_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case FILE_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case FREEZE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case PRNG_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case SCHEDULE_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case SCHEDULE_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case SCHEDULE_SIGN_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case SYSTEM_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case SYSTEM_UNDELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_ASSOCIATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_BURN_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_DISSOCIATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_FEE_SCHEDULE_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_FREEZE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_GRANT_KYC_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_MINT_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_PAUSE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_REVOKE_KYC_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_UNFREEZE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_UNPAUSE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOKEN_WIPE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOPIC_CREATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOPIC_DELETE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOPIC_MESSAGE_SUBMIT_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TOPIC_UPDATE_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case TRANSFER_TRANSACTION: + return signTransaction(*transaction.getTransaction()); + case UNKNOWN_TRANSACTION: + { + throw std::invalid_argument("Unrecognized TransactionType"); + } + } +} + //----- std::vector PrivateKey::getChainCode() const { diff --git a/sdk/main/src/Transaction.cc b/sdk/main/src/Transaction.cc index dd0624c49..bf926845e 100644 --- a/sdk/main/src/Transaction.cc +++ b/sdk/main/src/Transaction.cc @@ -839,13 +839,14 @@ void Transaction::addTransaction(const proto::Transaction& trans static_cast(transaction.signedtransactionbytes().size())); // Add the SignedTransaction protobuf object to the SignedTransaction protobuf object list. - addTransaction(signedTx); + mImpl->mSignedTransactions.push_back(signedTx); } //----- template void Transaction::addTransaction(const proto::SignedTransaction& transaction) { + mImpl->mTransactions.push_back(proto::Transaction()); mImpl->mSignedTransactions.push_back(transaction); }