diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ebb7cf..360e55a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this library will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [Unreleased] + +## Changed + +### [#1363] Account balances in the SynchronizerState +`shieldedBalance: WalletBalance` has been replaced with `accountBalances: AccountBalance`. `AccountBalance` provides the same values as `shieldedBalance` but adds up a pending changes. Under the hood this calls rust's `getWalletSummary` which improved also the syncing initial values of % and balances. + # 2.0.8 - 2024-01-30 Adopt `zcash-light-client-ffi 0.5.1`. This fixes a serialization problem diff --git a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift index fe7a4da7..6779bcf6 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift @@ -17,7 +17,7 @@ final class ScanAction { let rustBackend: ZcashRustBackendWelding let latestBlocksDataProvider: LatestBlocksDataProvider let logger: Logger - var progressReportReducer = Constants.reportDelay + var progressReportReducer = 0 init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) { self.configProvider = configProvider @@ -99,7 +99,7 @@ extension ScanAction: Action { } func stop() async { - progressReportReducer = Constants.reportDelay + progressReportReducer = 0 } } diff --git a/Sources/ZcashLightClientKit/Model/WalletSummary.swift b/Sources/ZcashLightClientKit/Model/WalletSummary.swift index 0b8db4c4..6f837cea 100644 --- a/Sources/ZcashLightClientKit/Model/WalletSummary.swift +++ b/Sources/ZcashLightClientKit/Model/WalletSummary.swift @@ -7,19 +7,23 @@ import Foundation -struct PoolBalance: Equatable { - let spendableValue: Zatoshi - let changePendingConfirmation: Zatoshi - let valuePendingSpendability: Zatoshi +public struct PoolBalance: Equatable { + public let spendableValue: Zatoshi + public let changePendingConfirmation: Zatoshi + public let valuePendingSpendability: Zatoshi - func total() -> Zatoshi { + static let zero = PoolBalance(spendableValue: .zero, changePendingConfirmation: .zero, valuePendingSpendability: .zero) + + public func total() -> Zatoshi { self.spendableValue + self.changePendingConfirmation + self.valuePendingSpendability } } -struct AccountBalance: Equatable { - let saplingBalance: PoolBalance - let unshielded: Zatoshi +public struct AccountBalance: Equatable { + public let saplingBalance: PoolBalance + public let unshielded: Zatoshi + + static let zero = AccountBalance(saplingBalance: .zero, unshielded: .zero) } struct ScanProgress: Equatable { diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index fa93e948..2183004b 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -35,8 +35,8 @@ public struct SynchronizerState: Equatable { /// given how application lifecycle varies between OS Versions, platforms, etc. /// SyncSessionIDs are provided to users public var syncSessionID: UUID - /// shielded balance known to this synchronizer given the data that has processed locally - public var shieldedBalance: WalletBalance + /// account (shielded) balances known to this synchronizer given the data that has processed locally + public var accountBalances: AccountBalance /// transparent balance known to this synchronizer given the data that has processed locally public var transparentBalance: WalletBalance /// status of the whole sync process @@ -49,7 +49,7 @@ public struct SynchronizerState: Equatable { public static var zero: SynchronizerState { SynchronizerState( syncSessionID: .nullID, - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .unprepared, latestBlockHeight: .zero @@ -58,13 +58,13 @@ public struct SynchronizerState: Equatable { init( syncSessionID: UUID, - shieldedBalance: WalletBalance, + accountBalances: AccountBalance, transparentBalance: WalletBalance, internalSyncStatus: InternalSyncStatus, latestBlockHeight: BlockHeight ) { self.syncSessionID = syncSessionID - self.shieldedBalance = shieldedBalance + self.accountBalances = accountBalances self.transparentBalance = transparentBalance self.internalSyncStatus = internalSyncStatus self.latestBlockHeight = latestBlockHeight @@ -249,6 +249,11 @@ public protocol Synchronizer: AnyObject { /// - Returns: balance in `Zatoshi` func getShieldedVerifiedBalance(accountIndex: Int) async throws -> Zatoshi + /// get account balances from the given account index + /// - Parameter accountIndex: the index of the account + /// - Returns: balances + func getAccountBalances(accountIndex: Int) async throws -> AccountBalance? + /// Rescans the known blocks with the current keys. /// /// `rewind(policy:)` can be called anytime. If the sync process is in progress then it is stopped first. In this case, it make some significant diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 0bfb2c5f..3ba1e74f 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -164,7 +164,8 @@ public class SDKSynchronizer: Synchronizer { await blockProcessor.start(retry: retry) case .stopped, .synced, .disconnected, .error: - await updateStatus(.syncing(0)) + let syncProgress = (try? await initializer.rustBackend.getWalletSummary()?.scanProgress?.progress()) ?? 0 + await updateStatus(.syncing(syncProgress)) await blockProcessor.start(retry: retry) } } @@ -413,6 +414,10 @@ public class SDKSynchronizer: Synchronizer { return try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height) } + public func getAccountBalances(accountIndex: Int = 0) async throws -> AccountBalance? { + try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)] + } + public func getShieldedBalance(accountIndex: Int = 0) async throws -> Zatoshi { try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)]? .saplingBalance.total() ?? Zatoshi.zero @@ -530,12 +535,9 @@ public class SDKSynchronizer: Synchronizer { // MARK: notify state private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState { - return await SynchronizerState( + await SynchronizerState( syncSessionID: syncSession.value, - shieldedBalance: WalletBalance( - verified: (try? await getShieldedVerifiedBalance()) ?? .zero, - total: (try? await getShieldedBalance()) ?? .zero - ), + accountBalances: (try? await getAccountBalances()) ?? .zero, transparentBalance: (try? await blockProcessor.getTransparentBalance(accountIndex: 0)) ?? .zero, internalSyncStatus: status, latestBlockHeight: latestBlocksDataProvider.latestBlockHeight diff --git a/Tests/DarksideTests/SynchronizerDarksideTests.swift b/Tests/DarksideTests/SynchronizerDarksideTests.swift index fb446535..85f629a7 100644 --- a/Tests/DarksideTests/SynchronizerDarksideTests.swift +++ b/Tests/DarksideTests/SynchronizerDarksideTests.swift @@ -190,35 +190,35 @@ class SynchronizerDarksideTests: ZcashTestCase { let expectedStates: [SynchronizerState] = [ SynchronizerState( syncSessionID: .nullID, - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .unprepared, latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(0), latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(0.9), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(1.0), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .synced, latestBlockHeight: 663189 @@ -267,36 +267,36 @@ class SynchronizerDarksideTests: ZcashTestCase { let expectedStates: [SynchronizerState] = [ SynchronizerState( syncSessionID: .nullID, - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .unprepared, latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(0), latestBlockHeight: 0 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(0.9), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: .syncing(1.0), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[0], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), - transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), + accountBalances: .zero, + transparentBalance: .zero, internalSyncStatus: .synced, latestBlockHeight: 663189 ) @@ -329,29 +329,29 @@ class SynchronizerDarksideTests: ZcashTestCase { let secondBatchOfExpectedStates: [SynchronizerState] = [ SynchronizerState( syncSessionID: uuids[1], - shieldedBalance: WalletBalance(verified: Zatoshi(100000), total: Zatoshi(200000)), - transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), + accountBalances: .zero, + transparentBalance: .zero, internalSyncStatus: .syncing(0), latestBlockHeight: 663189 ), SynchronizerState( syncSessionID: uuids[1], - shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), - transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), + accountBalances: .zero, + transparentBalance: .zero, internalSyncStatus: .syncing(0.9), latestBlockHeight: 663200 ), SynchronizerState( syncSessionID: uuids[1], - shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), - transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), + accountBalances: .zero, + transparentBalance: .zero, internalSyncStatus: .syncing(1.0), latestBlockHeight: 663200 ), SynchronizerState( syncSessionID: uuids[1], - shieldedBalance: WalletBalance(verified: Zatoshi(200000), total: Zatoshi(200000)), - transparentBalance: WalletBalance(verified: Zatoshi(0), total: Zatoshi(0)), + accountBalances: .zero, + transparentBalance: .zero, internalSyncStatus: .synced, latestBlockHeight: 663200 ) diff --git a/Tests/OfflineTests/SynchronizerOfflineTests.swift b/Tests/OfflineTests/SynchronizerOfflineTests.swift index 41715263..bdb8cec5 100644 --- a/Tests/OfflineTests/SynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/SynchronizerOfflineTests.swift @@ -472,7 +472,7 @@ class SynchronizerOfflineTests: ZcashTestCase { func synchronizerState(for internalSyncStatus: InternalSyncStatus) -> SynchronizerState { SynchronizerState( syncSessionID: .nullID, - shieldedBalance: .zero, + accountBalances: .zero, transparentBalance: .zero, internalSyncStatus: internalSyncStatus, latestBlockHeight: .zero diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 9afb8f6e..ba8805fc 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.1.4 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import Combine @testable import ZcashLightClientKit @@ -1671,6 +1671,30 @@ class SynchronizerMock: Synchronizer { } } + // MARK: - getAccountBalances + + var getAccountBalancesAccountIndexThrowableError: Error? + var getAccountBalancesAccountIndexCallsCount = 0 + var getAccountBalancesAccountIndexCalled: Bool { + return getAccountBalancesAccountIndexCallsCount > 0 + } + var getAccountBalancesAccountIndexReceivedAccountIndex: Int? + var getAccountBalancesAccountIndexReturnValue: AccountBalance? + var getAccountBalancesAccountIndexClosure: ((Int) async throws -> AccountBalance?)? + + func getAccountBalances(accountIndex: Int) async throws -> AccountBalance? { + if let error = getAccountBalancesAccountIndexThrowableError { + throw error + } + getAccountBalancesAccountIndexCallsCount += 1 + getAccountBalancesAccountIndexReceivedAccountIndex = accountIndex + if let closure = getAccountBalancesAccountIndexClosure { + return try await closure(accountIndex) + } else { + return getAccountBalancesAccountIndexReturnValue + } + } + // MARK: - rewind var rewindCallsCount = 0 diff --git a/Tests/TestUtils/Sourcery/generateMocks.sh b/Tests/TestUtils/Sourcery/generateMocks.sh index c1326ec8..b25143e5 100755 --- a/Tests/TestUtils/Sourcery/generateMocks.sh +++ b/Tests/TestUtils/Sourcery/generateMocks.sh @@ -3,7 +3,7 @@ scriptDir=${0:a:h} cd "${scriptDir}" -sourcery_version=2.1.4 +sourcery_version=2.1.7 if which sourcery >/dev/null; then if [[ $(sourcery --version) != $sourcery_version ]]; then diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index c0d2b1d3..9957adc7 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -145,7 +145,7 @@ extension SynchronizerState { static var mock: SynchronizerState { SynchronizerState( syncSessionID: .nullID, - shieldedBalance: WalletBalance(verified: Zatoshi(100), total: Zatoshi(200)), + accountBalances: .zero, transparentBalance: WalletBalance(verified: Zatoshi(200), total: Zatoshi(300)), internalSyncStatus: .syncing(0), latestBlockHeight: 222222