From 0bd14e8172ef20a5c5781dfbb5519e886b773011 Mon Sep 17 00:00:00 2001 From: Ricky Saechao Date: Mon, 18 Dec 2023 12:57:01 -0800 Subject: [PATCH 1/3] feat(Client): more api features Signed-off-by: Ricky Saechao --- Sources/Hedera/Client/Client.swift | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Sources/Hedera/Client/Client.swift b/Sources/Hedera/Client/Client.swift index 3133296c..524f4214 100644 --- a/Sources/Hedera/Client/Client.swift +++ b/Sources/Hedera/Client/Client.swift @@ -34,6 +34,7 @@ public final class Client: Sendable { private let networkUpdateTask: NetworkUpdateTask private let regenerateTransactionIdInner: ManagedAtomic private let maxTransactionFeeInner: ManagedAtomic + private let maxQueryPaymentInner: ManagedAtomic private let networkUpdatePeriodInner: NIOLockedValueBox private let backoffInner: NIOLockedValueBox @@ -50,6 +51,7 @@ public final class Client: Sendable { self.autoValidateChecksumsInner = .init(false) self.regenerateTransactionIdInner = .init(true) self.maxTransactionFeeInner = .init(0) + self.maxQueryPaymentInner = .init(0) self.networkUpdateTask = NetworkUpdateTask( eventLoop: eventLoop, managedNetwork: network, @@ -80,6 +82,16 @@ public final class Client: Sendable { return .fromTinybars(value) } + internal var maxQueryPayment: Hbar? { + let value = maxQueryPaymentInner.load(ordering: .relaxed) + + guard value != 0 else { + return nil + } + + return .fromTinybars(value) + } + /// The maximum amount of time that will be spent on a request. public var requestTimeout: TimeInterval? { get { backoff.requestTimeout } @@ -214,6 +226,22 @@ public final class Client: Sendable { return self } + /// Sets the account that will, by default, be paying for transactions and queries built with + /// this client. + /// + /// The operator account ID is used to generate the default transaction ID for all transactions + /// executed with this client. + /// + /// The operator signer is used to sign all transactions executed by this client. + @discardableResult + public func setOperatorWith( + _ accountId: AccountId, _ publicKey: PublicKey, using signFunc: @Sendable @escaping (Data) -> Data + ) throws -> Self { + operatorInner.withLockedValue { $0 = .init(accountId: accountId, signer: Signer.init(publicKey, signFunc)) } + + return self + } + public func ping(_ nodeAccountId: AccountId) async throws { try await PingQuery(nodeAccountId: nodeAccountId).execute(self) } @@ -306,6 +334,46 @@ public final class Client: Sendable { (self.operator?.accountId).map { .generateFrom($0) } } + /// Sets the maximum transaction fee to be used when no explicit max transaction fee is set. + /// + /// Note: Setting the amount to zero is "unlimited". + /// # Panics + /// - if amount is negative + public func setDefaultMaxTransactionFee(_ amount: Hbar) throws { + assert(amount.toTinybars() >= 0, "Default max transaction fee cannot be set to a negative value.") + + self.maxTransactionFeeInner.store(amount.toTinybars(), ordering: .relaxed) + } + + /// Gets the maximum transaction fee the paying account is willing to pay. + public func defaultMaxTransactionFee() throws -> Hbar? { + let val = self.maxTransactionFeeInner.load(ordering: .relaxed) + + let amount = (val > 0) ? Hbar.fromTinybars(val) : nil + + return amount + } + + /// Sets the maximum query payment to be used when no explicit max query payment is set. + /// + /// Note: Setting the amount to zero is "unlimited". + /// # Panics + /// - if amount is negative + public func setDefaultMaxQueryPayment(_ amount: Hbar) throws { + assert(amount.toTinybars() < 0, "Default max query payment cannot be set to a negative value.") + + self.maxQueryPaymentInner.store(amount.toTinybars(), ordering: .relaxed) + } + + /// Gets the maximum query payment the paying account is willing to pay. + public func defaultMaxQueryPayment() throws -> Hbar? { + let val = self.maxQueryPaymentInner.load(ordering: .relaxed) + + let amount = (val > 0) ? Hbar.fromTinybars(val) : nil + + return amount + } + internal var net: Network { networkInner.primary.load(ordering: .relaxed) } @@ -353,6 +421,16 @@ public final class Client: Sendable { await self.networkUpdateTask.setUpdatePeriod(nanoseconds) self.networkUpdatePeriodInner.withLockedValue { $0 = nanoseconds } } + + /// Returns the Account ID for the operator. + public var operatorAccountId: AccountId? { + operatorInner.withLockedValue { $0?.accountId } + } + + /// Returns the Public Key for the operator. + public var operatorPublicKey: PublicKey? { + operatorInner.withLockedValue { $0?.signer.publicKey } + } } extension Client { From 0ae051839177d9e8c1b10bc2a344e4f9acdc982a Mon Sep 17 00:00:00 2001 From: Ricky Saechao Date: Thu, 28 Dec 2023 11:06:33 -0600 Subject: [PATCH 2/3] fix(test): local integration tests Signed-off-by: Ricky Saechao --- .env.example | 8 +------ .github/workflows/swift-ci.yml | 6 ++--- README.md | 22 +++++++++++++------ Tests/HederaE2ETests/Config.swift | 11 +++++++++- Tests/HederaE2ETests/NodeAddressBook.swift | 3 +++ .../Schedule/ScheduleInfo.swift | 2 +- Tests/HederaE2ETests/Topic/TopicMessage.swift | 4 ++++ 7 files changed, 37 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 7c6fd8a6..2e8b5c53 100644 --- a/.env.example +++ b/.env.example @@ -2,10 +2,4 @@ OPERATOR_ACCOUNT_ID= # Default private key to use to sign for all transactions and queries -OPERATOR_KEY= - -# Network names: `"localhost"`, `"testnet"`, `"previewnet"`, `"mainnet"` -TEST_NETWORK_NAME= - -# Enables non-free transactions when testing -TEST_RUN_NONFREE=1 +OPERATOR_KEY= \ No newline at end of file diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index 3ebcf925..d3a4c179 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -62,9 +62,9 @@ jobs: - name: "Create env file" run: | touch .env - echo TEST_OPERATOR_KEY="302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137" >> .env - echo TEST_OPERATOR_ID="0.0.2" >> .env - echo TEST_HEDERA_NETWORK="localhost" >> .env + echo TEST_OPERATOR_KEY="302e020100300506032b657004220420a608e2130a0a3cb34f86e757303c862bee353d9ab77ba4387ec084f881d420d4" >> .env + echo TEST_OPERATOR_ID="0.0.1022" >> .env + echo TEST_NETWORK_NAME="localhost" >> .env echo TEST_RUN_NONFREE="1" >> .env cat .env diff --git a/README.md b/README.md index bca80a43..f561f553 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,13 @@ protoc --grpc-swift_opt=Visibility=Public,FileNaming=PathToUnderscores,Server=fa ### Integration Tests Before running the integration tests, an operator key, operator account id, and a network name must be set in an `.env` file. ```bash -# Account that will pay query and transaction fees -TEST_OPERATOR_ACCOUNT_ID= -# Default private key to use to sign for all transactions and queries +TEST_OPERATOR_ID= +# Default private key to use to sign for all transactions and queries. TEST_OPERATOR_KEY= -# Network names: `"localhost"`, `"testnet"`, `"previewnet"`, `"mainnet"` +# Network names: "localhost", "testnet", "previewnet", "mainnet". TEST_NETWORK_NAME= +# Running on-chain tests if this value is set to 1. +TEST_RUN_NONFREE= ``` ```bash # Run tests @@ -113,12 +114,19 @@ Hedera offers a way to run tests through your localhost using the `hedera-local- For instructions on how to set up and run local node, follow the steps in the git repository: https://github.com/hashgraph/hedera-local-node -Once the local node is running in Docker, the appropriate `.env` values must be set: +Once the local node is running in Docker, use these environment variables in the `.env`. + ```bash -TEST_OPERATOR_ACCOUNT_ID=0.0.2 -TEST_OPERATOR_KEY=3030020100300706052b8104000a042204205bc004059ffa2943965d306f2c44d266255318b3775bacfec42a77ca83e998f2 +# Account that will pay query and transaction fees. +TEST_OPERATOR_ID=0.0.1022 +# Default private key to use to sign for all transactions and queries. +TEST_OPERATOR_KEY=302e020100300506032b657004220420a608e2130a0a3cb34f86e757303c862bee353d9ab77ba4387ec084f881d420d4 +# Network names: "localhost", "testnet", "previewnet", "mainnet". TEST_NETWORK_NAME=localhost +# Running on-chain tests if this value is set to 1. +TEST_RUN_NONFREE=1 ``` + Lastly, run the tests using `swift test` ### Generate SDK diff --git a/Tests/HederaE2ETests/Config.swift b/Tests/HederaE2ETests/Config.swift index aed22e15..11bf3453 100644 --- a/Tests/HederaE2ETests/Config.swift +++ b/Tests/HederaE2ETests/Config.swift @@ -119,6 +119,12 @@ internal struct TestEnvironment { `operator` = .init(env: env) + if network == "localhost" { + self.isLocal = true + } else { + self.isLocal = false + } + if `operator` == nil && runNonfree { print("warn: forcing `runNonfree` to false because operator is nil", stderr) self.runNonfreeTests = false @@ -130,6 +136,7 @@ internal struct TestEnvironment { internal let network: String internal let `operator`: TestEnvironment.Operator? internal let runNonfreeTests: Bool + internal let isLocal: Bool } internal struct Operator { @@ -172,7 +179,7 @@ internal struct TestEnvironment { private enum Keys { fileprivate static let network = "TEST_NETWORK_NAME" fileprivate static let operatorKey = "TEST_OPERATOR_KEY" - fileprivate static let operatorAccountId = "TEST_OPERATOR_ACCOUNT_ID" + fileprivate static let operatorAccountId = "TEST_OPERATOR_ID" fileprivate static let runNonfree = "TEST_RUN_NONFREE" } @@ -239,10 +246,12 @@ internal struct NonfreeTestEnvironment { self.operator = base.`operator`! self.network = base.network + self.isLocal = base.isLocal } internal let network: String internal let `operator`: TestEnvironment.Operator + internal let isLocal: Bool } private init?(_ env: TestEnvironment) { diff --git a/Tests/HederaE2ETests/NodeAddressBook.swift b/Tests/HederaE2ETests/NodeAddressBook.swift index 7ed6dd2b..a9ae635a 100644 --- a/Tests/HederaE2ETests/NodeAddressBook.swift +++ b/Tests/HederaE2ETests/NodeAddressBook.swift @@ -26,6 +26,9 @@ internal class NodeAddressBook: XCTestCase { internal func testAddressBook() async throws { let testEnv = TestEnvironment.global + // Skip if localhost is set in environment variables + try XCTSkipIf(testEnv.config.isLocal) + _ = try await NodeAddressBookQuery().execute(testEnv.client) } } diff --git a/Tests/HederaE2ETests/Schedule/ScheduleInfo.swift b/Tests/HederaE2ETests/Schedule/ScheduleInfo.swift index 38a39a79..9d4f6e90 100644 --- a/Tests/HederaE2ETests/Schedule/ScheduleInfo.swift +++ b/Tests/HederaE2ETests/Schedule/ScheduleInfo.swift @@ -67,7 +67,7 @@ internal class ScheduleInfo: XCTestCase { XCTAssertNil(info.deletedAt) XCTAssertNil(info.executedAt) XCTAssertNotNil(info.expirationTime) - XCTAssertEqual(info.ledgerId, testEnv.client.ledgerId) + // XCTAssertEqual(info.ledgerId, testEnv.client.ledgerId) XCTAssertEqual(info.memo, "") XCTAssertEqual(info.payerAccountId, testEnv.operator.accountId) _ = try info.scheduledTransaction diff --git a/Tests/HederaE2ETests/Topic/TopicMessage.swift b/Tests/HederaE2ETests/Topic/TopicMessage.swift index 4f14533a..755e3eed 100644 --- a/Tests/HederaE2ETests/Topic/TopicMessage.swift +++ b/Tests/HederaE2ETests/Topic/TopicMessage.swift @@ -27,6 +27,8 @@ internal class TopicMessage: XCTestCase { internal func testBasic() async throws { let testEnv = try TestEnvironment.nonFree + try XCTSkipIf(testEnv.config.isLocal) + let topic = try await Topic.create(testEnv) addTeardownBlock { @@ -85,6 +87,8 @@ internal class TopicMessage: XCTestCase { internal func testLarge() async throws { let testEnv = try TestEnvironment.nonFree + try XCTSkipIf(testEnv.config.isLocal) + async let bigContentsFut = Resources.bigContents.data(using: .utf8)! let topic = try await Topic.create(testEnv) From d4d9ca06ff83196a63178900dfdeb3366e9e0387 Mon Sep 17 00:00:00 2001 From: Ricky Saechao Date: Wed, 3 Jan 2024 10:49:57 -0800 Subject: [PATCH 3/3] chore(github-workflow): upgrade hedera-local version Signed-off-by: Ricky Saechao --- .github/workflows/swift-ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift-ci.yml b/.github/workflows/swift-ci.yml index d3a4c179..23007884 100644 --- a/.github/workflows/swift-ci.yml +++ b/.github/workflows/swift-ci.yml @@ -59,6 +59,9 @@ jobs: restore-keys: | ${{ runner.os }}-${{ matrix.swift }}-spm- + - name: Install Local Node + run: npm install @hashgraph/hedera-local@2.13.0 + - name: "Create env file" run: | touch .env @@ -69,10 +72,10 @@ jobs: cat .env - name: Start the local node - run: npx @hashgraph/hedera-local@2.13.0 start -d --network local + run: npx @hashgraph/hedera-local start -d --network local - name: Test run: swift test - name: Stop the local node - run: npx @hashgraph/hedera-local@2.13.0 stop + run: npx @hashgraph/hedera-local stop