diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift index c0f7b214..37e16d11 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/CompactBlockStorage.swift @@ -11,6 +11,7 @@ import SQLite protocol ConnectionProvider { func connection() throws -> Connection + func close() } class CompactBlockStorage: CompactBlockDAO { @@ -29,6 +30,11 @@ class CompactBlockStorage: CompactBlockDAO { private func dataColumn() -> Expression { Expression("data") } + + func closeDBConnection() { + dbProvider.close() + } + func createTable() throws { do { let compactBlocks = compactBlocksTable() @@ -91,6 +97,21 @@ extension CompactBlockStorage: CompactBlockRepository { } return try await task.value } + + func latestBlock() throws -> ZcashCompactBlock { + let dataColumn = self.dataColumn() + let heightColumn = self.heightColumn() + let query = compactBlocksTable() + .select(dataColumn, heightColumn) + .order(heightColumn.desc) + .limit(1) + + guard let blockData = try dbProvider.connection().prepare(query).first(where: { _ in return true }) else { + throw StorageError.latestBlockNotFound + } + + return ZcashCompactBlock(height: Int(blockData[heightColumn]), data: Data(blob: blockData[dataColumn])) + } func write(blocks: [ZcashCompactBlock]) async throws { let task = Task(priority: .userInitiated) { diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageError.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageError.swift index 9932f81e..ee1b725f 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageError.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageError.swift @@ -17,6 +17,7 @@ enum StorageError: Error { case malformedEntity(fields: [String]?) case transactionFailed(underlyingError: Error) case invalidMigrationVersion(version: Int32) + case latestBlockNotFound case migrationFailed(underlyingError: Error) case migrationFailedWithMessage(message: String) } diff --git a/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageManager.swift b/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageManager.swift index 9b9f2c36..12d79568 100644 --- a/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageManager.swift +++ b/Sources/ZcashLightClientKit/Block/DatabaseStorage/StorageManager.swift @@ -70,4 +70,8 @@ class SimpleConnectionProvider: ConnectionProvider { } return conn } + + func close() { + self.db = nil + } } diff --git a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift index e6349df1..3c909976 100644 --- a/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift +++ b/Sources/ZcashLightClientKit/Block/Downloader/BlockDownloader.swift @@ -58,7 +58,7 @@ public protocol CompactBlockDownloading { Non-blocking */ func latestBlockHeightAsync() async throws -> BlockHeight - + /** Gets the transaction for the Id given - Parameter txId: Data representing the transaction Id diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index b9e9ecc1..1045945a 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -598,11 +598,17 @@ public actor CompactBlockProcessor { targetHeight: range.upperBound ) } + + try storage.createTable() + try await compactBlockValidation() try await compactBlockBatchScanning(range: range) try await compactBlockEnhancement(range: range) try await fetchUnspentTxOutputs(range: range) + try await removeCacheDB() } catch { + LoggerProxy.error("Sync failed with error: \(error)") + if !(Task.isCancelled) { await fail(error) } else { @@ -841,6 +847,30 @@ public actor CompactBlockProcessor { userInfo: nil ) } + + private func removeCacheDB() async throws { + let latestBlock: ZcashCompactBlock + do { + latestBlock = try storage.latestBlock() + } catch let error { + // If we don't have anything downloaded we don't need to remove DB and we also don't want to throw error and error out whole sync process. + if let err = error as? StorageError, case .latestBlockNotFound = err { + return + } else { + throw error + } + } + + storage.closeDBConnection() + try FileManager.default.removeItem(at: config.cacheDb) + try storage.createTable() + + // Latest downloaded block needs to be preserved because after the sync process is interrupted it must be correctly resumed. And for that + // we need correct information which was downloaded as latest. + try await storage.write(blocks: [latestBlock]) + + LoggerProxy.info("Cache removed") + } private func setTimer() async { let interval = self.config.blockPollInterval @@ -1142,8 +1172,6 @@ extension CompactBlockProcessor { ) let lastDownloadedBlockHeight = try downloader.lastDownloadedBlockHeight() - let lastScannedHeight = try transactionRepository.lastScannedHeight() - let latestBlockheight = try service.latestBlockHeight() // Syncing process can be interrupted in any phase. And here it must be detected in which phase is syncing process. @@ -1153,7 +1181,7 @@ extension CompactBlockProcessor { latestDownloadedBlockHeight = max(config.walletBirthday, lastDownloadedBlockHeight) } else { // Here all the blocks are downloaded and last scan height should be then used to compute processing range. - latestDownloadedBlockHeight = max(config.walletBirthday, lastScannedHeight) + latestDownloadedBlockHeight = max(config.walletBirthday, try transactionRepository.lastScannedHeight()) } diff --git a/Sources/ZcashLightClientKit/DAO/CompactBlockDAO.swift b/Sources/ZcashLightClientKit/DAO/CompactBlockDAO.swift index e24b5bc0..72eb934d 100644 --- a/Sources/ZcashLightClientKit/DAO/CompactBlockDAO.swift +++ b/Sources/ZcashLightClientKit/DAO/CompactBlockDAO.swift @@ -9,6 +9,8 @@ import Foundation protocol CompactBlockDAO { + func closeDBConnection() + func createTable() throws func insert(_ block: ZcashCompactBlock) throws diff --git a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift index 9621369f..a9551188 100644 --- a/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift +++ b/Sources/ZcashLightClientKit/Repository/CompactBlockRepository.swift @@ -20,9 +20,8 @@ extension ZcashCompactBlock: Encodable { } protocol CompactBlockRepository { /** - Gets the highest block that is currently stored. + Gets the height of the highest block that is currently stored. */ - func latestHeight() throws -> BlockHeight /** @@ -31,6 +30,11 @@ protocol CompactBlockRepository { */ func latestHeightAsync() async throws -> BlockHeight + /** + Gets the block with the highest height that is currently stored. + */ + func latestBlock() throws -> ZcashCompactBlock + /** Write the given blocks to this store, which may be anything from an in-memory cache to a DB. Non-Blocking diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index e88c4b69..2dc1b803 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -153,11 +153,7 @@ public protocol Synchronizer { /// - from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction /// - limit: the maximum amount of items this should return if available /// - Returns: an array with the given Transactions or nil - func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]? - - /// Returns the latest downloaded height from the compact block cache - func latestDownloadedHeight() async throws -> BlockHeight - + func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]? /// Returns the latest block height from the provided Lightwallet endpoint func latestHeight(result: @escaping (Result) -> Void) diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 0c9921b0..4c22a941 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -569,10 +569,6 @@ public class SDKSynchronizer: Synchronizer { PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all) } - public func latestDownloadedHeight() async throws -> BlockHeight { - try await blockProcessor.downloader.lastDownloadedBlockHeight() - } - public func latestHeight(result: @escaping (Result) -> Void) { Task { do { diff --git a/Tests/DarksideTests/AdvancedReOrgTests.swift b/Tests/DarksideTests/AdvancedReOrgTests.swift index 5b8f6068..4b8d3a8e 100644 --- a/Tests/DarksideTests/AdvancedReOrgTests.swift +++ b/Tests/DarksideTests/AdvancedReOrgTests.swift @@ -1308,8 +1308,8 @@ class AdvancedReOrgTests: XCTestCase { wait(for: [firstSyncExpectation], timeout: 500) - let latestDownloadedHeight = try await coordinator.synchronizer.latestDownloadedHeight() - XCTAssertEqual(latestDownloadedHeight, birthday + fullSyncLength) + let latestScannedHeight = coordinator.synchronizer.latestScannedHeight + XCTAssertEqual(latestScannedHeight, birthday + fullSyncLength) } func handleError(_ error: Error?) { diff --git a/Tests/TestUtils/FakeStorage.swift b/Tests/TestUtils/FakeStorage.swift index 108f5efc..bce6ae3b 100644 --- a/Tests/TestUtils/FakeStorage.swift +++ b/Tests/TestUtils/FakeStorage.swift @@ -25,7 +25,11 @@ class ZcashConsoleFakeStorage: CompactBlockRepository { func latestHeight() throws -> Int { return self.latestBlockHeight } - + + func latestBlock() throws -> ZcashLightClientKit.ZcashCompactBlock { + return ZcashCompactBlock(height: latestBlockHeight, data: Data()) + } + func rewind(to height: BlockHeight) throws { fakeRewind(to: height) } diff --git a/Tests/TestUtils/TestDbBuilder.swift b/Tests/TestUtils/TestDbBuilder.swift index 6f1375bb..cb38ea3e 100644 --- a/Tests/TestUtils/TestDbBuilder.swift +++ b/Tests/TestUtils/TestDbBuilder.swift @@ -116,17 +116,25 @@ class TestDbBuilder { } } -struct InMemoryDbProvider: ConnectionProvider { +class InMemoryDbProvider: ConnectionProvider { var readonly: Bool - var conn: Connection + var conn: Connection? init(readonly: Bool = false) throws { self.readonly = readonly - self.conn = try Connection(.inMemory, readonly: readonly) } func connection() throws -> Connection { - self.conn + guard let conn else { + let newConnection = try Connection(.inMemory, readonly: readonly) + self.conn = newConnection + return newConnection + } + return conn + } + + func close() { + self.conn = nil } }