Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-account and PCZT support #1517

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a09f2b1
import-ufvk-ffi
LukasKorba Dec 3, 2024
3bfe5b3
ImportAccount UFVK
LukasKorba Dec 4, 2024
06181fb
AccountUUID refactor - phase 1
LukasKorba Dec 4, 2024
7a6bd72
AccountUUID refactor - phase 2
LukasKorba Dec 4, 2024
27af6f1
PR Comments addressed and code cleaned up
LukasKorba Dec 5, 2024
92bd2e6
Update swift.yml
LukasKorba Dec 5, 2024
05002a8
Generated mocks
LukasKorba Dec 5, 2024
bb03333
Performance, Network and Dark side tests
LukasKorba Dec 5, 2024
5179f4e
getAccount for UUID
LukasKorba Dec 6, 2024
e48cd60
Account conformace
LukasKorba Dec 6, 2024
df1b319
hd_account_index wrapped in Zip32AccountIndex
LukasKorba Dec 9, 2024
89773c6
account_id -> account_uuid refactor
LukasKorba Dec 9, 2024
f69aa19
Public importAccount method
LukasKorba Dec 9, 2024
8c099e3
seedFingerprint and zip32AccountIndex parameters in importAccount added
LukasKorba Dec 9, 2024
dcf6cd9
Codable UnifiedAddress conformance
LukasKorba Dec 9, 2024
7782d6f
Optional account_name
LukasKorba Dec 9, 2024
32bb9ba
FFI bump
LukasKorba Dec 10, 2024
08e5a73
DerivationTool.deriveUnifiedAddressFrom(ufvk)
LukasKorba Dec 10, 2024
6bab337
PCZT methods
LukasKorba Dec 10, 2024
0fba7f2
PCZT methods updated
LukasKorba Dec 10, 2024
5943d63
FFI bump
LukasKorba Dec 10, 2024
30af9c6
PCZT API updated
LukasKorba Dec 10, 2024
dd587ac
from_account_id reverted
LukasKorba Dec 10, 2024
26ad508
FFI bumped
LukasKorba Dec 10, 2024
632626a
Reset of synchronizer alias
LukasKorba Dec 11, 2024
ee32938
FFI bump
LukasKorba Dec 11, 2024
f499898
Create and submit PCZT
LukasKorba Dec 12, 2024
fb38ac8
Alias checker disconnected
LukasKorba Dec 12, 2024
da00a69
Pczt typealias and clean up
LukasKorba Dec 13, 2024
f16d098
Code cleanup
LukasKorba Dec 13, 2024
ce667eb
FFI 0.12.0 bump
LukasKorba Dec 17, 2024
a8b1524
Review comments addressed
LukasKorba Jan 7, 2025
3bea79c
Comments updated
LukasKorba Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SDKSynchronizer.getAccountBalance -> AccountBalance?` into `SDKSynchronizer.getAccountsBalances -> [AccountUUID: AccountBalance]`

## Removed
- `SDKSynchronizer.sendToAddress`, deperacted in 2.1
- `SDKSynchronizer.shieldFunds`, deperacted in 2.1
- `SDKSynchronizer.sendToAddress`, deprecated in 2.1
- `SDKSynchronizer.shieldFunds`, deprecated in 2.1

## Checkpoints

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.24.2"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3"),
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", from: "0.12.0")
.package(url: "https://github.com/Electric-Coin-Company/zcash-light-client-ffi", exact: "0.12.0")
],
targets: [
.target(
Expand Down
2 changes: 1 addition & 1 deletion Sources/ZcashLightClientKit/Constants/ZcashSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public enum ZcashSDKTestnetConstants: NetworkConstants {
public static let defaultDbNamePrefix = "ZcashSdk_testnet_"
}

/// Used when importing an aaccount `importAccount(..., purpose: AccountPurpose)`
/// Used when importing an account `importAccount(..., purpose: AccountPurpose)`
public enum AccountPurpose: UInt32, Equatable {
case spending = 0
case viewOnly
Expand Down
12 changes: 12 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,14 @@ public enum ZcashError: Equatable, Error {
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0071
case rustExtractAndStoreTxFromPCZT(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.getAccount
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0072
case rustUUIDAccountNotFound(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT
/// - `rustError` contains error generated by the rust layer.
/// ZRUST0073
case rustTxidPtrIncorrectLength(_ rustError: String)
/// SQLite query failed when fetching all accounts from the database.
/// - `sqliteError` is error produced by SQLite library.
/// ZADAO0001
Expand Down Expand Up @@ -757,6 +765,8 @@ public enum ZcashError: Equatable, Error {
case .rustCreatePCZTFromProposal: return "Error from rust layer when calling ZcashRustBackend.createPCZTFromProposal"
case .rustAddProofsToPCZT: return "Error from rust layer when calling ZcashRustBackend.addProofsToPCZT"
case .rustExtractAndStoreTxFromPCZT: return "Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT"
case .rustUUIDAccountNotFound: return "Error from rust layer when calling ZcashRustBackend.getAccount"
case .rustTxidPtrIncorrectLength: return "Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT"
case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database."
case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them."
case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database."
Expand Down Expand Up @@ -947,6 +957,8 @@ public enum ZcashError: Equatable, Error {
case .rustCreatePCZTFromProposal: return .rustCreatePCZTFromProposal
case .rustAddProofsToPCZT: return .rustAddProofsToPCZT
case .rustExtractAndStoreTxFromPCZT: return .rustExtractAndStoreTxFromPCZT
case .rustUUIDAccountNotFound: return .rustUUIDAccountNotFound
case .rustTxidPtrIncorrectLength: return .rustTxidPtrIncorrectLength
case .accountDAOGetAll: return .accountDAOGetAll
case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode
case .accountDAOFindBy: return .accountDAOFindBy
Expand Down
4 changes: 4 additions & 0 deletions Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ public enum ZcashErrorCode: String {
case rustAddProofsToPCZT = "ZRUST0070"
/// Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT
case rustExtractAndStoreTxFromPCZT = "ZRUST0071"
/// Error from rust layer when calling ZcashRustBackend.getAccount
case rustUUIDAccountNotFound = "ZRUST0072"
/// Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT
case rustTxidPtrIncorrectLength = "ZRUST0073"
/// SQLite query failed when fetching all accounts from the database.
case accountDAOGetAll = "ZADAO0001"
/// Fetched accounts from SQLite but can't decode them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

// swiftlint:disable identifier_name

Check warning on line 10 in Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Blanket Disable Command Violation: Use 'next', 'this' or 'previous' instead to disable the 'identifier_name' rule once, or re-enable it as soon as possible` (blanket_disable_command)

/*
This enum won't every be directly used in the code. It is just definition of errors and it used as source for Sourcery. And the files used in the
Expand Down Expand Up @@ -406,8 +406,15 @@
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0071"
case rustExtractAndStoreTxFromPCZT(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.getAccount
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0072"
case rustUUIDAccountNotFound(_ rustError: String)
/// Error from rust layer when calling ZcashRustBackend.extractAndStoreTxFromPCZT
/// - `rustError` contains error generated by the rust layer.
// sourcery: code="ZRUST0073"
case rustTxidPtrIncorrectLength(_ rustError: String)


// MARK: - Account DAO

/// SQLite query failed when fetching all accounts from the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ protocol ZcashKeyDerivationBackendWelding {
func isValidUnifiedFullViewingKey(_ ufvk: String) -> Bool

/// Derives and returns a UnifiedAddress from a UnifiedFullViewingKey
/// - Parameter ufvk: UTF-8 encoded String to validate
/// - Returns: true `UnifiedAddress`
/// - Parameter ufvk: UTF-8 encoded String containing a valid UFVK
/// - Returns: the corresponding default `UnifiedAddress`
func deriveUnifiedAddressFrom(ufvk: String) throws -> UnifiedAddress

/// Derives and returns a unified spending key from the given seed and ZIP 32 account index.
Expand Down
43 changes: 34 additions & 9 deletions Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,16 @@ struct ZcashRustBackend: ZcashRustBackendWelding {
)

guard let accountPtr else {
throw ZcashError.rustImportAccountUfvk(lastErrorMessage(fallback: "`importAccount` failed with unknown error"))
throw ZcashError.rustImportAccountUfvk(lastErrorMessage(fallback: "`getAccount` failed with unknown error"))
}

defer { zcashlc_free_account(accountPtr) }

return accountPtr.pointee.unsafeToAccount()
guard let validAccount = accountPtr.pointee.unsafeToAccount() else {
throw ZcashError.rustUUIDAccountNotFound(lastErrorMessage(fallback: "`getAccount` failed with unknown error"))
}

return validAccount
}

// swiftlint:disable:next function_parameter_count
Expand Down Expand Up @@ -358,7 +362,7 @@ struct ZcashRustBackend: ZcashRustBackendWelding {
pcztWithProofs: Pczt,
pcztWithSigs: Pczt
) async throws -> Data {
let pcztPtr: UnsafeMutablePointer<FfiBoxedSlice>? = pcztWithProofs.withUnsafeBytes { pcztWithProofsBuffer in
let txidPtr: UnsafeMutablePointer<FfiBoxedSlice>? = pcztWithProofs.withUnsafeBytes { pcztWithProofsBuffer in
guard let pcztWithProofsBufferPtr = pcztWithProofsBuffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return nil
}
Expand All @@ -384,15 +388,19 @@ struct ZcashRustBackend: ZcashRustBackendWelding {
}
}

guard let pcztPtr else {
guard let txidPtr else {
throw ZcashError.rustExtractAndStoreTxFromPCZT(lastErrorMessage(fallback: "`extractAndStoreTxFromPCZT` failed with unknown error"))
}

defer { zcashlc_free_boxed_slice(pcztPtr) }
guard txidPtr.pointee.len == 32 else {
throw ZcashError.rustTxidPtrIncorrectLength(lastErrorMessage(fallback: "`extractAndStoreTxFromPCZT` failed with unknown error"))
}

defer { zcashlc_free_boxed_slice(txidPtr) }

return Data(
bytes: pcztPtr.pointee.ptr,
count: Int(pcztPtr.pointee.len)
bytes: txidPtr.pointee.ptr,
count: Int(txidPtr.pointee.len)
)
}

Expand Down Expand Up @@ -1163,8 +1171,25 @@ extension FfiAccount {

/// converts an [`FfiAccount`] into a [`Account`]
/// - Note: This does not check that the converted value actually holds a valid Account
func unsafeToAccount() -> Account {
.init(
func unsafeToAccount() -> Account? {
// Invalid UUID check
guard uuidArray != [UInt8](repeating: 0, count: 16) else {
return nil
}

// Invalid ZIP 32 account index
if hd_account_index == UInt32.max {
return .init(
id: AccountUUID(id: uuidArray),
name: account_name != nil ? String(cString: account_name) : nil,
keySource: key_source != nil ? String(cString: key_source) : nil,
seedFingerprint: nil,
hdAccountIndex: nil
)
}

// Valid ZIP32 account index
return .init(
id: AccountUUID(id: uuidArray),
name: account_name != nil ? String(cString: account_name) : nil,
keySource: key_source != nil ? String(cString: key_source) : nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ protocol ZcashRustBackendWelding {
/// Returns a list of the accounts in the wallet.
func listAccounts() async throws -> [Account]

/// Adds a new account to the wallet by importing the UFVK that will be used to detect incoming
/// payments.
///
/// Derivation metadata may optionally be included. To indicate that no derivation metadata is
/// available, `seedFingerprint` and `zip32AccountIndex` should be set to `nil`. Derivation
/// metadata will not be stored unless both the seed fingerprint and the HD account index are
/// provided.
///
/// - Returns: the globally unique identifier for the account.
// swiftlint:disable:next function_parameter_count
func importAccount(
ufvk: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import Combine

/// Synchronizer implementation for UIKit and iOS 13+
// swiftlint:disable type_body_length

Check warning on line 13 in Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Blanket Disable Command Violation: Use 'next', 'this' or 'previous' instead to disable the 'type_body_length' rule once, or re-enable it as soon as possible` (blanket_disable_command)
public class SDKSynchronizer: Synchronizer {
public var alias: ZcashSynchronizerAlias { initializer.alias }

Expand Down Expand Up @@ -425,12 +425,12 @@
logger: logger
)

let txIds = try await initializer.rustBackend.extractAndStoreTxFromPCZT(
let txId = try await initializer.rustBackend.extractAndStoreTxFromPCZT(
pcztWithProofs: pcztWithProofs,
pcztWithSigs: pcztWithSigs
)

let transactions = try await transactionEncoder.createTransactionsFromTxIds([txIds])
let transactions = try await transactionEncoder.fetchTransactionsForTxIds([txId])

return submitTransactions(transactions)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/ZcashLightClientKit/Tool/DerivationTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public protocol KeyValidation {

public protocol KeyDeriving {
/// Derives and returns a UnifiedAddress from a UnifiedFullViewingKey
/// - Parameter ufvk: UTF-8 encoded String to validate
/// - Returns: true `UnifiedAddress`
/// - Parameter ufvk: UTF-8 encoded String containing a valid UFVK
/// - Returns: the corresponding default `UnifiedAddress`
func deriveUnifiedAddressFrom(ufvk: String) throws -> UnifiedAddress

/// Given the seed bytes and ZIP 32 account index, return the corresponding UnifiedSpendingKey.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ protocol TransactionEncoder {
/// - Parameter transaction: a transaction overview
func submit(transaction: EncodedTransaction) async throws

func createTransactionsFromTxIds(_ txIds: [Data]) async throws -> [ZcashTransaction.Overview]
/// Tries to fetch the transaction for the given transction ids.
/// - Parameter txIds: an array of transaction ids to be fetched from the DB.
func fetchTransactionsForTxIds(_ txIds: [Data]) async throws -> [ZcashTransaction.Overview]

func closeDBConnection()
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,10 @@ class WalletTransactionEncoder: TransactionEncoder {
usk: spendingKey
)

return try await createTransactionsFromTxIds(txIds)
return try await fetchTransactionsForTxIds(txIds)
}

func createTransactionsFromTxIds(_ txIds: [Data]) async throws -> [ZcashTransaction.Overview] {
guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else {
throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams
}

func fetchTransactionsForTxIds(_ txIds: [Data]) async throws -> [ZcashTransaction.Overview] {
logger.debug("transaction ids: \(txIds)")

var txs: [ZcashTransaction.Overview] = []
Expand Down
2 changes: 1 addition & 1 deletion Tests/PerformanceTests/SynchronizerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class SynchronizerTests: ZcashTestCase {
rustBackend = nil
}

// TODO: [#1518] Fix the test, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1518
// TODO: [#1521] Fix the test, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1521
func _testHundredBlocksSync() async throws {
guard let seedData = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==") else {
XCTFail("seedData expected to be successfuly instantiated.")
Expand Down
3 changes: 2 additions & 1 deletion Tests/TestUtils/TestsData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,6 @@ class TestsData {
}

extension TestsData {
static let mockedAccountUUID = AccountUUID(id: Array<UInt8>(repeating: 0, count: 16))
/// `mockedAccountUUID` is used to mock the rust backend for check only purposes. It's never passed to it.
static let mockedAccountUUID = AccountUUID(id: [UInt8](repeating: 0, count: 16))
}
Loading