diff --git a/changelog.md b/CHANGELOG.md similarity index 82% rename from changelog.md rename to CHANGELOG.md index 21902027..b8a6552d 100644 --- a/changelog.md +++ b/CHANGELOG.md @@ -1,81 +1,13 @@ +# 0.17.0-alpha.3 +- [#602] Improve error logging for InitializerError and RustWeldingError + # 0.17.0-alpha.2 - [#579] Fix database lock - [#592] Fix various tests and deleted some that are not useful anymore - [#581] getTransparentBalanceForAccount error not handled + # 0.17.0-alpha.1 -## Changes to Demo APP -The demo application now uses the SDKSynchronizer to create addresses and -shield funds. -`DerivationToolViewController` was removed. See `DerivationTool` unit tests -for sample code. -`GetAddressViewController` now derives transparent and sapling addresses -from Unified Address -`SendViewController` uses Unified Spending Key and type-safe `Memo` - -## Changes To SDK -### `CompactBlockProcessor` -`public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress?` -`public func getSaplingAddress(accountIndex: Int) -> SaplingAddress?` derived from UA -`public func getTransparentAddress(accountIndex: Int) -> TransparentAddress?` -is derived from UA -`public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance` now -fetches from account exclusively -`func refreshUTXOs(tAddress: TransparentAddress, startHeight: BlockHeight) async throws -> RefreshedUTXOs` -uses `TransparentAddress` - -### Initializer -Migration of DataDB and CacheDB are delegated to `librustzcash` - -removed `public func getAddress(index account: Int = 0) -> String` - - -### Wallet Types -`UnifiedSpendingKey` to represent Unified Spending Keys. This is a binary -encoded not meant to be stored or backed up. This only serves the purpuse -of letting clients use the least priviledge keys at all times for every -operation. - -### Synchronizer -`sendToAddress` and `shieldFunds` now take a `UnifiedSpendingKey` instead -of the respective spending and transparent private keys. -`refreshUTXOs` uses `TransparentAddress` - -### KeyDeriving protocol -Addresses should be obtained from the `Synchronizer` by using the `get_address` functions -Transparent and Sapling receivers should be obtained by extracting the receivers of a UA -````Swift -public extension UnifiedAddress { - /// Extracts the sapling receiver from this UA if available - /// - Returns: an `Optional` - func saplingReceiver() -> SaplingAddress? { - try? DerivationTool.saplingReceiver(from: self) - } - - /// Extracts the transparent receiver from this UA if available - /// - Returns: an `Optional` - func transparentReceiver() -> TransparentAddress? { - try? DerivationTool.transparentReceiver(from: self) - } -```` - -**Removed** -`func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]` -`func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey` -`func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey]` -`func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress` -`func deriveTransparentAddress(seed: [UInt8], account: Int, index: Int) throws -> TransparentAddress` -`func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> TransparentAccountPrivKey` -`func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress` - -**Added** -`static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress?` -`static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress?` -`static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]` -`func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey` -`public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey` - - - +See MIGRATING.md # 0.16-13-beta - [#597] SDK does not build with SQLite 0.14 diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 00000000..25b03e0f --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,74 @@ +# Migrating from 0.16.x-beta to 0.17.0-alpha.x + +## Changes to Demo APP +The demo application now uses the SDKSynchronizer to create addresses and +shield funds. +`DerivationToolViewController` was removed. See `DerivationTool` unit tests +for sample code. +`GetAddressViewController` now derives transparent and sapling addresses +from Unified Address +`SendViewController` uses Unified Spending Key and type-safe `Memo` + +## Changes To SDK +### `CompactBlockProcessor` +`public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress?` +`public func getSaplingAddress(accountIndex: Int) -> SaplingAddress?` derived from UA +`public func getTransparentAddress(accountIndex: Int) -> TransparentAddress?` +is derived from UA +`public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance` now +fetches from account exclusively +`func refreshUTXOs(tAddress: TransparentAddress, startHeight: BlockHeight) async throws -> RefreshedUTXOs` +uses `TransparentAddress` + +### Initializer +Migration of DataDB and CacheDB are delegated to `librustzcash` + +removed `public func getAddress(index account: Int = 0) -> String` + + +### Wallet Types +`UnifiedSpendingKey` to represent Unified Spending Keys. This is a binary +encoded not meant to be stored or backed up. This only serves the purpuse +of letting clients use the least priviledge keys at all times for every +operation. + +### Synchronizer +`sendToAddress` and `shieldFunds` now take a `UnifiedSpendingKey` instead +of the respective spending and transparent private keys. +`refreshUTXOs` uses `TransparentAddress` + +### KeyDeriving protocol +Addresses should be obtained from the `Synchronizer` by using the `get_address` functions +Transparent and Sapling receivers should be obtained by extracting the receivers of a UA +````Swift +public extension UnifiedAddress { + /// Extracts the sapling receiver from this UA if available + /// - Returns: an `Optional` + func saplingReceiver() -> SaplingAddress? { + try? DerivationTool.saplingReceiver(from: self) + } + + /// Extracts the transparent receiver from this UA if available + /// - Returns: an `Optional` + func transparentReceiver() -> TransparentAddress? { + try? DerivationTool.transparentReceiver(from: self) + } +```` + +**Removed** +`func deriveUnifiedFullViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [UnifiedFullViewingKey]` +`func deriveViewingKey(spendingKey: SaplingExtendedSpendingKey) throws -> SaplingExtendedFullViewingKey` +`func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [SaplingExtendedSpendingKey]` +`func deriveUnifiedAddress(from ufvk: UnifiedFullViewingKey) throws -> UnifiedAddress` +`func deriveTransparentAddress(seed: [UInt8], account: Int, index: Int) throws -> TransparentAddress` +`func deriveTransparentAccountPrivateKey(seed: [UInt8], account: Int) throws -> TransparentAccountPrivKey` +`func deriveTransparentAddressFromAccountPrivateKey(_ xprv: TransparentAccountPrivKey, index: Int) throws -> TransparentAddress` + +**Added** +`static func saplingReceiver(from unifiedAddress: UnifiedAddress) throws -> SaplingAddress?` +`static func transparentReceiver(from unifiedAddress: UnifiedAddress) throws -> TransparentAddress?` +`static func receiverTypecodesFromUnifiedAddress(_ address: UnifiedAddress) throws -> [UnifiedAddress.ReceiverTypecodes]` +`func deriveUnifiedSpendingKey(seed: [UInt8], accountIndex: Int) throws -> UnifiedSpendingKey` +`public func deriveUnifiedFullViewingKey(from spendingKey: UnifiedSpendingKey) throws -> UnifiedFullViewingKey` + + diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index 085ba55e..f3ea6183 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -13,10 +13,9 @@ Wrapper for the Rust backend. This class basically represents all the Rust-walle capabilities and the supporting data required to exercise those abilities. */ public enum InitializerError: Error { - case cacheDbInitFailed - case dataDbInitFailed - case accountInitFailed - case falseStart + case cacheDbInitFailed(Error) + case dataDbInitFailed(Error) + case accountInitFailed(Error) case invalidViewingKey(key: String) } @@ -197,7 +196,7 @@ public class Initializer { do { try storage.createTable() } catch { - throw InitializerError.cacheDbInitFailed + throw InitializerError.cacheDbInitFailed(error) } do { @@ -205,7 +204,7 @@ public class Initializer { return .seedRequired } } catch { - throw InitializerError.dataDbInitFailed + throw InitializerError.dataDbInitFailed(error) } let checkpoint = Checkpoint.birthday(with: self.walletBirthday, network: network) @@ -221,7 +220,7 @@ public class Initializer { } catch RustWeldingError.dataDbNotEmpty { // this is fine } catch { - throw InitializerError.dataDbInitFailed + throw InitializerError.dataDbInitFailed(error) } self.walletBirthday = checkpoint.height @@ -230,19 +229,17 @@ public class Initializer { lowerBoundHeight = max(walletBirthday, lastDownloaded) do { - guard try rustBackend.initAccountsTable( + try rustBackend.initAccountsTable( dbData: dataDbURL, ufvks: viewingKeys, networkType: network.networkType - ) else { - throw rustBackend.lastError() ?? InitializerError.accountInitFailed - } + ) } catch RustWeldingError.dataDbNotEmpty { // this is fine } catch RustWeldingError.malformedStringInput { throw RustWeldingError.malformedStringInput } catch { - throw rustBackend.lastError() ?? InitializerError.accountInitFailed + throw InitializerError.accountInitFailed(error) } let migrationManager = MigrationManager( @@ -371,3 +368,19 @@ enum CompactBlockProcessorBuilder { ) } } + + +extension InitializerError: LocalizedError { + public var errorDescription: String? { + switch self { + case .invalidViewingKey: + return "The provided viewing key is invalid" + case .cacheDbInitFailed(let error): + return "cacheDb Init failed with error: \(error.localizedDescription)" + case .dataDbInitFailed(let error): + return "dataDb init failed with error: \(error.localizedDescription)" + case .accountInitFailed(let error): + return "account table init failed with error: \(error.localizedDescription)" + } + } +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 48be5c7b..65df795a 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -434,7 +434,7 @@ class ZcashRustBackend: ZcashRustBackendWelding { dbData: URL, ufvks: [UnifiedFullViewingKey], networkType: NetworkType - ) throws -> Bool { + ) throws { let dbData = dbData.osStr() var ffiUfvks = [FFIEncodedKey]() @@ -470,14 +470,16 @@ class ZcashRustBackend: ZcashRustBackendWelding { networkType.networkId ) } - + defer { for ufvk in ffiUfvks { ufvk.encoding.deallocate() } } - - return result + + guard result else { + throw lastError() ?? .genericError(message: "`initAccountsTable` failed with unknown error") + } } // swiftlint:disable function_parameter_count @@ -730,3 +732,28 @@ extension UnsafeMutablePointer where Pointee == UInt8 { } } + +extension RustWeldingError: LocalizedError { + var errorDescription: String? { + switch self { + case .genericError(let message): + return "RustWeldingError generic error: \(message)" + case .dataDbInitFailed(let message): + return "`RustWeldingError.dataDbInitFailed` with message: \(message)" + case .dataDbNotEmpty: + return "`.DataDbNotEmpty`. This is usually not an error." + case .invalidInput(let message): + return "`RustWeldingError.invalidInput` with message: \(message)" + case .malformedStringInput: + return "`.malformedStringInput` Called a function with a malformed string input." + case .invalidRewind: + return "`.invalidRewind` called the rewind API with an arbitrary height that is not valid." + case .noConsensusBranchId(let branchId): + return "`.noConsensusBranchId` number \(branchId)" + case .saplingSpendParametersNotFound: + return "`.saplingSpendParametersNotFound` sapling parameters not present at specified URL" + case .unableToDeriveKeys: + return "`.unableToDeriveKeys` the requested keys could not be derived from the source provided" + } + } +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index 3f43b9b2..41ef36c0 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -245,7 +245,7 @@ protocol ZcashRustBackendWelding { dbData: URL, ufvks: [UnifiedFullViewingKey], networkType: NetworkType - ) throws -> Bool + ) throws /// initializes the data db. This will performs any migrations needed on the sqlite file /// provided. Some migrations might need that callers provide the seed bytes. diff --git a/Tests/DarksideTests/TransactionEnhancementTests.swift b/Tests/DarksideTests/TransactionEnhancementTests.swift index bb05205c..10040ae0 100644 --- a/Tests/DarksideTests/TransactionEnhancementTests.swift +++ b/Tests/DarksideTests/TransactionEnhancementTests.swift @@ -74,11 +74,13 @@ class TransactionEnhancementTests: XCTestCase { } ] - guard try rustBackend.initAccountsTable( - dbData: processorConfig.dataDb, - ufvks: ufvks, - networkType: network.networkType - ) else { + do { + try rustBackend.initAccountsTable( + dbData: processorConfig.dataDb, + ufvks: ufvks, + networkType: network.networkType + ) + } catch { XCTFail("Failed to init accounts table error: " + String(describing: rustBackend.getLastError())) return } diff --git a/Tests/NetworkTests/BlockScanTests.swift b/Tests/NetworkTests/BlockScanTests.swift index bdbfaec2..525c74a4 100644 --- a/Tests/NetworkTests/BlockScanTests.swift +++ b/Tests/NetworkTests/BlockScanTests.swift @@ -131,11 +131,13 @@ class BlockScanTests: XCTestCase { .map { try derivationTool.deriveUnifiedFullViewingKey(from: $0) } - guard try self.rustWelding.initAccountsTable( - dbData: self.dataDbURL, - ufvks: [ufvk], - networkType: network.networkType - ) else { + do { + try self.rustWelding.initAccountsTable( + dbData: self.dataDbURL, + ufvks: [ufvk], + networkType: network.networkType + ) + } catch { XCTFail("failed to init account table. error: \(self.rustWelding.getLastError() ?? "no error found")") return } diff --git a/Tests/OfflineTests/ZcashRustBackendTests.swift b/Tests/OfflineTests/ZcashRustBackendTests.swift index 067dcbd6..d4728e0b 100644 --- a/Tests/OfflineTests/ZcashRustBackendTests.swift +++ b/Tests/OfflineTests/ZcashRustBackendTests.swift @@ -71,10 +71,13 @@ class ZcashRustBackendTests: XCTestCase { .deriveFullViewingKey() ] - guard try ZcashRustBackend.initAccountsTable(dbData: dbData!, ufvks: ufvks, networkType: networkType) else { + do { + try ZcashRustBackend.initAccountsTable(dbData: dbData!, ufvks: ufvks, networkType: networkType) + } catch { XCTFail("failed with error: \(String(describing: ZcashRustBackend.lastError()))") return } + XCTAssertNotNil( try ZcashRustBackend.createAccount( dbData: dbData!, diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 588bb46f..9a79e980 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -55,6 +55,10 @@ extension LightWalletServiceMockResponse { } class MockRustBackend: ZcashRustBackendWelding { + static func initAccountsTable(dbData: URL, ufvks: [ZcashLightClientKit.UnifiedFullViewingKey], networkType: ZcashLightClientKit.NetworkType) throws { + + } + static func createToAddress(dbData: URL, usk: ZcashLightClientKit.UnifiedSpendingKey, to address: String, value: Int64, memo: ZcashLightClientKit.MemoBytes?, spendParamsPath: String, outputParamsPath: String, networkType: ZcashLightClientKit.NetworkType) -> Int64 { -1 } diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index a9761a27..eb72d20d 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.17.0-alpha.2' + s.version = '0.17.0-alpha.3' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC