From a9e8a40267369745a2f06f7828e9efcc1042d7b4 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Thu, 22 Feb 2024 21:00:16 -0300 Subject: [PATCH] Refactor code to build on top of Proposal API --- .../CombineSynchronizer.swift | 22 +++++++++- .../ZcashLightClientKit/Synchronizer.swift | 10 ++--- .../Synchronizer/CombineSDKSynchronizer.swift | 12 ++++++ .../Synchronizer/SDKSynchronizer.swift | 38 ++++++++---------- .../Transaction/TransactionEncoder.swift | 12 +++--- .../WalletTransactionEncoder.swift | 34 +++------------- .../PaymentURIFulfillmentTests.swift | 40 +++++++++++++------ .../AutoMockable.generated.swift | 30 +++++++------- 8 files changed, 108 insertions(+), 90 deletions(-) diff --git a/Sources/ZcashLightClientKit/CombineSynchronizer.swift b/Sources/ZcashLightClientKit/CombineSynchronizer.swift index 2581af68..16ae0830 100644 --- a/Sources/ZcashLightClientKit/CombineSynchronizer.swift +++ b/Sources/ZcashLightClientKit/CombineSynchronizer.swift @@ -35,19 +35,39 @@ public protocol CombineSynchronizer { func getUnifiedAddress(accountIndex: Int) -> SinglePublisher func getTransparentAddress(accountIndex: Int) -> SinglePublisher + @available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients. use `proposeTransfer` instead") func sendToAddress( spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, memo: Memo? ) -> SinglePublisher - + + @available(*, deprecated, message: "Upcoming SDK 2.1 will create multiple transactions at once for some recipients. use `proposeShielding:` instead") func shieldFunds( spendingKey: UnifiedSpendingKey, memo: Memo, shieldingThreshold: Zatoshi ) -> SinglePublisher + func proposeTransfer( + accountIndex: Int, + recipient: Recipient, + amount: Zatoshi, + memo: Memo? + ) -> SinglePublisher + + func proposeShielding( + accountIndex: Int, + shieldingThreshold: Zatoshi, + memo: Memo + ) -> SinglePublisher + + func proposefulfillingPaymentURI( + _ uri: String, + accountIndex: Int + ) -> SinglePublisher + var allTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var sentTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } var receivedTransactions: SinglePublisher<[ZcashTransaction.Overview], Never> { get } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 835a1f17..4cbafa76 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -225,16 +225,16 @@ public protocol Synchronizer: AnyObject { memo: Memo? ) async throws -> ZcashTransaction.Overview - /// Attempts to fulfill a [ZIP-321](https://zips.z.cash/zip-0321) payment URI using the given `UnifiedSpendingKey` + /// Attempts to propose fulfilling a [ZIP-321](https://zips.z.cash/zip-0321) payment URI using the given `accountIndex` /// - Parameter uri: a valid ZIP-321 payment URI - /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur. + /// - Parameter accountIndex: the account index that allows spends to occur. /// /// - NOTE: If `prepare()` hasn't already been called since creating of synchronizer instance or since the last wipe then this method throws /// `SynchronizerErrors.notPrepared`. - func fulfillPaymentURI( + func proposefulfillingPaymentURI( _ uri: String, - spendingKey: UnifiedSpendingKey - ) async throws -> ZcashTransaction.Overview + accountIndex: Int + ) async throws -> Proposal /// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`. /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds diff --git a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift index 5aca71b5..1811b12c 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift @@ -90,6 +90,18 @@ extension CombineSDKSynchronizer: CombineSynchronizer { } } + public func proposefulfillingPaymentURI( + _ uri: String, + accountIndex: Int + ) -> SinglePublisher { + AsyncToCombineGateway.executeThrowingAction() { + try await self.synchronizer.proposefulfillingPaymentURI( + uri, + accountIndex: accountIndex + ) + } + } + public func createProposedTransactions( proposal: Proposal, spendingKey: UnifiedSpendingKey diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 657572d4..8025d9e0 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -291,14 +291,29 @@ public class SDKSynchronizer: Synchronizer { ) async throws -> Proposal? { try throwIfUnprepared() - let proposal = try await transactionEncoder.proposeShielding( + return try await transactionEncoder.proposeShielding( accountIndex: accountIndex, shieldingThreshold: shieldingThreshold, memoBytes: memo.asMemoBytes(), transparentReceiver: transparentReceiver?.stringEncoded ) + } - return proposal + public func proposefulfillingPaymentURI( + _ uri: String, + accountIndex: Int + ) async throws -> Proposal { + do { + try throwIfUnprepared() + return try await transactionEncoder.proposeFulfillingPaymentFromURI( + uri, + accountIndex: accountIndex + ) + } catch ZcashError.rustCreateToAddress(let e) { + throw ZcashError.rustProposeTransferFromURI(e) + } catch { + throw error + } } public func createProposedTransactions( @@ -344,25 +359,6 @@ public class SDKSynchronizer: Synchronizer { } } - public func fulfillPaymentURI(_ uri: String, spendingKey: UnifiedSpendingKey) async throws -> ZcashTransaction.Overview { - do { - let transaction = try await transactionEncoder.createTransactionFromPaymentURI( - uri, - spendingKey: spendingKey - ) - - let encodedTransaction = try transaction.encodedTransaction() - - try await transactionEncoder.submit(transaction: encodedTransaction) - - return transaction - } catch ZcashError.rustCreateToAddress(let e) { - throw ZcashError.rustProposeTransferFromURI(e) - } catch { - throw error - } - } - public func sendToAddress( spendingKey: UnifiedSpendingKey, zatoshi: Zatoshi, diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift index 694a37a2..2deba5b4 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -2,7 +2,7 @@ // TransactionEncoder.swift // ZcashLightClientKit // -// Created by Francisco Gindre on 11/20/19. +// Created by Francisco Gindre on 2019-11-20. // import Foundation @@ -72,20 +72,20 @@ protocol TransactionEncoder { spendingKey: UnifiedSpendingKey ) async throws -> [ZcashTransaction.Overview] - /// Creates a transaction to fulfill a [ZIP-321](https://zips.z.cash/zip-0321), throwing an exception whenever things are missing. When the provided wallet implementation + /// Creates a transaction proposal to fulfill a [ZIP-321](https://zips.z.cash/zip-0321), throwing an exception whenever things are missing. When the provided wallet implementation /// doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using /// double-bangs for things). /// /// - Parameters: /// - Parameter uri: a valid ZIP-321 payment URI. - /// - Parameter spendingKey: a `UnifiedSpendingKey` containing the spending key + /// - Parameter accountIndex: the index of the account the proposal should be made from. /// - Throws: /// - `walletTransEncoderCreateTransactionMissingSaplingParams` if the sapling parameters aren't downloaded. /// - Some `ZcashError.rust*` if the creation of transaction fails. - func createTransactionFromPaymentURI( + func proposeFulfillingPaymentFromURI( _ uri: String, - spendingKey: UnifiedSpendingKey - ) async throws -> ZcashTransaction.Overview + accountIndex: Int + ) async throws -> Proposal /// submits a transaction to the Zcash peer-to-peer network. /// - Parameter transaction: a transaction overview diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index c28b51e1..d081d1d6 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -87,39 +87,15 @@ class WalletTransactionEncoder: TransactionEncoder { return Proposal(inner: proposal) } - func createTransactionFromPaymentURI( + func proposeFulfillingPaymentFromURI( _ uri: String, - spendingKey: UnifiedSpendingKey - ) async throws -> ZcashTransaction.Overview { - let txId = try await createSpendFromPaymentURI( - uri, - spendingKey: spendingKey - ) - - logger.debug("transaction id: \(txId)") - return try await repository.find(rawID: txId) - } - - func createSpendFromPaymentURI( - _ uri: String, - spendingKey: UnifiedSpendingKey - ) async throws -> Data { - guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else { - throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams - } - - // TODO: Expose the proposal in a way that enables querying its fee. + accountIndex: Int + ) async throws -> Proposal { let proposal = try await rustBackend.proposeTransferFromURI( uri, - account: Int32(spendingKey.account) + account: Int32(accountIndex) ) - - let txId = try await rustBackend.createProposedTransaction( - proposal: proposal, - usk: spendingKey - ) - - return txId + return Proposal(inner: proposal) } func createProposedTransactions( diff --git a/Tests/DarksideTests/PaymentURIFulfillmentTests.swift b/Tests/DarksideTests/PaymentURIFulfillmentTests.swift index d03f9e73..22f54394 100644 --- a/Tests/DarksideTests/PaymentURIFulfillmentTests.swift +++ b/Tests/DarksideTests/PaymentURIFulfillmentTests.swift @@ -101,7 +101,7 @@ class PaymentURIFulfillmentTests: ZcashTestCase { sleep(1) let sendExpectation = XCTestExpectation(description: "send expectation") - var pendingEntity: ZcashTransaction.Overview? + var proposal: ZcashTransaction.Overview? /* 2. send transaction to recipient address @@ -111,24 +111,38 @@ class PaymentURIFulfillmentTests: ZcashTestCase { let paymentURI = "zcash:\(Environment.testRecipientAddress)?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase" do { - let pendingTx = try await coordinator.synchronizer.fulfillPaymentURI( + let proposal = try await coordinator.synchronizer.proposefulfillingPaymentURI( paymentURI, - spendingKey: self.coordinator.spendingKey + accountIndex: 0 ) - pendingEntity = pendingTx + let transactions = try await coordinator.synchronizer.createProposedTransactions( + proposal: proposal, + spendingKey: coordinator.spendingKey + ) + + for try await tx in transactions { + switch tx { + case .grpcFailure(_, let error): + XCTFail("transaction failed to submit with error:\(error.localizedDescription)") + return + case .success(txId: let txId): + continue + case .submitFailure(txId: let txId, code: let code, description: let description): + XCTFail("transaction failed to submit with code: \(code) - description: \(description)") + return + case .notAttempted(txId: let txId): + XCTFail("transaction not attempted") + return + } + } sendExpectation.fulfill() } catch { await handleError(error) } - await fulfillment(of: [sendExpectation], timeout: 11) + await fulfillment(of: [sendExpectation], timeout: 13) - guard pendingEntity != nil else { - XCTFail("no pending transaction after sending") - try await coordinator.stop() - return - } /** 3. getIncomingTransaction @@ -304,13 +318,13 @@ class PaymentURIFulfillmentTests: ZcashTestCase { let paymentURI = "zcash:zecIsGreat17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a?amount=0.0002&memo=\(memo)&message=Thank%20you%20for%20your%20purchase&label=Your%20Purchase" do { - let _ = try await coordinator.synchronizer.fulfillPaymentURI( + let _ = try await coordinator.synchronizer.proposefulfillingPaymentURI( paymentURI, - spendingKey: self.coordinator.spendingKey + accountIndex: 0 ) XCTFail("`fulfillPaymentURI` should have failed") - } catch ZcashError.rustCreateToAddress { + } catch ZcashError.rustProposeTransferFromURI { XCTAssertTrue(true) } catch { XCTFail("Expected ZcashError.rustCreateToAddress but got \(error.localizedDescription)") diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 7c51d457..a6a7dcb3 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -1493,27 +1493,27 @@ class SynchronizerMock: Synchronizer { } } - // MARK: - fulfillPaymentURI + // MARK: - proposefulfillingPaymentURI - var fulfillPaymentURISpendingKeyThrowableError: Error? - var fulfillPaymentURISpendingKeyCallsCount = 0 - var fulfillPaymentURISpendingKeyCalled: Bool { - return fulfillPaymentURISpendingKeyCallsCount > 0 + var proposefulfillingPaymentURIAccountIndexThrowableError: Error? + var proposefulfillingPaymentURIAccountIndexCallsCount = 0 + var proposefulfillingPaymentURIAccountIndexCalled: Bool { + return proposefulfillingPaymentURIAccountIndexCallsCount > 0 } - var fulfillPaymentURISpendingKeyReceivedArguments: (uri: String, spendingKey: UnifiedSpendingKey)? - var fulfillPaymentURISpendingKeyReturnValue: ZcashTransaction.Overview! - var fulfillPaymentURISpendingKeyClosure: ((String, UnifiedSpendingKey) async throws -> ZcashTransaction.Overview)? + var proposefulfillingPaymentURIAccountIndexReceivedArguments: (uri: String, accountIndex: Int)? + var proposefulfillingPaymentURIAccountIndexReturnValue: Proposal! + var proposefulfillingPaymentURIAccountIndexClosure: ((String, Int) async throws -> Proposal)? - func fulfillPaymentURI(_ uri: String, spendingKey: UnifiedSpendingKey) async throws -> ZcashTransaction.Overview { - if let error = fulfillPaymentURISpendingKeyThrowableError { + func proposefulfillingPaymentURI(_ uri: String, accountIndex: Int) async throws -> Proposal { + if let error = proposefulfillingPaymentURIAccountIndexThrowableError { throw error } - fulfillPaymentURISpendingKeyCallsCount += 1 - fulfillPaymentURISpendingKeyReceivedArguments = (uri: uri, spendingKey: spendingKey) - if let closure = fulfillPaymentURISpendingKeyClosure { - return try await closure(uri, spendingKey) + proposefulfillingPaymentURIAccountIndexCallsCount += 1 + proposefulfillingPaymentURIAccountIndexReceivedArguments = (uri: uri, accountIndex: accountIndex) + if let closure = proposefulfillingPaymentURIAccountIndexClosure { + return try await closure(uri, accountIndex) } else { - return fulfillPaymentURISpendingKeyReturnValue + return proposefulfillingPaymentURIAccountIndexReturnValue } }