Skip to content

Commit

Permalink
Add remaining transaction requirements (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
izik1 authored Apr 28, 2023
1 parent 4d5f678 commit 47f3edc
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 20 deletions.
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

0 comments on commit 47f3edc

Please sign in to comment.