Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Client): more api features #320

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
13 changes: 8 additions & 5 deletions .github/workflows/swift-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,23 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.swift }}-spm-

- name: Install Local Node
run: npm install @hashgraph/[email protected]

- 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

- 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
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
78 changes: 78 additions & 0 deletions Sources/Hedera/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class Client: Sendable {
private let networkUpdateTask: NetworkUpdateTask
private let regenerateTransactionIdInner: ManagedAtomic<Bool>
private let maxTransactionFeeInner: ManagedAtomic<Int64>
private let maxQueryPaymentInner: ManagedAtomic<Int64>
private let networkUpdatePeriodInner: NIOLockedValueBox<UInt64?>
private let backoffInner: NIOLockedValueBox<Backoff>

Expand All @@ -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,
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 10 additions & 1 deletion Tests/HederaE2ETests/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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"
}

Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions Tests/HederaE2ETests/NodeAddressBook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion Tests/HederaE2ETests/Schedule/ScheduleInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Tests/HederaE2ETests/Topic/TopicMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Loading