Merge pull request #393 from zcash/384_adopt_type_safe_memos

This commit is contained in:
Francisco Gindre 2022-09-09 19:41:33 -07:00 committed by GitHub
commit 375cf3ce41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 464 additions and 288 deletions

View File

@ -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:

View File

@ -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
}
}

View File

@ -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
)
}

View File

@ -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<UInt8>) 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)
}
}
}

View File

@ -0,0 +1,7 @@
{
"network": "main",
"height": "1686871",
"hash": "0000000000443d2be0b66f0f2c03784cf267e50935562a23c10d9d3841343cd7",
"time": 1654001768,
"saplingTree": "01802ef6c171eca37050fbc6cdf8d9712a8c5e4650af1a31300c143e063a680e2100140000000001e684c306416b750c7b4883e145922c129e5919a658b02e6146d2da8d17a3ff42000001ae61a98ec05d976a41e46f0b7ae8dee5bda5ab490eea7b701153511a15be42590198c748a5e9516711db607e282f08013ba00b3e77bc66ec28702ca17893a5154b000139113a1e0f54d93c6180f2df7d16afbf7c320f6c7665cb10e1fc309878b0d716019a0214bcb1fd7a70e53166101992147512f3debb0fdce45ac625fffd64771010000134136b9f1f00c2e9d16dc7358ef920862511c8fc42fb7074cbc9016d8d4e8b4c015eddc191a81221b7900bbdcd8610e5df595e3cdc7fd5b3432e3825206ae35b05017eda713cd733ccc555123788692a1876f9ca292b0aa2ddf3f45ed2b47f027340000000015ec9e9b1295908beed437df4126032ca57ada8e3ebb67067cd22a73c79a84009"
}

View File

@ -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<UInt8>(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<UInt8>(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,

View File

@ -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

View File

@ -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<PendingTransactionEntity, Error>) -> 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<PendingTransactionEntity, Error>) -> 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<PendingTransactionEntity, Error>) -> 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<RefreshedUTXOs, Error>) -> 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.

View File

@ -458,35 +458,12 @@ public class SDKSynchronizer: Synchronizer {
// MARK: Synchronizer methods
@available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead")
public func sendToAddress(
spendingKey: String,
zatoshi: Int64,
toAddress: String,
memo: String?,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> 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
public func sendToAddress(
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
toAddress: Recipient,
memo: String?,
memo: Memo,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
@ -508,16 +485,16 @@ public class SDKSynchronizer: Synchronizer {
}
}
}
public func shieldFunds(
transparentAccountPrivateKey: TransparentAccountPrivKey,
memo: String?,
memo: Memo,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> 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)
@ -536,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,
@ -558,12 +535,11 @@ public class SDKSynchronizer: Synchronizer {
}
case .failure(let error):
resultBlock(.failure(error))
resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error)))
}
}
} catch {
resultBlock(.failure(error))
return
resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error)))
}
}
@ -572,7 +548,7 @@ public class SDKSynchronizer: Synchronizer {
spendingKey: SaplingExtendedSpendingKey,
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
memo: Memo,
from accountIndex: Int,
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
) {
@ -580,7 +556,7 @@ public class SDKSynchronizer: Synchronizer {
let spend = try transactionManager.initSpend(
zatoshi: zatoshi,
toAddress: toAddress,
memo: memo,
memo: memo.asMemoBytes(),
from: accountIndex
)
@ -600,11 +576,11 @@ public class SDKSynchronizer: Synchronizer {
}
case .failure(let error):
resultBlock(.failure(error))
resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error)))
}
}
} catch {
resultBlock(.failure(error))
resultBlock(.failure(SynchronizerError.uncategorized(underlyingError: error)))
}
}

View File

@ -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

View File

@ -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
@ -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,22 +69,19 @@ 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?,
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
*/
///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
}

View File

@ -17,7 +17,7 @@ protocol OutboundTransactionManager {
func initSpend(
zatoshi: Zatoshi,
toAddress: String,
memo: String?,
memo: MemoBytes,
from accountIndex: Int
) throws -> PendingTransactionEntity

View File

@ -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
}

View File

@ -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 {

View File

@ -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)",
memo: 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 {
@ -511,7 +511,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: "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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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(string: "test send \(self.description) \(Date().description)"),
from: 0
) { result in
switch result {

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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<UInt8>(Zip302MemoTests.emptyMemoBytes)
let emptyMemoBytes = try MemoBytes(contiguousBytes: contiguousEmptyBytes)
XCTAssertEqual(emptyMemoBytes.bytes, .emptyMemoBytes)
let contiguousTextMemoBytes = ContiguousArray<UInt8>(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)
}
}

View File

@ -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

View File

@ -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)