Skip to content

Commit

Permalink
Use zcash_client_sqlite to manage migrations for the wallet db.
Browse files Browse the repository at this point in the history
This change removes responsibility for maintaining the state of
the wallet database from `ZcashLightClientKit` in favor of using
the migration system now provided by librustzcash. This will help
to ensure that the structure of the database is kept consistent with
the functions that query and update the database state.

Co-authored-by: Francisco Gindre <francisco.gindre@gmail.com>
  • Loading branch information
nuttycom and pacu committed Aug 24, 2022
1 parent 739c7a7 commit 905ee40
Show file tree
Hide file tree
Showing 25 changed files with 296 additions and 357 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@
},
{
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"repositoryURL": "https://github.com/nuttycom/zcash-light-client-ffi.git",
"state": {
"branch": null,
"revision": "b7e8a2abab84c44046b4afe4ee4522a0fa2fcc7f",
"version": "0.0.3"
"branch": "bin/librustzcash_0_7-migrations",
"revision": "dbf7ac51a7de0bbc2c1088973236d68ceb752361",
"version": null
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
loggerProxy: loggerProxy
)

try! wallet.initialize()
_ = try! wallet.initialize(with: DemoAppConfig.seed)
var storage = SampleStorage.shared
storage!.seed = Data(DemoAppConfig.seed)
storage!.privateKey = try! DerivationTool(networkType: kZcashNetwork.networkType)
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,18 @@ class DerivationToolViewController: UIViewController {
throw DerivationErrors.couldNotDeriveSpendingKeys(underlyingError: DerivationErrors.unknown)
}

guard let viewingKey = try derivationTool.deriveViewingKeys(seed: seedBytes, numberOfAccounts: 1).first else {
guard let viewingKey = try derivationTool.deriveUnifiedFullViewingKeysFromSeed(seedBytes, numberOfAccounts: 1).first else {
throw DerivationErrors.couldNotDeriveViewingKeys(underlyingError: DerivationErrors.unknown)
}

let shieldedAddress = try derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
let unifiedAddress = try derivationTool.deriveUnifiedAddress(viewingKey: viewingKey.encoding)

let transparentAddress = try derivationTool.deriveTransparentAddress(seed: seedBytes)

updateLabels(
spendingKey: spendingKey,
viewingKey: viewingKey,
shieldedAddress: shieldedAddress,
viewingKey: viewingKey.encoding,
shieldedAddress: unifiedAddress,
transaparentAddress: transparentAddress
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit
import ZcashLightClientKit
class GetAddressViewController: UIViewController {
@IBOutlet weak var zAddressLabel: UILabel!
@IBOutlet weak var unifiedAddressLabel: UILabel!
@IBOutlet weak var tAddressLabel: UILabel!
@IBOutlet weak var spendingKeyLabel: UILabel! // THIS SHOULD BE SUPER SECRET!!!!!

Expand All @@ -18,16 +18,16 @@ class GetAddressViewController: UIViewController {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
// Do any additional setup after loading the view.

zAddressLabel.text = (try? derivationTool.deriveShieldedAddress(seed: DemoAppConfig.seed, accountIndex: 0)) ?? "No Addresses found"
unifiedAddressLabel.text = (try? derivationTool.deriveUnifiedAddress(seed: DemoAppConfig.seed, accountIndex: 0)) ?? "No Addresses found"
tAddressLabel.text = (try? derivationTool.deriveTransparentAddress(seed: DemoAppConfig.seed)) ?? "could not derive t-address"
spendingKeyLabel.text = SampleStorage.shared.privateKey ?? "No Spending Key found"
zAddressLabel.addGestureRecognizer(
unifiedAddressLabel.addGestureRecognizer(
UITapGestureRecognizer(
target: self,
action: #selector(addressTapped(_:))
)
)
zAddressLabel.isUserInteractionEnabled = true
unifiedAddressLabel.isUserInteractionEnabled = true

tAddressLabel.isUserInteractionEnabled = true
tAddressLabel.addGestureRecognizer(
Expand Down Expand Up @@ -70,8 +70,7 @@ class GetAddressViewController: UIViewController {
@IBAction func addressTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard")

UIPasteboard.general.string = try? DerivationTool(networkType: kZcashNetwork.networkType)
.deriveShieldedAddress(seed: DemoAppConfig.seed, accountIndex: 0)
UIPasteboard.general.string = unifiedAddressLabel.text

let alert = UIAlertController(
title: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ class GetUTXOsViewController: UIViewController {
let derivationTool = DerivationTool(networkType: kZcashNetwork.networkType)
// swiftlint:disable:next force_unwrapping
let spendingKey = try derivationTool.deriveSpendingKeys(seed: seed, numberOfAccounts: 1).first!
let transparentSecretKey = try derivationTool.deriveTransparentPrivateKey(seed: seed)
let transparentSecretKey = try derivationTool.deriveTransparentAccountPrivateKey(seed: seed, account: 0)

KRProgressHUD.showMessage("🛡 Shielding 🛡")

AppDelegate.shared.sharedSynchronizer.shieldFunds(
spendingKey: spendingKey,
transparentSecretKey: transparentSecretKey,
transparentAccountPrivateKey: transparentSecretKey,
memo: "shielding is fun!",
from: 0,
resultBlock: { result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class SendViewController: UIViewController {
super.viewDidLoad()
synchronizer = AppDelegate.shared.sharedSynchronizer
// swiftlint:disable:next force_try
try! synchronizer.prepare()
_ = try! synchronizer.prepare(with: DemoAppConfig.seed)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
self.view.addGestureRecognizer(tapRecognizer)
setUp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class SyncBlocksViewController: UIViewController {

let wallet = Initializer.shared
// swiftlint:disable:next force_try
try! wallet.initialize()
_ = try! wallet.initialize(with: DemoAppConfig.seed)
processor = CompactBlockProcessor(initializer: wallet)
statusLabel.text = textFor(state: processor?.state ?? .stopped)
progressBar.progress = 0
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
"package": "libzcashlc",
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"state": {
"branch": "librustzcash_0_7",
"revision": "127a6b9d19841fea232bf0d0887ae5f2cad8df04",
"branch": "librustzcash_0_7_build_test",
"revision": "5a48541c1da48eb082defaf0fa127657f4f99094",
"version": null
}
}
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.8.0"),
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0"),
.package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi.git", branch: "librustzcash_0_7"),
.package(name:"libzcashlc", url: "https://github.com/zcash-hackworks/zcash-light-client-ffi.git", branch: "librustzcash_0_7_build_test"),
],
targets: [
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import Foundation
import SQLite

class MigrationManager {
enum DataDbMigrations: Int32 {
case none = 0
case version1 = 1
}

enum CacheDbMigration: Int32 {
case none = 0
}
Expand All @@ -22,136 +17,27 @@ class MigrationManager {
case none = 0
}

static let latestDataDbMigrationVersion: Int32 = DataDbMigrations.version1.rawValue
static let latestCacheDbMigrationVersion: Int32 = CacheDbMigration.none.rawValue
static let latestPendingDbMigrationVersion: Int32 = PendingDbMigration.none.rawValue

var cacheDb: ConnectionProvider
var dataDb: ConnectionProvider
var pendingDb: ConnectionProvider
var network: NetworkType

init(
cacheDbConnection: ConnectionProvider,
dataDbConnection: ConnectionProvider,
pendingDbConnection: ConnectionProvider,
networkType: NetworkType
) {
self.cacheDb = cacheDbConnection
self.dataDb = dataDbConnection
self.pendingDb = pendingDbConnection
self.network = networkType
}

func performMigration(ufvks: [UnifiedFullViewingKey]) throws {
// TODO: DataDB migrations will be handled by rustBackend.initDataDb
// once https://github.com/zcash/librustzcash/pull/600 merges, and in
// the interim the old migrations here will fail if we try to run them
// due to changes to table column names in zcash/librustzcash.
//try migrateDataDb(ufvks: ufvks)
try migrateCacheDb()
try migratePendingDb()
}

func performVersion1Migration(viewingKeys: [UnifiedFullViewingKey]) throws {
LoggerProxy.debug("Starting migration version 1 from viewing Keys")
let db = try self.dataDb.connection()

let placeholder = "deriveMe"
let migrationStatement =
"""
BEGIN TRANSACTION;
PRAGMA foreign_keys = OFF;
DROP TABLE utxos;
CREATE TABLE IF NOT EXISTS utxos(
id_utxo INTEGER PRIMARY KEY,
address TEXT NOT NULL,
prevout_txid BLOB NOT NULL,
prevout_idx INTEGER NOT NULL,
script BLOB NOT NULL,
value_zat INTEGER NOT NULL,
height INTEGER NOT NULL,
spent_in_tx INTEGER,
FOREIGN KEY (spent_in_tx) REFERENCES transactions(id_tx),
CONSTRAINT tx_outpoint UNIQUE (prevout_txid, prevout_idx)
);
CREATE TABLE IF NOT EXISTS accounts_new (
account INTEGER PRIMARY KEY,
extfvk TEXT NOT NULL,
address TEXT NOT NULL,
transparent_address TEXT NOT NULL
);
INSERT INTO accounts_new SELECT account, extfvk, address, '\(placeholder)' FROM accounts;
DROP TABLE accounts;
ALTER TABLE accounts_new RENAME TO accounts;
PRAGMA user_version = 1;
PRAGMA foreign_keys = ON;
COMMIT TRANSACTION;
"""
LoggerProxy.debug("db.execute(\"\(migrationStatement)\")")
try db.execute(migrationStatement)

LoggerProxy.debug("db.run() succeeded")

// derive transparent (shielding) addresses
let accountsDao = AccountSQDAO(dbProvider: self.dataDb)

let accounts = try accountsDao.getAll()

guard !accounts.isEmpty else {
LoggerProxy.debug("no existing accounts found while performing this migration")
return
}

guard accounts.count == viewingKeys.count else {
let message = """
Number of accounts found and viewing keys provided don't match.
Found \(accounts.count) account(s) and there were \(viewingKeys.count) Viewing key(s) provided.
"""
LoggerProxy.debug(message)
throw StorageError.migrationFailedWithMessage(message: message)
}

let derivationTool = DerivationTool(networkType: self.network)

for tuple in zip(accounts, viewingKeys) {
// TODO: Should the v1 migration be changed to "migrate from pre-v1 database to v2"?
let tAddr = try derivationTool.deriveTransparentAddressFromPublicKey(tuple.1.encoding)
var account = tuple.0
account.transparentAddress = tAddr
try accountsDao.update(account)
}

// sanity check
guard try accountsDao.getAll().first(where: { $0.transparentAddress == placeholder }) == nil else {
LoggerProxy.error("Accounts Migration performed but the transparent addresses were not derived")
throw StorageError.migrationFailed(underlyingError: KeyDerivationErrors.unableToDerive)
}
}

func performVersion1Migration(_ seedBytes: [UInt8]) throws {
LoggerProxy.debug("Starting migration version 1")

// derive transparent (shielding) addresses
let accountsDao = AccountSQDAO(dbProvider: self.dataDb)

let accounts = try accountsDao.getAll()

guard !accounts.isEmpty else {
LoggerProxy.debug("no existing accounts found while performing this migration")
return
}

let derivationTool = DerivationTool(networkType: self.network)

let ufvks = try derivationTool.deriveUnifiedFullViewingKeysFromSeed(seedBytes, numberOfAccounts: accounts.count)

try performVersion1Migration(viewingKeys: ufvks)
}
}

private extension MigrationManager {
Expand Down Expand Up @@ -186,31 +72,6 @@ private extension MigrationManager {
LoggerProxy.debug("Cache Db - no migration needed")
}
}

func migrateDataDb(ufvks: [UnifiedFullViewingKey]) throws {
let currentDataDbVersion = try dataDb.connection().getUserVersion()
LoggerProxy.debug(
"Attempting to perform migration for data Db - currentVersion: \(currentDataDbVersion)." +
"Latest version is: \(Self.latestDataDbMigrationVersion)"
)

if currentDataDbVersion < Self.latestDataDbMigrationVersion {
for dbVersion in (currentDataDbVersion + 1) ... Self.latestDataDbMigrationVersion {
guard let version = DataDbMigrations.init(rawValue: dbVersion) else {
LoggerProxy.error("failed to determine migration version")
throw StorageError.invalidMigrationVersion(version: dbVersion)
}
switch version {
case .version1:
try performVersion1Migration(viewingKeys: ufvks)
case .none:
break
}
}
} else {
LoggerProxy.debug("Data Db - no migration needed")
}
}
}

extension Connection {
Expand Down
3 changes: 1 addition & 2 deletions Sources/ZcashLightClientKit/Entity/AccountEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ extension Account: UnifiedAddress {
get {
address
}
// swiftlint:disable unused_setter_value
set {
address = encoding
address = newValue
}
}
}
Expand Down
17 changes: 12 additions & 5 deletions Sources/ZcashLightClientKit/Initializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ The [cash.z.wallet.sdk.block.CompactBlockProcessor] handles all the remaining Ru
functionality, related to processing blocks.
*/
public class Initializer {

public enum InitializationResult {
case success
case seedRequired
}

private(set) var rustBackend: ZcashRustBackendWelding.Type
private(set) var alias: String
private(set) var endpoint: LightWalletEndpoint
Expand Down Expand Up @@ -188,17 +194,17 @@ public class Initializer {
- Parameters:
- viewingKeys: Extended Full Viewing Keys to initialize the DBs with
*/
public func initialize() throws {
public func initialize(with seed: [UInt8]?) throws -> InitializationResult {
do {
try storage.createTable()
} catch {
throw InitializerError.cacheDbInitFailed
}

do {
try rustBackend.initDataDb(dbData: dataDbURL, networkType: network.networkType)
} catch RustWeldingError.dataDbNotEmpty {
// this is fine
if case .seedRequired = try rustBackend.initDataDb(dbData: dataDbURL, seed: seed, networkType: network.networkType) {
return .seedRequired
}
} catch {
throw InitializerError.dataDbInitFailed
}
Expand Down Expand Up @@ -242,12 +248,13 @@ public class Initializer {

let migrationManager = MigrationManager(
cacheDbConnection: SimpleConnectionProvider(path: cacheDbURL.path),
dataDbConnection: SimpleConnectionProvider(path: dataDbURL.path),
pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path),
networkType: self.network.networkType
)

try migrationManager.performMigration(ufvks: viewingKeys)

return .success
}

/**
Expand Down
Loading

0 comments on commit 905ee40

Please # to comment.