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

Add remaining transaction requirements #76

Merged
merged 5 commits into from
Apr 28, 2023
Merged
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
16 changes: 16 additions & 0 deletions Sources/Hedera/ChunkedTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ extension ChunkedTransaction.FirstChunkView: Execute {
internal var explicitTransactionId: TransactionId? { transaction.transactionId }
internal var requiresTransactionId: Bool { true }

internal var operatorAccountId: AccountId? {
self.transaction.operatorAccountId
}

internal var regenerateTransactionId: Bool? {
self.transaction.regenerateTransactionId
}

internal func makeRequest(_ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> (
GrpcRequest, Context
) {
Expand Down Expand Up @@ -226,6 +234,14 @@ extension ChunkedTransaction.ChunkView: Execute {
internal var explicitTransactionId: TransactionId? { nil }
internal var requiresTransactionId: Bool { true }

internal var operatorAccountId: AccountId? {
self.transaction.operatorAccountId
}

internal var regenerateTransactionId: Bool? {
self.transaction.regenerateTransactionId
}

internal func makeRequest(_ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> (
GrpcRequest, Context
) {
Expand Down
24 changes: 23 additions & 1 deletion Sources/Hedera/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public final class Client: Sendable {
internal let network: Network
private let operatorInner: NIOLockedValueBox<Operator?>
private let autoValidateChecksumsInner: AtomicBool
private let regenerateTransactionIdInner: AtomicBool
private let maxTransactionFeeInner: AtomicInt64

private init(
Expand All @@ -65,6 +66,7 @@ public final class Client: Sendable {
self.operatorInner = .init(nil)
self.ledgerIdInner = .init(ledgerId)
self.autoValidateChecksumsInner = .init(false)
self.regenerateTransactionIdInner = .init(true)
self.maxTransactionFeeInner = .init(0)
}

Expand Down Expand Up @@ -152,7 +154,7 @@ public final class Client: Sendable {
/// this client.
@discardableResult
public func setOperator(_ accountId: AccountId, _ privateKey: PrivateKey) -> Self {
operatorInner.withLockedValue { $0 = .init(accountId: accountId, signer: privateKey) }
operatorInner.withLockedValue { $0 = .init(accountId: accountId, signer: .privateKey(privateKey)) }

return self
}
Expand Down Expand Up @@ -225,6 +227,26 @@ public final class Client: Sendable {
autoValidateChecksums
}

/// Whether or not the transaction ID should be refreshed if a ``Status/transactionExpired`` occurs.
///
/// By default, this is true.
///
/// >Note: Some operations forcibly disable transaction ID regeneration, such as setting the transaction ID explicitly.
public var defaultRegenerateTransactionId: Bool {
get { self.regenerateTransactionIdInner.inner.load(ordering: .relaxed) }
set(value) { self.regenerateTransactionIdInner.inner.store(value, ordering: .relaxed) }
}

/// Sets whether or not the transaction ID should be refreshed if a ``Status/transactionExpired`` occurs.
///
/// Various operations such as setting the transaction ID exlicitly can forcibly disable transaction ID regeneration.
@discardableResult
public func setDefaultRegenerateTransactionId(_ defaultRegenerateTransactionId: Bool) -> Self {
self.defaultRegenerateTransactionId = defaultRegenerateTransactionId

return self
}

internal func generateTransactionId() -> TransactionId? {
(self.operator?.accountId).map { .generateFrom($0) }
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hedera/Contract/ContractCreateFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public final class ContractCreateFlow {
///
/// - Returns: `self`
@discardableResult
public func signWith(_ publicKey: PublicKey, _ signer: @escaping (Data) -> (Data)) -> Self {
public func signWith(_ publicKey: PublicKey, _ signer: @Sendable @escaping (Data) -> (Data)) -> Self {
self.contractCreateData.signer = .init(publicKey, signer)

return self
Expand Down
17 changes: 14 additions & 3 deletions Sources/Hedera/Execute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ internal protocol Execute {

var requiresTransactionId: Bool { get }

/// ID for the account paying for this transaction, if explicitly specified.
var operatorAccountId: AccountId? { get }

/// Whether or not the transaction ID should be refreshed if a ``Status/transactionExpired`` occurs.
var regenerateTransactionId: Bool? { get }

/// Check whether to retry for a given pre-check status.
func shouldRetryPrecheck(forStatus status: Status) -> Bool

Expand Down Expand Up @@ -90,7 +96,9 @@ internal func executeAny<E: Execute & ValidateChecksums>(_ client: Client, _ exe

let explicitTransactionId = executable.explicitTransactionId
var transactionId =
executable.requiresTransactionId ? (explicitTransactionId ?? client.generateTransactionId()) : nil
executable.requiresTransactionId
? (explicitTransactionId ?? executable.operatorAccountId.map(TransactionId.generateFrom)
?? client.generateTransactionId()) : nil

let explicitNodeIndexes = try executable.nodeAccountIds.map { try client.network.nodeIndexesForIds($0) }

Expand Down Expand Up @@ -143,11 +151,14 @@ internal func executeAny<E: Execute & ValidateChecksums>(_ client: Client, _ exe
// try the next node in our allowed list, immediately
lastError = executable.makeErrorPrecheck(precheckStatus, transactionId)

case .transactionExpired where explicitTransactionId == nil:
case .transactionExpired
where explicitTransactionId == nil
&& (executable.regenerateTransactionId ?? client.defaultRegenerateTransactionId):
// the transaction that was generated has since expired
// re-generate the transaction ID and try again, immediately
lastError = executable.makeErrorPrecheck(precheckStatus, transactionId)
transactionId = client.generateTransactionId()
transactionId =
executable.operatorAccountId.map(TransactionId.generateFrom) ?? client.generateTransactionId()
continue inner

case .UNRECOGNIZED(let value):
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hedera/Operator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

internal struct Operator {
internal let accountId: AccountId
internal let signer: PrivateKey
internal let signer: Signer

internal func generateTransactionId() -> TransactionId {
.generateFrom(accountId)
Expand Down
8 changes: 8 additions & 0 deletions Sources/Hedera/PingQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ extension PingQuery: Execute {

internal var explicitTransactionId: TransactionId? { nil }

internal var operatorAccountId: AccountId? {
nil
}

internal var regenerateTransactionId: Bool? {
false
}

internal var requiresTransactionId: Bool { false }

internal func makeRequest(_ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> (Proto_Query, ()) {
Expand Down
1 change: 1 addition & 0 deletions Sources/Hedera/PrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ public struct PrivateKey: LosslessStringConvertible, ExpressibleByStringLiteral,
return false
}

@Sendable
public func sign(_ message: Data) -> Data {
switch kind {
case .ecdsa(let key):
Expand Down
8 changes: 8 additions & 0 deletions Sources/Hedera/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ extension Query: Execute {
self.requiresPayment
}

internal var operatorAccountId: AccountId? {
self.payment.operatorAccountId
}

internal var regenerateTransactionId: Bool? {
self.payment.regenerateTransactionId
}

internal func makeRequest(_ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> (Proto_Query, ()) {
let request = toQueryProtobufWith(
try .with { proto in
Expand Down
8 changes: 8 additions & 0 deletions Sources/Hedera/QueryCost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ extension QueryCost: Execute {

internal var explicitTransactionId: TransactionId? { nil }

internal var operatorAccountId: AccountId? {
nil
}

internal var regenerateTransactionId: Bool? {
false
}

internal var requiresTransactionId: Bool { false }

internal func makeRequest(_ transactionId: TransactionId?, _ nodeAccountId: AccountId) throws -> (Proto_Query, ()) {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Hedera/Signer.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Foundation

internal final class Signer {
internal init(_ publicKey: PublicKey, _ signFunc: @escaping (Data) -> Data) {
internal final class Signer: Sendable {
internal init(_ publicKey: PublicKey, _ signFunc: @Sendable @escaping (Data) -> Data) {
self.publicKey = publicKey
self.signFunc = signFunc
}

internal let publicKey: PublicKey
fileprivate let signFunc: (Data) -> Data
fileprivate let signFunc: @Sendable (Data) -> Data

internal static func privateKey(_ key: PrivateKey) -> Self {
Self(key.publicKey, key.sign(_:))
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hedera/TopicMessageQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ extension TopicMessageQuery: MirrorRequest {
}
}

enum IncompleteMessage {
private enum IncompleteMessage {
case partial(expiry: Timestamp, messages: [ProtoTopicMessageChunk])
case expired
case complete
Expand Down
Loading