diff --git a/Sources/Hedera/Backoff.swift b/Sources/Hedera/Backoff.swift new file mode 100644 index 00000000..e3d62195 --- /dev/null +++ b/Sources/Hedera/Backoff.swift @@ -0,0 +1,23 @@ +import Foundation + +enum Backoff { + static let receiptInitialDelay: UInt32 = 1 + static let receiptRetryDelay: TimeInterval = 0.5 + + static func getDelayUs(startTime: Date, attempt: UInt8) -> UInt32? { + // exponential backoff algorithm: + // next delay is some constant * rand(0, 2 ** attempt - 1) + let delay = Backoff.receiptRetryDelay + * Double.random(in: 0.. Proto_TransactionResponse -let receiptInitialDelay: UInt32 = 1 -let receiptRetryDelay: TimeInterval = 0.5 - public class Transaction { var inner: Proto_Transaction var client: Client? @@ -83,46 +80,11 @@ public class Transaction { // sleep(receiptInitialDelay) // while true { - // attempt += 1 - - // let receipt = queryReceipt(client) - // switch receipt { - // case .success(let receipt): - // let receiptStatus = receipt.status - - // if Int(receiptStatus) == Proto_ResponseCodeEnum.unknown.rawValue || - // receiptStatus == Proto_ResponseCodeEnum.ok.rawValue { - // // stop trying if the delay will put us over `validDuration` - // guard let delayUs = getReceiptDelayUs(startTime: startTime, attempt: attempt) else { - // return .failure(HederaError(message: "executeForReceipt timed out")) - // } - - // usleep(delayUs) - // } else { - // return .success(mapResponse(receipt)) - // } - // case .failure(let error): - // return .failure(error) - // } + // // } // } - func getReceiptDelayUs(startTime: Date, attempt: UInt8) -> UInt32? { - // exponential backoff algorithm: - // next delay is some constant * rand(0, 2 ** attempt - 1) - let delay = receiptRetryDelay - * Double.random(in: 0.. Result in - if response.nodeTransactionPrecheckCode == .ok { - return .success(self.txId) - } else { - return .failure(HederaError(message: "preCheckCode was not OK: \(response.nodeTransactionPrecheckCode)")) + // return methodForTransaction(client.grpcClient(for: node))(inner, nil).response.map { response -> Result in + // if response.nodeTransactionPrecheckCode == .ok { + // return .success(self.txId) + // } else { + // return .failure(HederaError(message: "preCheckCode was not OK: \(response.nodeTransactionPrecheckCode)")) + // } + // } + + return client.eventLoopGroup.next().submit { + let startTime = Date() + var attempt: UInt8 = 0 + + sleep(Backoff.receiptInitialDelay) + + while(true) { + attempt += 1 + + let response = Result { try self.methodForTransaction(client.grpcClient(for: node))(self.inner, nil).response.wait() } + switch response { + case .success(let response): + switch response.nodeTransactionPrecheckCode { + case .busy: + // stop trying if the delay will put us over `validDuration` + guard let delayUs = Backoff.getDelayUs(startTime: startTime, attempt: attempt) else { + return .failure(HederaError(message: "execute timed out")) + } + + usleep(delayUs) + case .ok: + return .success(self.txId) + default: + return .failure(HederaError(message: "preCheckCode was not OK: \(response.nodeTransactionPrecheckCode)")) + } + case .failure(let error): + return .failure(HederaError(message: "\(error)")) + } } } }