From 9b930391a46bec4b9dbfe1f3341543bee26bd2e5 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Tue, 31 May 2022 09:27:24 -0300 Subject: [PATCH 1/2] [#384] Adopt Type Safe Memos in the FFI and SDK Closes #384 This makes the SDK use the Memo and MemoBytes types across the FFI and the public interface of the SDKSynchronizer. Addresses the comments by @defuse on issue https://github.com/zcash/ZcashLightClientKit/issues/378. These changes depend on zcash-light-client-ffi repo changes managing memos as byte arrays instead of string encoding of memos. There are some compromises in terms of database representation of memos and their exposure in transaction representing entities because we wanted to avoid changing the database at this moment and be retro- compatible with earlier versions and maintain deprecated APIs for wallets to have time to adopt. Memo and MemoBytes are WIP and subject to change. we are exploring making Memo an opaque type based on a struct and drop the enum approach to make the interface less throwing. Fix: wrap new functions and deprecate old API. catch Memo errors update get_sent and get_received memo functions from rust welding point to branch in FFI repo Fix type Tests/DarksideTests/BalanceTests.swift Co-authored-by: Carter Jernigan PR suggestions. Inverted condition PR Suggestions + cleanup merge fixes Suggestions from PR to treat null bytes on TextMemo refactor unpaddedRawBytes function and add tests undo change in PendingTransactionSQLDAO Fix broken import on Swift Package for libzcashlc Fix libzcashlc url Fix travis.yml --- .travis.yml | 2 +- .../DAO/PendingTransactionDao.swift | 10 +- Sources/ZcashLightClientKit/Model/Memo.swift | 74 +++++- .../checkpoints/mainnet/1686871.json | 7 + .../Rust/ZcashRustBackend.swift | 75 ++++-- .../Rust/ZcashRustBackendWelding.swift | 22 +- .../Synchronizer/SDKSynchronizer.swift | 62 ++++- .../PersistentTransactionManager.swift | 18 +- .../Transaction/TransactionEncoder.swift | 55 ++++- .../Transaction/TransactionManager.swift | 2 +- .../WalletTransactionEncoder.swift | 18 +- Tests/DarksideTests/AdvancedReOrgTests.swift | 233 +++++++++--------- Tests/DarksideTests/BalanceTests.swift | 23 +- Tests/DarksideTests/NetworkUpgradeTests.swift | 10 +- .../PendingTransactionUpdatesTest.swift | 2 +- Tests/DarksideTests/RewindRescanTests.swift | 4 +- Tests/DarksideTests/ShieldFundsTests.swift | 2 +- Tests/DarksideTests/Z2TReceiveTests.swift | 2 +- Tests/OfflineTests/NullBytesTests.swift | 14 +- .../PendingTransactionRepositoryTests.swift | 13 +- Tests/OfflineTests/Zip302MemoTests.swift | 96 +++++++- Tests/TestUtils/Tests+Utils.swift | 1 + 22 files changed, 522 insertions(+), 223 deletions(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1686871.json diff --git a/.travis.yml b/.travis.yml index 20229f0e..5db0a059 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ os: osx osx_image: xcode13.4 xcode_project: ./Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj xcode_scheme: ZcashLightClientSample -xcode_destination: platform=iOS Simulator,OS=15.2,name=iPhone 8 +xcode_destination: platform=iOS Simulator,OS=15.5,name=iPhone 8 addons: homebrew: packages: diff --git a/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift b/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift index ea323a3f..adf6f8fd 100644 --- a/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift +++ b/Sources/ZcashLightClientKit/DAO/PendingTransactionDao.swift @@ -7,7 +7,8 @@ import Foundation import SQLite -struct PendingTransaction: PendingTransactionEntity, Codable { +struct PendingTransaction: PendingTransactionEntity, Decodable, Encodable { + enum CodingKeys: String, CodingKey { case toAddress = "to_address" case accountIndex = "account_index" @@ -57,7 +58,7 @@ struct PendingTransaction: PendingTransactionEntity, Codable { raw: entity.raw, id: entity.id, value: entity.value, - memo: entity.memo, + memo: entity.memo == nil ? Data(MemoBytes.empty().bytes) : entity.memo, rawTransactionId: entity.raw ) } @@ -144,8 +145,7 @@ struct PendingTransaction: PendingTransactionEntity, Codable { } extension PendingTransaction { - // TODO: Handle Memo - init(value: Zatoshi, toAddress: String, memo: String?, account index: Int) { + init(value: Zatoshi, toAddress: String, memo: MemoBytes, account index: Int) { self = PendingTransaction( toAddress: toAddress, accountIndex: index, @@ -160,7 +160,7 @@ extension PendingTransaction { raw: nil, id: nil, value: value, - memo: memo?.encodeAsZcashTransactionMemo(), + memo: Data(memo.bytes), rawTransactionId: nil ) } diff --git a/Sources/ZcashLightClientKit/Model/Memo.swift b/Sources/ZcashLightClientKit/Model/Memo.swift index 3db94f4e..ba646562 100644 --- a/Sources/ZcashLightClientKit/Model/Memo.swift +++ b/Sources/ZcashLightClientKit/Model/Memo.swift @@ -14,12 +14,12 @@ public enum Memo: Equatable { case arbitrary([UInt8]) /// Parses the given bytes as in ZIP-302 - public init?(bytes: [UInt8]) throws { + public init(bytes: [UInt8]) throws { self = try MemoBytes(bytes: bytes).intoMemo() } /// Converts these memo bytes into a ZIP-302 Memo - public init?(memoBytes: MemoBytes) throws { + public init(memoBytes: MemoBytes) throws { self = try memoBytes.intoMemo() } @@ -27,7 +27,7 @@ public enum Memo: Equatable { /// - Throws: /// - `MemoBytes.Errors.tooLong(length)` if the UTF-8 length /// of this string is greater than `MemoBytes.capacity` (512 bytes) - public init?(string: String) throws { + public init(string: String) throws { self = .text(try MemoText(String(string.utf8))) } } @@ -60,12 +60,14 @@ public struct MemoText: Equatable { public private(set) var string: String init(_ string: String) throws { - guard string.utf8.count <= MemoBytes.capacity else { - throw MemoBytes.Errors.tooLong(string.utf8.count) + let trimmedString = String(string.reversed().drop(while: { $0 == "\u{0}"}).reversed()) + + guard trimmedString.count == string.count else { + throw MemoBytes.Errors.endsWithNullBytes } - guard !string.containsCStringNullBytesBeforeStringEnding() else { - throw MemoBytes.Errors.invalidUTF8 + guard string.utf8.count <= MemoBytes.capacity else { + throw MemoBytes.Errors.tooLong(string.utf8.count) } self.string = string @@ -74,7 +76,11 @@ public struct MemoText: Equatable { public struct MemoBytes: Equatable { public enum Errors: Error { + /// Invalid UTF-8 Bytes where detected when attempting to create a Text Memo case invalidUTF8 + /// Trailing null-bytes were found when attempting to create a Text memo + case endsWithNullBytes + /// the resulting bytes provided are too long to be stored as a Memo in any of its forms. case tooLong(Int) } @@ -100,6 +106,18 @@ public struct MemoBytes: Equatable { self.bytes = rawBytes } + init(contiguousBytes: ContiguousArray) throws { + guard contiguousBytes.capacity <= Self.capacity else { throw Errors.tooLong(contiguousBytes.capacity) } + + var rawBytes = [UInt8](repeating: 0x0, count: Self.capacity) + + _ = contiguousBytes.withUnsafeBufferPointer { ptr in + memmove(&rawBytes[0], ptr.baseAddress, ptr.count) + } + + self.bytes = rawBytes + } + public static func empty() -> Self { try! Self(bytes: .emptyMemoBytes) } @@ -108,6 +126,8 @@ public struct MemoBytes: Equatable { extension MemoBytes.Errors: LocalizedError { var localizedDescription: String { switch self { + case .endsWithNullBytes: + return "MemoBytes.Errors.endsWithNullBytes: The UTF-8 bytes provided have trailing null-bytes." case .invalidUTF8: return "MemoBytes.Errors.invalidUTF8: Invalid UTF-8 byte found on memo bytes" case .tooLong(let length): @@ -161,12 +181,7 @@ public extension MemoBytes { extension MemoBytes { /// Returns raw bytes, excluding null padding func unpaddedRawBytes() -> [UInt8] { - guard let firstNullByte = self.bytes.enumerated() - .reversed() - .first(where: { $0.1 != 0 }) - .map({ $0.0 + 1 }) else { return [UInt8](bytes[0 ... 1]) } - - return [UInt8](bytes[0 ... firstNullByte]) + self.bytes.unpaddedRawBytes() } } @@ -176,6 +191,17 @@ extension Array where Element == UInt8 { emptyMemo[0] = 0xF6 return emptyMemo } + + func unpaddedRawBytes() -> [UInt8] { + guard let lastNullByte = self.enumerated() + .reversed() + .first(where: { $0.1 != 0 }) + .map({ $0.0 + 1 }) else { + return [UInt8](self[0 ..< 1]) + } + + return [UInt8](self[0 ..< lastNullByte]) + } } extension String { @@ -187,3 +213,25 @@ extension String { self = s } } + +extension Optional where WrappedType == String { + func intoMemo() throws -> Memo { + switch self { + case .none: + return .empty + case .some(let string): + return try Memo(string: string) + } + } +} + +extension Optional where WrappedType == Data { + func intoMemoBytes() throws -> MemoBytes { + switch self { + case .none: + return .empty() + case .some(let data): + return try .init(bytes: data.bytes) + } + } +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1686871.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1686871.json new file mode 100644 index 00000000..c23317ad --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1686871.json @@ -0,0 +1,7 @@ +{ + "network": "main", + "height": "1686871", + "hash": "0000000000443d2be0b66f0f2c03784cf267e50935562a23c10d9d3841343cd7", + "time": 1654001768, + "saplingTree": "01802ef6c171eca37050fbc6cdf8d9712a8c5e4650af1a31300c143e063a680e2100140000000001e684c306416b750c7b4883e145922c129e5919a658b02e6146d2da8d17a3ff42000001ae61a98ec05d976a41e46f0b7ae8dee5bda5ab490eea7b701153511a15be42590198c748a5e9516711db607e282f08013ba00b3e77bc66ec28702ca17893a5154b000139113a1e0f54d93c6180f2df7d16afbf7c320f6c7665cb10e1fc309878b0d716019a0214bcb1fd7a70e53166101992147512f3debb0fdce45ac625fffd64771010000134136b9f1f00c2e9d16dc7358ef920862511c8fc42fb7074cbc9016d8d4e8b4c015eddc191a81221b7900bbdcd8610e5df595e3cdc7fd5b3432e3825206ae35b05017eda713cd733ccc555123788692a1876f9ca292b0aa2ddf3f45ed2b47f027340000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009" +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 4037a63f..da50fdf1 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -359,25 +359,74 @@ class ZcashRustBackend: ZcashRustBackendWelding { return WalletBalance(verified: Zatoshi(verified), total: Zatoshi(total)) } + @available(*, deprecated, message: "This function will be deprecated soon. Use `getReceivedMemo(dbData:idNote:networkType)` instead") static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? { let dbData = dbData.osStr() guard let memoCStr = zcashlc_get_received_memo_as_utf8(dbData.0, dbData.1, idNote, networkType.networkId) else { return nil } - let memo = String(validatingUTF8: memoCStr) - zcashlc_string_free(memoCStr) - return memo + defer { + zcashlc_string_free(memoCStr) + } + + return String(validatingUTF8: memoCStr) } - - static func getSentMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? { + + @available(*, deprecated, message: "This function will be deprecated soon. Use `getSentMemo(dbData:idNote:networkType)` instead") + static func getSentMemoAsUTF8( + dbData: URL, + idNote: Int64, + networkType: NetworkType + ) -> String? { let dbData = dbData.osStr() guard let memoCStr = zcashlc_get_sent_memo_as_utf8(dbData.0, dbData.1, idNote, networkType.networkId) else { return nil } - let memo = String(validatingUTF8: memoCStr) - zcashlc_string_free(memoCStr) - return memo + defer { + zcashlc_string_free(memoCStr) + } + + return String(validatingUTF8: memoCStr) } + + static func getSentMemo( + dbData: URL, + idNote: Int64, + networkType: NetworkType + ) -> Memo? { + let dbData = dbData.osStr() + + var contiguousMemoBytes = ContiguousArray(MemoBytes.empty().bytes) + var success = false + + contiguousMemoBytes.withUnsafeMutableBytes{ memoBytePtr in + success = zcashlc_get_sent_memo(dbData.0, dbData.1, idNote, memoBytePtr.baseAddress, networkType.networkId) + } + + guard success else { return nil } + + return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() } + } + + static func getReceivedMemo( + dbData: URL, + idNote: Int64, + networkType: NetworkType + ) -> Memo? { + let dbData = dbData.osStr() + + var contiguousMemoBytes = ContiguousArray(MemoBytes.empty().bytes) + var success = false + + contiguousMemoBytes.withUnsafeMutableBufferPointer { memoBytePtr in + success = zcashlc_get_received_memo(dbData.0, dbData.1, idNote, memoBytePtr.baseAddress, networkType.networkId) + } + + guard success else { return nil } + + return (try? MemoBytes(contiguousBytes: contiguousMemoBytes)).flatMap { try? $0.intoMemo() } + } + static func validateCombinedChain(dbCache: URL, dbData: URL, networkType: NetworkType) -> Int32 { let dbCache = dbCache.osStr() @@ -420,13 +469,12 @@ class ZcashRustBackend: ZcashRustBackendWelding { extsk: String, to address: String, value: Int64, - memo: String?, + memo: MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: NetworkType ) -> Int64 { let dbData = dbData.osStr() - let memoBytes = memo ?? "" return zcashlc_create_to_address( dbData.0, @@ -435,7 +483,7 @@ class ZcashRustBackend: ZcashRustBackendWelding { [CChar](extsk.utf8CString), [CChar](address.utf8CString), value, - [CChar](memoBytes.utf8CString), + memo.bytes, spendParamsPath, UInt(spendParamsPath.lengthOfBytes(using: .utf8)), outputParamsPath, @@ -450,20 +498,19 @@ class ZcashRustBackend: ZcashRustBackendWelding { dbData: URL, account: Int32, xprv: String, - memo: String?, + memo: MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: NetworkType ) -> Int64 { let dbData = dbData.osStr() - let memoBytes = memo ?? "" return zcashlc_shield_funds( dbData.0, dbData.1, account, [CChar](xprv.utf8CString), - [CChar](memoBytes.utf8CString), + memo.bytes, spendParamsPath, UInt(spendParamsPath.lengthOfBytes(using: .utf8)), outputParamsPath, diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index bede117b..7e1f813f 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -160,7 +160,16 @@ protocol ZcashRustBackendWelding { - dbData: location of the data db file - idNote: note_id of note where the memo is located */ + @available(*, deprecated, message: "This function will be deprecated soon. Use `getReceivedMemo(dbData:idNote:networkType)` instead") static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? + + /** + get received memo from note + - Parameters: + - dbData: location of the data db file + - idNote: note_id of note where the memo is located + */ + static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: NetworkType) -> Memo? /** get sent memo from note @@ -168,7 +177,16 @@ protocol ZcashRustBackendWelding { - dbData: location of the data db file - idNote: note_id of note where the memo is located */ + @available(*, deprecated, message: "This function will be deprecated soon. Use `getSentMemo(dbData:idNote:networkType)` instead") static func getSentMemoAsUTF8(dbData: URL, idNote: Int64, networkType: NetworkType) -> String? + + /** + get sent memo from note + - Parameters: + - dbData: location of the data db file + - idNote: note_id of note where the memo is located + */ + static func getSentMemo(dbData: URL, idNote: Int64, networkType: NetworkType) -> Memo? /** Checks that the scanned blocks in the data database, when combined with the recent @@ -301,7 +319,7 @@ protocol ZcashRustBackendWelding { extsk: String, to address: String, value: Int64, - memo: String?, + memo: MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: NetworkType @@ -324,7 +342,7 @@ protocol ZcashRustBackendWelding { dbData: URL, account: Int32, xprv: String, - memo: String?, + memo: MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: NetworkType diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index a7dd8bfe..a4e73935 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -458,7 +458,7 @@ public class SDKSynchronizer: Synchronizer { // MARK: Synchronizer methods - @available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead") + @available(*, deprecated, message: "This function will be removed soon, use the one receiving a `Zatoshi` value instead") public func sendToAddress( spendingKey: String, zatoshi: Int64, @@ -482,6 +482,7 @@ public class SDKSynchronizer: Synchronizer { } // swiftlint:disable:next function_parameter_count + @available(*, deprecated, message: "use Memo type instead of Optional") public func sendToAddress( spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, @@ -489,6 +490,29 @@ public class SDKSynchronizer: Synchronizer { memo: String?, from accountIndex: Int, resultBlock: @escaping (Result) -> Void + ) { + do { + self.sendToAddress( + spendingKey: spendingKey, + zatoshi: zatoshi, + toAddress: toAddress, + memo: try memo.intoMemo(), + from: accountIndex, + resultBlock: resultBlock + ) + } catch { + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) + } + } + + // swiftlint:disable:next function_parameter_count + public func sendToAddress( + spendingKey: String, + zatoshi: Zatoshi, + toAddress: String, + memo: Memo, + from accountIndex: Int, + resultBlock: @escaping (Result) -> Void ) { initializer.downloadParametersIfNeeded { downloadResult in DispatchQueue.main.async { [weak self] in @@ -508,16 +532,16 @@ public class SDKSynchronizer: Synchronizer { } } } - + public func shieldFunds( - transparentAccountPrivateKey: TransparentAccountPrivKey, - memo: String?, + transparentSecretKey: String, + memo: Memo, from accountIndex: Int, resultBlock: @escaping (Result) -> Void ) { // let's see if there are funds to shield let derivationTool = DerivationTool(networkType: self.network.networkType) - + do { let tAddr = try derivationTool.deriveTransparentAddressFromAccountPrivateKey(transparentAccountPrivateKey, index: 0) @@ -562,8 +586,26 @@ public class SDKSynchronizer: Synchronizer { } } } catch { - resultBlock(.failure(error)) - return + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) + } + } + + @available(*, deprecated, message: "use shieldFunds with a Memo type") + public func shieldFunds( + transparentSecretKey: String, + memo: String?, + from accountIndex: Int, + resultBlock: @escaping (Result) -> Void + ) { + do { + shieldFunds( + transparentSecretKey: transparentSecretKey, + memo: try memo.intoMemo(), + from: accountIndex, + resultBlock: resultBlock + ) + } catch { + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } @@ -572,7 +614,7 @@ public class SDKSynchronizer: Synchronizer { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, toAddress: String, - memo: String?, + memo: Memo, from accountIndex: Int, resultBlock: @escaping (Result) -> Void ) { @@ -580,7 +622,7 @@ public class SDKSynchronizer: Synchronizer { let spend = try transactionManager.initSpend( zatoshi: zatoshi, toAddress: toAddress, - memo: memo, + memo: memo.asMemoBytes(), from: accountIndex ) @@ -604,7 +646,7 @@ public class SDKSynchronizer: Synchronizer { } } } catch { - resultBlock(.failure(error)) + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } diff --git a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift index 54d940fb..e3f328a9 100644 --- a/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/PersistentTransactionManager.swift @@ -19,6 +19,7 @@ enum TransactionManagerError: Error { } class PersistentTransactionManager: OutboundTransactionManager { + var repository: PendingTransactionRepository var encoder: TransactionEncoder var service: LightWalletService @@ -41,7 +42,7 @@ class PersistentTransactionManager: OutboundTransactionManager { func initSpend( zatoshi: Zatoshi, toAddress: String, - memo: String?, + memo: MemoBytes, from accountIndex: Int ) throws -> PendingTransactionEntity { guard let insertedTx = try repository.find( @@ -75,7 +76,7 @@ class PersistentTransactionManager: OutboundTransactionManager { do { let encodedTransaction = try self.encoder.createShieldingTransaction( tAccountPrivateKey: xprv, - memo: pendingTransaction.memo?.asZcashTransactionMemo(), + memoBytes: try pendingTransaction.memo.intoMemoBytes(), from: pendingTransaction.accountIndex ) let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) @@ -94,6 +95,14 @@ class PersistentTransactionManager: OutboundTransactionManager { DispatchQueue.main.async { result(.failure(TransactionManagerError.updateFailed(pendingTransaction))) } + } catch MemoBytes.Errors.invalidUTF8 { + DispatchQueue.main.async { + result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo contains invalid UTF-8 bytes"))) + } + } catch MemoBytes.Errors.tooLong(let length) { + DispatchQueue.main.async { + result(.failure(TransactionManagerError.shieldingEncodingFailed(pendingTransaction, reason: "Memo is too long. expected 512 bytes, received \(length)"))) + } } catch { DispatchQueue.main.async { result(.failure(error)) @@ -109,14 +118,17 @@ class PersistentTransactionManager: OutboundTransactionManager { ) { queue.async { [weak self] in guard let self = self else { return } + do { + let encodedTransaction = try self.encoder.createTransaction( spendingKey: spendingKey, zatoshi: pendingTransaction.value, to: pendingTransaction.toAddress, - memo: pendingTransaction.memo?.asZcashTransactionMemo(), + memoBytes: try pendingTransaction.memo.intoMemoBytes(), from: pendingTransaction.accountIndex ) + let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction) var pending = pendingTransaction diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift index e71f99a5..4435b313 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -28,7 +28,7 @@ protocol TransactionEncoder { /// - Parameter spendingKey: a `SaplingExtendedSpendingKey` containing the spending key /// - Parameter zatoshi: the amount to send in `Zatoshi` /// - Parameter to: string containing the recipient address - /// - Parameter memo: string containing the memo (optional) + /// - Parameter memoBytes: MemoBytes for this transaction /// - Parameter accountIndex: index of the account that will be used to send the funds /// /// - Throws: a TransactionEncoderError @@ -36,7 +36,7 @@ protocol TransactionEncoder { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, to address: String, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int ) throws -> EncodedTransaction @@ -80,11 +80,50 @@ protocol TransactionEncoder { from accountIndex: Int ) throws -> EncodedTransaction - /** - Fetch the Transaction Entity from the encoded representation - - Parameter encodedTransaction: The encoded transaction to expand - - Returns: a TransactionEntity based on the given Encoded Transaction - - Throws: a TransactionEncoderError - */ +/// Creates a transaction, 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). +/// Non-blocking + +/// - Parameters: +/// - Parameter spendingKey: a string containing the spending key +/// - Parameter zatoshi: the amount to send in zatoshis +/// - Parameter to: string containing the recipient address +/// - Parameter memoBytes: MemoBytes for this transaction +/// - Parameter accountIndex: index of the account that will be used to send the funds +/// - Parameter result: a non escaping closure that receives a Result containing either an ///EncodedTransaction or a TransactionEncoderError + func createTransaction( + spendingKey: String, + zatoshi: Int, + to address: String, + memoBytes: MemoBytes, + from accountIndex: Int, + result: @escaping TransactionEncoderResultBlock + )// swiftlint:disable function_parameter_count + +/// Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB. +/// 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). +/// +/// Blocking +/// +/// - Parameters: +/// - Parameter spendingKey: a string containing the spending key +/// - Parameter tSecretKey: transparent secret key to spend the UTXOs +/// - Parameter Parameter memoBytes: MemoBytes for this transaction +/// - Parameter accountIndex: index of the account that will be used to send the funds +/// +/// - Throws: a TransactionEncoderError + func createShieldingTransaction( + spendingKey: String, + tSecretKey: String, + memoBytes: MemoBytes, + from accountIndex: Int + ) throws -> EncodedTransaction + + ///Fetch the Transaction Entity from the encoded representation + /// - Parameter encodedTransaction: The encoded transaction to expand + /// - Returns: a TransactionEntity based on the given Encoded Transaction + /// - Throws: a TransactionEncoderError func expandEncodedTransaction(_ encodedTransaction: EncodedTransaction) throws -> TransactionEntity } diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift index a9cc4277..469a6707 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionManager.swift @@ -17,7 +17,7 @@ protocol OutboundTransactionManager { func initSpend( zatoshi: Zatoshi, toAddress: String, - memo: String?, + memo: MemoBytes, from accountIndex: Int ) throws -> PendingTransactionEntity diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 9f020410..651b10c3 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -53,14 +53,14 @@ class WalletTransactionEncoder: TransactionEncoder { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, to address: String, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int ) throws -> EncodedTransaction { let txId = try createSpend( spendingKey: spendingKey, zatoshi: zatoshi, to: address, - memo: memo, + memoBytes: memoBytes, from: accountIndex ) @@ -83,7 +83,7 @@ class WalletTransactionEncoder: TransactionEncoder { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, to address: String, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int, result: @escaping TransactionEncoderResultBlock ) { @@ -96,7 +96,7 @@ class WalletTransactionEncoder: TransactionEncoder { spendingKey: spendingKey, zatoshi: zatoshi, to: address, - memo: memo, + memoBytes: memoBytes, from: accountIndex ) ) @@ -111,7 +111,7 @@ class WalletTransactionEncoder: TransactionEncoder { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, to address: String, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int ) throws -> Int { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { @@ -124,7 +124,7 @@ class WalletTransactionEncoder: TransactionEncoder { extsk: spendingKey.stringEncoded, to: address, value: zatoshi.amount, - memo: memo, + memo: memoBytes, spendParamsPath: self.spendParamsURL.path, outputParamsPath: self.outputParamsURL.path, networkType: networkType @@ -139,12 +139,12 @@ class WalletTransactionEncoder: TransactionEncoder { func createShieldingTransaction( tAccountPrivateKey: TransparentAccountPrivKey, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int ) throws -> EncodedTransaction { let txId = try createShieldingSpend( xprv: tAccountPrivateKey.encoding, - memo: memo, + memo: memoBytes, accountIndex: accountIndex ) @@ -162,7 +162,7 @@ class WalletTransactionEncoder: TransactionEncoder { } } - func createShieldingSpend(xprv: String, memo: String?, accountIndex: Int) throws -> Int { + func createShieldingSpend(xprv: String, memo: MemoBytes, accountIndex: Int) throws -> Int { guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else { throw TransactionEncoderError.missingParams } diff --git a/Tests/DarksideTests/AdvancedReOrgTests.swift b/Tests/DarksideTests/AdvancedReOrgTests.swift index a2d8e11e..d5eccce9 100644 --- a/Tests/DarksideTests/AdvancedReOrgTests.swift +++ b/Tests/DarksideTests/AdvancedReOrgTests.swift @@ -66,22 +66,21 @@ class AdvancedReOrgTests: XCTestCase { reorgExpectation.fulfill() } - /* - pre-condition: know balances before tx at received_Tx_height arrives - 1. Setup w/ default dataset - 2. applyStaged(received_Tx_height) - 3. sync up to received_Tx_height - 3a. verify that balance is previous balance + tx amount - 4. get that transaction hex encoded data - 5. stage 5 empty blocks w/heights received_Tx_height to received_Tx_height + 3 - 6. stage tx at received_Tx_height + 3 - 6a. applyheight(received_Tx_height + 1) - 7. sync to received_Tx_height + 1 - 8. assert that reorg happened at received_Tx_height - 9. verify that balance equals initial balance - 10. sync up to received_Tx_height + 3 - 11. verify that balance equals initial balance + tx amount - */ + + /// pre-condition: know balances before tx at received_Tx_height arrives + /// 1. Setup w/ default dataset + /// 2. applyStaged(received_Tx_height) + /// 3. sync up to received_Tx_height + /// 3a. verify that balance is previous balance + tx amount + /// 4. get that transaction hex encoded data + /// 5. stage 5 empty blocks w/heights received_Tx_height to received_Tx_height + 3 + /// 6. stage tx at received_Tx_height + 3 + /// 6a. applyheight(received_Tx_height + 1) + /// 7. sync to received_Tx_height + 1 + /// 8. assert that reorg happened at received_Tx_height + /// 9. verify that balance equals initial balance + /// 10. sync up to received_Tx_height + 3 + /// 11. verify that balance equals initial balance + tx amount func testReOrgChangesInboundTxMinedHeight() throws { hookToReOrgNotification() try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) @@ -242,32 +241,31 @@ class AdvancedReOrgTests: XCTestCase { XCTAssertEqual(initialTotalBalance + receivedTx.value, finalReorgTxTotalBalance) } - /** - An outbound, unconfirmed transaction in a specific block changes height in the event of a reorg - - - The wallet handles this change, reflects it appropriately in local storage, and funds remain spendable post confirmation. - - Pre-conditions: - - Wallet has spendable funds - - 1. Setup w/ default dataset - 2. applyStaged(received_Tx_height) - 3. sync up to received_Tx_height - 4. create transaction - 5. stage 10 empty blocks - 6. submit tx at sentTxHeight - 6a. getIncomingTx - 6b. stageTransaction(sentTx, sentTxHeight) - 6c. applyheight(sentTxHeight + 1 ) - 7. sync to sentTxHeight + 2 - 8. stage sentTx and otherTx at sentTxheight - 9. applyStaged(sentTx + 2) - 10. sync up to received_Tx_height + 2 - 11. verify that the sent tx is mined and balance is correct - 12. applyStaged(sentTx + 10) - 13. verify that there's no more pending transaction - */ + + /// An outbound, unconfirmed transaction in a specific block changes height in the event of a reorg + /// + /// + /// The wallet handles this change, reflects it appropriately in local storage, and funds remain spendable post confirmation. + /// + /// Pre-conditions: + /// - Wallet has spendable funds + /// + /// 1. Setup w/ default dataset + /// 2. applyStaged(received_Tx_height) + /// 3. sync up to received_Tx_height + /// 4. create transaction + /// 5. stage 10 empty blocks + /// 6. submit tx at sentTxHeight + /// a. getIncomingTx + /// b. stageTransaction(sentTx, sentTxHeight) + /// c. applyheight(sentTxHeight + 1 ) + /// 7. sync to sentTxHeight + 2 + /// 8. stage sentTx and otherTx at sentTxheight + /// 9. applyStaged(sentTx + 2) + /// 10. sync up to received_Tx_height + 2 + /// 11. verify that the sent tx is mined and balance is correct + /// 12. applyStaged(sentTx + 10) + /// 13. verify that there's no more pending transaction func testReorgChangesOutboundTxIndex() throws { try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service, branchID: branchID, chainName: chainName) let receivedTxHeight: BlockHeight = 663188 @@ -303,7 +301,7 @@ class AdvancedReOrgTests: XCTestCase { spendingKey: coordinator.spendingKeys!.first!, zatoshi: sendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test transaction", + memo: try Memo(string: "test transaction"), from: 0 ) { result in switch result { @@ -522,17 +520,16 @@ class AdvancedReOrgTests: XCTestCase { XCTAssertEqual(afterReOrgVerifiedBalance, initialVerifiedBalance) } - /** - Steps: - 1. sync up to an incoming transaction (incomingTxHeight + 1) - 1a. save balances - 2. stage 4 blocks from incomingTxHeight - 1 with different nonce - 3. stage otherTx at incomingTxHeight - 4. stage incomingTx at incomingTxHeight - 5. applyHeight(incomingHeight + 3) - 6. sync to latest height - 7. check that balances still match - */ + + /// Steps: + /// 1. sync up to an incoming transaction (incomingTxHeight + 1) + /// 1a. save balances + /// 2. stage 4 blocks from incomingTxHeight - 1 with different nonce + /// 3. stage otherTx at incomingTxHeight + /// 4. stage incomingTx at incomingTxHeight + /// 5. applyHeight(incomingHeight + 3) + /// 6. sync to latest height + /// 7. check that balances still match func testReOrgChangesInboundTxIndexInBlock() throws { try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) @@ -649,33 +646,30 @@ class AdvancedReOrgTests: XCTestCase { XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), initialBalance) XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), initialVerifiedBalance) } - - /** - A Re Org occurs and changes the height of an outbound transaction - Pre-condition: Wallet has funds - - Steps: - 1. create fake chain - 1a. sync to latest height - 2. send transaction to recipient address - 3. getIncomingTransaction - 4. stage transaction at sentTxHeight - 5. applyHeight(sentTxHeight) - 6. sync to latest height - 6a. verify that there's a pending transaction with a mined height of sentTxHeight - 7. stage 15 blocks from sentTxHeight - 7. a stage sent tx to sentTxHeight + 2 - 8. applyHeight(sentTxHeight + 1) to cause a 1 block reorg - 9. sync to latest height - 10. verify that there's a pending transaction with -1 mined height - 11. applyHeight(sentTxHeight + 2) - 11a. sync to latest height - 12. verify that there's a pending transaction with a mined height of sentTxHeight + 2 - 13. apply height(sentTxHeight + 15) - 14. sync to latest height - 15. verify that there's no pending transaction and that the tx is displayed on the sentTransactions collection - - */ + + /// A Re Org occurs and changes the height of an outbound transaction + /// Pre-condition: Wallet has funds + /// + /// Steps: + /// 1. create fake chain + /// 1a. sync to latest height + /// 2. send transaction to recipient address + /// 3. getIncomingTransaction + /// 4. stage transaction at sentTxHeight + /// 5. applyHeight(sentTxHeight) + /// 6. sync to latest height + /// 6a. verify that there's a pending transaction with a mined height of sentTxHeight + /// 7. stage 15 blocks from sentTxHeight + /// 7. a stage sent tx to sentTxHeight + 2 + /// 8. applyHeight(sentTxHeight + 1) to cause a 1 block reorg + /// 9. sync to latest height + /// 10. verify that there's a pending transaction with -1 mined height + /// 11. applyHeight(sentTxHeight + 2) + /// 11a. sync to latest height + /// 12. verify that there's a pending transaction with a mined height of sentTxHeight + 2 + /// 13. apply height(sentTxHeight + 15) + /// 14. sync to latest height + /// 15. verify that there's no pending transaction and that the tx is displayed on the sentTransactions collection func testReOrgChangesOutboundTxMinedHeight() throws { hookToReOrgNotification() @@ -710,7 +704,7 @@ class AdvancedReOrgTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: Zatoshi(20000), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -889,21 +883,20 @@ class AdvancedReOrgTests: XCTestCase { ) } - /** - Uses the zcash-hackworks data set. - A Re Org occurs at 663195, and sweeps an Inbound Tx that appears later on the chain. - Steps: - 1. reset dlwd - 2. load blocks from txHeightReOrgBefore - 3. applyStaged(663195) - 4. sync to latest height - 5. get balances - 6. load blocks from dataset txHeightReOrgBefore - 7. apply stage 663200 - 8. sync to latest height - 9. verify that the balance is equal to the one before the reorg - */ + /// Uses the zcash-hackworks data set. + + /// A Re Org occurs at 663195, and sweeps an Inbound Tx that appears later on the chain. + /// Steps: + /// 1. reset dlwd + /// 2. load blocks from txHeightReOrgBefore + /// 3. applyStaged(663195) + /// 4. sync to latest height + /// 5. get balances + /// 6. load blocks from dataset txHeightReOrgBefore + /// 7. apply stage 663200 + /// 8. sync to latest height + /// 9. verify that the balance is equal to the one before the reorg func testReOrgChangesInboundMinedHeight() throws { try coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) sleep(2) @@ -950,16 +943,14 @@ class AdvancedReOrgTests: XCTestCase { XCTAssert(afterReOrgTxHeight > initialTxHeight) } - /** - Re Org removes incoming transaction and is never mined - Steps: - 1. sync prior to incomingTxHeight - 1 to get balances there - 2. sync to latest height - 3. cause reorg - 4. sync to latest height - 5. verify that reorg Happened at reorgHeight - 6. verify that balances match initial balances - */ + /// Re Org removes incoming transaction and is never mined + /// Steps: + /// 1. sync prior to incomingTxHeight - 1 to get balances there + /// 2. sync to latest height + /// 3. cause reorg + /// 4. sync to latest height + /// 5. verify that reorg Happened at reorgHeight + /// 6. verify that balances match initial balances func testReOrgRemovesIncomingTxForever() throws { hookToReOrgNotification() try coordinator.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName) @@ -1019,21 +1010,19 @@ class AdvancedReOrgTests: XCTestCase { XCTAssertEqual(initialTotalBalance, coordinator.synchronizer.initializer.getBalance()) } - /** - Transaction was included in a block, and then is not included in a block after a reorg, and expires. - Steps: - 1. create fake chain - 1a. sync to latest height - 2. send transaction to recipient address - 3. getIncomingTransaction - 4. stage transaction at sentTxHeight - 5. applyHeight(sentTxHeight) - 6. sync to latest height - 6a. verify that there's a pending transaction with a mined height of sentTxHeight - 7. stage 15 blocks from sentTxHeigth to cause a reorg - 8. sync to latest height - 9. verify that there's an expired transaction as a pending transaction - */ + /// Transaction was included in a block, and then is not included in a block after a reorg, and expires. + /// Steps: + /// 1. create fake chain + /// 1a. sync to latest height + /// 2. send transaction to recipient address + /// 3. getIncomingTransaction + /// 4. stage transaction at sentTxHeight + /// 5. applyHeight(sentTxHeight) + /// 6. sync to latest height + /// 6a. verify that there's a pending transaction with a mined height of sentTxHeight + /// 7. stage 15 blocks from sentTxHeigth to cause a reorg + /// 8. sync to latest height + /// 9. verify that there's an expired transaction as a pending transaction func testReOrgRemovesOutboundTxAndIsNeverMined() throws { hookToReOrgNotification() @@ -1070,7 +1059,7 @@ class AdvancedReOrgTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: Zatoshi(20000), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try! Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { diff --git a/Tests/DarksideTests/BalanceTests.swift b/Tests/DarksideTests/BalanceTests.swift index 2c0db15e..0a29f900 100644 --- a/Tests/DarksideTests/BalanceTests.swift +++ b/Tests/DarksideTests/BalanceTests.swift @@ -83,7 +83,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalance, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -227,7 +227,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalanceMinusOne, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + memo: try Memo(string: "\(self.description) \(Date().description)"), from: 0, resultBlock: { result in switch result { @@ -368,7 +368,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalanceMinusOne, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + memo: try Memo(string: "test send \(self.description) \(Date().description)"), from: 0, resultBlock: { result in switch result { @@ -510,8 +510,8 @@ class BalanceTests: XCTestCase { coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, zatoshi: sendAmount, - toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + toAddress: testRecipientAddress, + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -682,7 +682,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: sendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + memo: try Memo(string: "test send \(self.description) \(Date().description)"), from: 0, resultBlock: { result in switch result { @@ -831,7 +831,7 @@ class BalanceTests: XCTestCase { /* Send */ - let memo = "shielding is fun!" + let memo = try Memo(string: "shielding is fun!") var pendingTx: PendingTransactionEntity? coordinator.synchronizer.sendToAddress( spendingKey: spendingKeys, @@ -892,7 +892,12 @@ class BalanceTests: XCTestCase { */ XCTAssertEqual(confirmedTx.value, self.sendAmount) XCTAssertEqual(confirmedTx.toAddress, self.testRecipientAddress) - XCTAssertEqual(confirmedTx.memo?.asZcashTransactionMemo(), memo) + do { + let confirmedMemo = try confirmedTx.memo.intoMemoBytes().intoMemo() + XCTAssertEqual(confirmedMemo, memo) + } catch { + XCTFail("failed retrieving memo from confirmed transaction. Error: \(error.localizedDescription)") + } guard let transactionId = confirmedTx.rawTransactionId else { XCTFail("no raw transaction id") @@ -1009,7 +1014,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: sendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description)", + memo: try Memo(string: "test send \(self.description)"), from: 0, resultBlock: { result in switch result { diff --git a/Tests/DarksideTests/NetworkUpgradeTests.swift b/Tests/DarksideTests/NetworkUpgradeTests.swift index af87fc3b..fc72f8c3 100644 --- a/Tests/DarksideTests/NetworkUpgradeTests.swift +++ b/Tests/DarksideTests/NetworkUpgradeTests.swift @@ -83,7 +83,7 @@ class NetworkUpgradeTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: spendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -172,7 +172,7 @@ class NetworkUpgradeTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: spendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -241,7 +241,7 @@ class NetworkUpgradeTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: spendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -342,7 +342,7 @@ class NetworkUpgradeTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: spendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -454,7 +454,7 @@ class NetworkUpgradeTests: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: spendAmount, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { diff --git a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift index b4fa0e78..ff5b9490 100644 --- a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift +++ b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift @@ -98,7 +98,7 @@ class PendingTransactionUpdatesTest: XCTestCase { spendingKey: self.coordinator.spendingKeys!.first!, zatoshi: Zatoshi(20000), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "this is a test", + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { diff --git a/Tests/DarksideTests/RewindRescanTests.swift b/Tests/DarksideTests/RewindRescanTests.swift index c64fe284..cf00ee7f 100644 --- a/Tests/DarksideTests/RewindRescanTests.swift +++ b/Tests/DarksideTests/RewindRescanTests.swift @@ -170,7 +170,7 @@ class RewindRescanTests: XCTestCase { spendingKey: coordinator.spendingKey, zatoshi: Zatoshi(1000), toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: nil, + memo: .empty, from: 0 ) { result in sendExpectation.fulfill() @@ -271,7 +271,7 @@ class RewindRescanTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalance, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test send \(self.description) \(Date().description)", + memo: try Memo("test send \(self.description) \(Date().description)"), from: 0 ) { result in switch result { diff --git a/Tests/DarksideTests/ShieldFundsTests.swift b/Tests/DarksideTests/ShieldFundsTests.swift index 02e0e6cc..342ff2d5 100644 --- a/Tests/DarksideTests/ShieldFundsTests.swift +++ b/Tests/DarksideTests/ShieldFundsTests.swift @@ -210,7 +210,7 @@ class ShieldFundsTests: XCTestCase { // shield the funds coordinator.synchronizer.shieldFunds( transparentAccountPrivateKey: transparentAccountPrivateKey, - memo: "shield funds", + memo: try Memo(string: "shield funds"), from: 0 ) { result in switch result { diff --git a/Tests/DarksideTests/Z2TReceiveTests.swift b/Tests/DarksideTests/Z2TReceiveTests.swift index 6f746a4c..3dcfdf82 100644 --- a/Tests/DarksideTests/Z2TReceiveTests.swift +++ b/Tests/DarksideTests/Z2TReceiveTests.swift @@ -105,7 +105,7 @@ class Z2TReceiveTests: XCTestCase { spendingKey: coordinator.spendingKeys!.first!, zatoshi: sendAmount, toAddress: try! Recipient(testRecipientAddress, network: self.network.networkType), - memo: "test transaction", + memo: try Memo(string: "test transaction"), from: 0 ) { result in switch result { diff --git a/Tests/OfflineTests/NullBytesTests.swift b/Tests/OfflineTests/NullBytesTests.swift index 3b751a35..b6111398 100644 --- a/Tests/OfflineTests/NullBytesTests.swift +++ b/Tests/OfflineTests/NullBytesTests.swift @@ -122,10 +122,20 @@ class NullBytesTests: XCTestCase { XCTAssertFalse(validZaddr.containsCStringNullBytesBeforeStringEnding()) XCTAssertTrue( - "zs1gqtfu59z20s\09t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp" + "zs1gqtfu59z20s\u{0}9t20mxlxj86zpw6p69l0ev98uxrmlykf2nchj2dw8ny5e0l22kwmld2afc37gkfp" .containsCStringNullBytesBeforeStringEnding() ) - XCTAssertTrue("\0".containsCStringNullBytesBeforeStringEnding()) + XCTAssertTrue("\u{0}".containsCStringNullBytesBeforeStringEnding()) XCTAssertFalse("".containsCStringNullBytesBeforeStringEnding()) } + + func testTrimTrailingNullBytes() throws { + let nullTrailedString = "This Is a memo with text and trailing null bytes\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}" + + let nonNullTrailedString = "This Is a memo with text and trailing null bytes" + + let trimmedString = String(nullTrailedString.reversed().drop(while: { $0 == "\u{0}"}).reversed()) + + XCTAssertEqual(trimmedString, nonNullTrailedString) + } } diff --git a/Tests/OfflineTests/PendingTransactionRepositoryTests.swift b/Tests/OfflineTests/PendingTransactionRepositoryTests.swift index f3b864e7..bcab55b9 100644 --- a/Tests/OfflineTests/PendingTransactionRepositoryTests.swift +++ b/Tests/OfflineTests/PendingTransactionRepositoryTests.swift @@ -157,8 +157,8 @@ class PendingTransactionRepositoryTests: XCTestCase { XCTAssertEqual(updatedTransaction.toAddress, stored!.toAddress) } - func createAndStoreMockedTransaction() -> PendingTransactionEntity { - var transaction = mockTransaction() + func createAndStoreMockedTransaction(with value: Zatoshi = Zatoshi(1000)) -> PendingTransactionEntity { + var transaction = mockTransaction(with: value) var id: Int? XCTAssertNoThrow(try { id = try pendingRepository.create(transaction) }()) @@ -173,12 +173,7 @@ class PendingTransactionRepositoryTests: XCTestCase { } } - private func mockTransaction() -> PendingTransactionEntity { - PendingTransaction( - value: Zatoshi(Int64.random(in: 1 ... 1_000_000)), - toAddress: recipientAddress, - memo: nil, - account: 0 - ) + private func mockTransaction(with value: Zatoshi = Zatoshi(1000)) -> PendingTransactionEntity { + PendingTransaction(value: value, toAddress: recipientAddress, memo: .empty(), account: 0) } } diff --git a/Tests/OfflineTests/Zip302MemoTests.swift b/Tests/OfflineTests/Zip302MemoTests.swift index 90d29a88..ce7e4c2a 100644 --- a/Tests/OfflineTests/Zip302MemoTests.swift +++ b/Tests/OfflineTests/Zip302MemoTests.swift @@ -105,10 +105,7 @@ class Zip302MemoTests: XCTestCase { func testItCreatesAMemoFromAValidAndShortEnoughText() throws { let almostTooLongString = "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo but it's just short enough" - guard let memo = try Memo(string: almostTooLongString) else { - XCTFail("Expected `Memo` or `Error` thrown but found `nil`") - return - } + let memo = try Memo(string: almostTooLongString) let memoBytes = try memo.asMemoBytes() @@ -128,12 +125,101 @@ class Zip302MemoTests: XCTestCase { switch error { case .tooLong(let count): XCTAssertEqual(count, 515) - case .invalidUTF8: XCTFail("Expected `.tooLong(515) but found `.invalidUTF8`") + case .endsWithNullBytes: + XCTFail("Expected `.tooLong(515) but found `.endsWithNullBytes`") } } } + + func testInitMemoBytesFromContiguousBytes() throws { + let contiguousEmptyBytes = ContiguousArray(Zip302MemoTests.emptyMemoBytes) + + let emptyMemoBytes = try MemoBytes(contiguousBytes: contiguousEmptyBytes) + + XCTAssertEqual(emptyMemoBytes.bytes, .emptyMemoBytes) + + let contiguousTextMemoBytes = ContiguousArray(Zip302MemoTests.helloImATextMemo) + + let textMemoBytes = try MemoBytes(contiguousBytes: contiguousTextMemoBytes) + + XCTAssertEqual(textMemoBytes.bytes, Zip302MemoTests.helloImATextMemo) + } + + func testThrowsWhenTextMemoIsConstructedWithTrailingNullBytes() throws { + let nullTrailedString = "This Is a memo with text and trailing null bytes\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}" + + XCTAssertThrowsError(try Memo(string: nullTrailedString)) { error in + guard let thrownError = error as? MemoBytes.Errors else { + XCTFail("Thrown erros is not MemoBytes.Error") + return + } + + switch thrownError { + case .invalidUTF8, .tooLong: + XCTFail("Expected .endsWithNullBytes found other errors") + case .endsWithNullBytes: + return + } + } + } + + func testThrowsWhenTextMemoIsConstructedWithNullBytes() throws { + let nullTrailedString = "\u{0}" + + XCTAssertThrowsError(try Memo(string: nullTrailedString)) { error in + guard let thrownError = error as? MemoBytes.Errors else { + XCTFail("Thrown erros is not MemoBytes.Error") + return + } + + switch thrownError { + case .invalidUTF8, .tooLong: + XCTFail("Expected .endsWithNullBytes found other errors") + case .endsWithNullBytes: + return + } + } + } + + func testTextMemoIsConstructedWithLeadingNullBytes() throws { + let nullLedString = "\u{0}ABC" + + let nullLedTextMemo = try MemoText(nullLedString) + + let nullLedMemo = try Memo(string: nullLedString) + + if case .text(let textMemo) = nullLedMemo { + XCTAssertEqual(nullLedTextMemo, textMemo) + } else { + XCTFail("Expected a TextMemo") + } + } + + func testTextMemoIsConstructedWithEmptyString() throws { + let emptyString = "" + + let emptyTextMemo = try MemoText(emptyString) + + let emptyStringMemo = try Memo(string: emptyString) + + if case .text(let textMemo) = emptyStringMemo { + XCTAssertEqual(emptyTextMemo, textMemo) + } else { + XCTFail("Expected a TextMemo") + } + } + + func testUnpaddedRawBytesWhenPaddingIsFound() throws { + let expected: [UInt8] = [0x56, 0x17, 0xe0, 0xac, 0x3c, 0xbc, 0xde] + + XCTAssertEqual(Zip302MemoTests.shortButPaddedBytes.unpaddedRawBytes(), expected) + } + + func testUnpaddedRawBytesWhenThereIsNoPadding() throws { + XCTAssertEqual(Self.fullMemoBytes.unpaddedRawBytes(), Self.fullMemoBytes) + } } diff --git a/Tests/TestUtils/Tests+Utils.swift b/Tests/TestUtils/Tests+Utils.swift index 4d110bc1..14378c01 100644 --- a/Tests/TestUtils/Tests+Utils.swift +++ b/Tests/TestUtils/Tests+Utils.swift @@ -117,6 +117,7 @@ func deleteParametersFromDocuments() throws { output: documents.appendingPathComponent("sapling-output.params") ) } + func deleteParamsFrom(spend: URL, output: URL) { try? FileManager.default.removeItem(at: spend) try? FileManager.default.removeItem(at: output) From 0282d81584888009127174a49e196a6c6ec2f502 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Thu, 8 Sep 2022 20:44:38 -0300 Subject: [PATCH 2/2] Fix Errors introduced by Merge-Commit --- Package.resolved | 2 +- .../ZcashLightClientKit/Synchronizer.swift | 37 +--------- .../Synchronizer/SDKSynchronizer.swift | 74 +------------------ .../Transaction/TransactionEncoder.swift | 48 +----------- Tests/DarksideTests/BalanceTests.swift | 4 +- Tests/DarksideTests/RewindRescanTests.swift | 2 +- Tests/TestUtils/Stubs.swift | 16 ++++ 7 files changed, 30 insertions(+), 153 deletions(-) diff --git a/Package.resolved b/Package.resolved index ae06ee39..af5aca43 100644 --- a/Package.resolved +++ b/Package.resolved @@ -87,7 +87,7 @@ "repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state": { "branch": "bin/librustzcash_0_7", - "revision": "823f864a7952073fb9718cf75610691756e34d59", + "revision": "61534023777235cc5b76d4ec44e27edc1658eea6", "version": null } } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index b338682b..3771e159 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -76,7 +76,6 @@ public protocol Synchronizer { /// reflects current connection state to LightwalletEndpoint var connectionState: ConnectionState { get } - /// prepares this initializer to operate. Initializes the internal state with the given /// Extended Viewing Keys and a wallet birthday found in the initializer object @@ -90,13 +89,11 @@ public protocol Synchronizer { /// Stop this synchronizer. Implementations should ensure that calling this method cancels all jobs that were created by this instance. func stop() throws - /// Gets the sapling shielded address for the given account. /// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used. /// - Returns the address or nil if account index is incorrect func getSaplingAddress(accountIndex: Int) -> SaplingAddress? - /// Gets the unified address for the given account. /// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used. @@ -108,36 +105,18 @@ public protocol Synchronizer { /// - Returns the address or nil if account index is incorrect func getTransparentAddress(accountIndex: Int) -> TransparentAddress? - /// Sends zatoshi. - /// - Parameter spendingKey: the key that allows spends to occur. - /// - Parameter zatoshi: the amount of zatoshi to send. - /// - Parameter toAddress: the recipient's address. - /// - Parameter memo: the optional memo to include as part of the transaction. - /// - Parameter accountIndex: the optional account id to use. By default, the first account is used. - @available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead") - // swiftlint:disable:next function_parameter_count - func sendToAddress( - spendingKey: String, - zatoshi: Int64, - toAddress: String, - memo: String?, - from accountIndex: Int, - resultBlock: @escaping (_ result: Result) -> Void - ) - - /// Sends zatoshi. /// - Parameter spendingKey: the key that allows spends to occur. /// - Parameter zatoshi: the amount to send in Zatoshi. /// - Parameter toAddress: the recipient's address. - /// - Parameter memo: the optional memo to include as part of the transaction. + /// - Parameter memo: the memo to include as part of the transaction. /// - Parameter accountIndex: the optional account id to use. By default, the first account is used. // swiftlint:disable:next function_parameter_count func sendToAddress( spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, - memo: String?, + memo: Memo, from accountIndex: Int, resultBlock: @escaping (_ result: Result) -> Void ) @@ -148,7 +127,7 @@ public protocol Synchronizer { /// - Parameter accountIndex: the optional account id that will be used to shield your funds to. By default, the first account is used. func shieldFunds( transparentAccountPrivateKey: TransparentAccountPrivKey, - memo: String?, + memo: Memo, from accountIndex: Int, resultBlock: @escaping (Result) -> Void ) @@ -194,14 +173,12 @@ public protocol Synchronizer { /// Returns the latest block height from the provided Lightwallet endpoint /// Blocking func latestHeight() throws -> BlockHeight - /// Returns the latests UTXOs for the given address from the specified height on func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result) -> Void) /// Returns the last stored transparent balance func getTransparentBalance(accountIndex: Int) throws -> WalletBalance - /// Returns the shielded total balance (includes verified and unverified balance) @available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead") @@ -237,23 +214,19 @@ public enum SyncStatus: Equatable { /// Indicates that this Synchronizer is actively downloading new blocks from the server. case downloading(_ status: BlockProgress) - /// Indicates that this Synchronizer is actively validating new blocks that were downloaded /// from the server. Blocks need to be verified before they are scanned. This confirms that /// each block is chain-sequential, thereby detecting missing blocks and reorgs. case validating - /// Indicates that this Synchronizer is actively scanning new valid blocks that were /// downloaded from the server. case scanning(_ progress: BlockProgress) - /// Indicates that this Synchronizer is actively enhancing newly scanned blocks /// with additional transaction details, fetched from the server. case enhancing(_ progress: EnhancementProgress) - /// fetches the transparent balance and stores it locally case fetching @@ -261,11 +234,9 @@ public enum SyncStatus: Equatable { /// When set, a UI element may want to turn green. case synced - /// Indicates that [stop] has been called on this Synchronizer and it will no longer be used. case stopped - /// Indicates that this Synchronizer is disconnected from its lightwalletd server. /// When set, a UI element may want to turn red. case disconnected @@ -289,7 +260,6 @@ public enum SyncStatus: Equatable { } } - /// Kind of transactions handled by a Synchronizer public enum TransactionKind { case sent @@ -297,7 +267,6 @@ public enum TransactionKind { case all } - /// Type of rewind available /// -birthday: rewinds the local state to this wallet's birthday /// -height: rewinds to the nearest blockheight to the one given as argument. diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index a4e73935..ebafba70 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -458,58 +458,11 @@ public class SDKSynchronizer: Synchronizer { // MARK: Synchronizer methods - @available(*, deprecated, message: "This function will be removed soon, use the one receiving a `Zatoshi` value instead") - public func sendToAddress( - spendingKey: String, - zatoshi: Int64, - toAddress: String, - memo: String?, - from accountIndex: Int, - resultBlock: @escaping (Result) -> Void - ) { - do { - sendToAddress( - spendingKey: try SaplingExtendedSpendingKey(encoding: spendingKey, network: network.networkType), - zatoshi: Zatoshi(zatoshi), - toAddress: try Recipient(toAddress, network: network.networkType), - memo: memo, - from: accountIndex, - resultBlock: resultBlock - ) - } catch { - resultBlock(.failure(SynchronizerError.invalidAccount)) - } - } - // swiftlint:disable:next function_parameter_count - @available(*, deprecated, message: "use Memo type instead of Optional") public func sendToAddress( spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, toAddress: Recipient, - memo: String?, - from accountIndex: Int, - resultBlock: @escaping (Result) -> Void - ) { - do { - self.sendToAddress( - spendingKey: spendingKey, - zatoshi: zatoshi, - toAddress: toAddress, - memo: try memo.intoMemo(), - from: accountIndex, - resultBlock: resultBlock - ) - } catch { - resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) - } - } - - // swiftlint:disable:next function_parameter_count - public func sendToAddress( - spendingKey: String, - zatoshi: Zatoshi, - toAddress: String, memo: Memo, from accountIndex: Int, resultBlock: @escaping (Result) -> Void @@ -534,7 +487,7 @@ public class SDKSynchronizer: Synchronizer { } public func shieldFunds( - transparentSecretKey: String, + transparentAccountPrivateKey: TransparentAccountPrivKey, memo: Memo, from accountIndex: Int, resultBlock: @escaping (Result) -> Void @@ -560,7 +513,7 @@ public class SDKSynchronizer: Synchronizer { return } - let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: uAddr.stringEncoded, memo: memo, from: accountIndex) + let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: uAddr.stringEncoded, memo: try memo.asMemoBytes(), from: accountIndex) transactionManager.encodeShieldingTransaction( xprv: transparentAccountPrivateKey, @@ -582,7 +535,7 @@ public class SDKSynchronizer: Synchronizer { } case .failure(let error): - resultBlock(.failure(error)) + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } } catch { @@ -590,25 +543,6 @@ public class SDKSynchronizer: Synchronizer { } } - @available(*, deprecated, message: "use shieldFunds with a Memo type") - public func shieldFunds( - transparentSecretKey: String, - memo: String?, - from accountIndex: Int, - resultBlock: @escaping (Result) -> Void - ) { - do { - shieldFunds( - transparentSecretKey: transparentSecretKey, - memo: try memo.intoMemo(), - from: accountIndex, - resultBlock: resultBlock - ) - } catch { - resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) - } - } - // swiftlint:disable:next function_parameter_count func createToAddress( spendingKey: SaplingExtendedSpendingKey, @@ -642,7 +576,7 @@ public class SDKSynchronizer: Synchronizer { } case .failure(let error): - resultBlock(.failure(error)) + resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error))) } } } catch { diff --git a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift index 4435b313..e7f649e5 100644 --- a/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/TransactionEncoder.swift @@ -50,7 +50,7 @@ protocol TransactionEncoder { /// - Parameter spendingKey: a `SaplingExtendedSpendingKey` containing the spending key /// - Parameter zatoshi: the amount to send in `Zatoshi` /// - Parameter to: string containing the recipient address - /// - Parameter memo: string containing the memo (optional) + /// - Parameter MemoBytes: string containing the memo (optional) /// - Parameter accountIndex: index of the account that will be used to send the funds /// - Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a /// TransactionEncoderError // swiftlint:disable:next function_parameter_count @@ -58,7 +58,7 @@ protocol TransactionEncoder { spendingKey: SaplingExtendedSpendingKey, zatoshi: Zatoshi, to address: String, - memo: String?, + memoBytes: MemoBytes, from accountIndex: Int, result: @escaping TransactionEncoderResultBlock ) @@ -69,54 +69,12 @@ protocol TransactionEncoder { - Parameters: - Parameter tAccountPrivateKey: transparent account private key to spend the UTXOs - - Parameter memo: string containing the memo (optional) + - Parameter memoBytes: containing the memo (optional) - Parameter accountIndex: index of the account that will be used to send the funds - - Throws: a TransactionEncoderError */ func createShieldingTransaction( tAccountPrivateKey: TransparentAccountPrivKey, - memo: String?, - from accountIndex: Int - ) throws -> EncodedTransaction - -/// Creates a transaction, 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). -/// Non-blocking - -/// - Parameters: -/// - Parameter spendingKey: a string containing the spending key -/// - Parameter zatoshi: the amount to send in zatoshis -/// - Parameter to: string containing the recipient address -/// - Parameter memoBytes: MemoBytes for this transaction -/// - Parameter accountIndex: index of the account that will be used to send the funds -/// - Parameter result: a non escaping closure that receives a Result containing either an ///EncodedTransaction or a TransactionEncoderError - func createTransaction( - spendingKey: String, - zatoshi: Int, - to address: String, - memoBytes: MemoBytes, - from accountIndex: Int, - result: @escaping TransactionEncoderResultBlock - )// swiftlint:disable function_parameter_count - -/// Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB. -/// 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). -/// -/// Blocking -/// -/// - Parameters: -/// - Parameter spendingKey: a string containing the spending key -/// - Parameter tSecretKey: transparent secret key to spend the UTXOs -/// - Parameter Parameter memoBytes: MemoBytes for this transaction -/// - Parameter accountIndex: index of the account that will be used to send the funds -/// -/// - Throws: a TransactionEncoderError - func createShieldingTransaction( - spendingKey: String, - tSecretKey: String, memoBytes: MemoBytes, from accountIndex: Int ) throws -> EncodedTransaction diff --git a/Tests/DarksideTests/BalanceTests.swift b/Tests/DarksideTests/BalanceTests.swift index 0a29f900..648b5d5e 100644 --- a/Tests/DarksideTests/BalanceTests.swift +++ b/Tests/DarksideTests/BalanceTests.swift @@ -83,7 +83,7 @@ class BalanceTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalance, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - try Memo(string: "this is a test"), + memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in switch result { @@ -510,7 +510,7 @@ class BalanceTests: XCTestCase { coordinator.synchronizer.sendToAddress( spendingKey: spendingKey, zatoshi: sendAmount, - toAddress: testRecipientAddress, + toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), memo: try Memo(string: "this is a test"), from: 0, resultBlock: { result in diff --git a/Tests/DarksideTests/RewindRescanTests.swift b/Tests/DarksideTests/RewindRescanTests.swift index cf00ee7f..163965cb 100644 --- a/Tests/DarksideTests/RewindRescanTests.swift +++ b/Tests/DarksideTests/RewindRescanTests.swift @@ -271,7 +271,7 @@ class RewindRescanTests: XCTestCase { spendingKey: spendingKey, zatoshi: maxBalance, toAddress: try Recipient(testRecipientAddress, network: self.network.networkType), - memo: try Memo("test send \(self.description) \(Date().description)"), + memo: try Memo(string: "test send \(self.description) \(Date().description)"), from: 0 ) { result in switch result { diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 93492981..fb2a00a0 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -77,6 +77,22 @@ extension LightWalletServiceMockResponse { } class MockRustBackend: ZcashRustBackendWelding { + static func getReceivedMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) -> ZcashLightClientKit.Memo? { + nil + } + + static func getSentMemo(dbData: URL, idNote: Int64, networkType: ZcashLightClientKit.NetworkType) -> ZcashLightClientKit.Memo? { + nil + } + + static func createToAddress(dbData: URL, account: Int32, extsk: String, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 { + -1 + } + + static func shieldFunds(dbCache: URL, dbData: URL, account: Int32, xprv: String, memo: ZcashLightClientKit.MemoBytes, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 { + -1 + } + static func initDataDb(dbData: URL, seed: [UInt8]?, networkType: ZcashLightClientKit.NetworkType) throws -> ZcashLightClientKit.DbInitResult { .seedRequired