diff --git a/CHANGELOG.md b/CHANGELOG.md index d466553a..de2c712c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,21 @@ - `CompactBlockProcessor.ProcessorInfo.isSyncing`. Use `Synchronizer.status` instead. - `CompactBlockProcessor.ProcessorInfo.syncProgress`. Use `Synchronizer.progress` instead. - `alsoClearBlockCache` parameter from rewind functions of `Synchronizer` and `CompactBlockProcessor` as it take no - affect on the current rewind functionality result. + effect on the current rewind functionality result. +- Internally, we removed access to the shared block table from the Kotlin layer, which resulted in eliminating these + APIs: + - `SdkSynchornizer.findBlockHash()` + - `SdkSynchornizer.findBlockHashAsHex()` + +### Changed +- `CompactBlockProcessor.quickRewind()` and `CompactBlockProcessor.rewindToNearestHeight()` now might fail due to + internal changes in getting scanned height. Thus, these functions return `Boolean` results. + +### Fixed +- `Synchronizer.getMemos()` now correctly returns a flow of strings for sent and received transactions. Issue **#1154**. +- `CompactBlockProcessor` now triggers transaction polling while block synchronization is in progress as expected. + Clients will be notified briefly after every new transaction is discovered via `Synchronizer.transactions` API. + Issue **#1170**. ## 1.21.0-beta01 Note: This is the last _1.x_ version release. The upcoming version _2.0_ brings the **Spend-before-Sync** feature, @@ -32,12 +46,6 @@ which speeds up discovering the wallet's spendable balance. - etc. - Checkpoints -### Fixed -- `Synchronizer.getMemos()` now correctly returns a flow of strings for sent and received transactions. Issue **#1154**. -- `CompactBlockProcessor` now triggers transaction polling while block synchronization is in progress as expected. - Clients will be notified briefly after every new transaction is discovered via `Synchronizer.transactions` API. - Issue **#1170**. - ## 1.20.0-beta01 - The SDK internally migrated from `BackendExt` rust backend extension functions to more type-safe `TypesafeBackend`. - `Synchronizer.getMemos()` now internally handles expected `RuntimeException` from the rust layer and transforms it diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt index ed61af6b..d0958510 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt @@ -36,6 +36,17 @@ interface Backend { suspend fun decryptAndStoreTransaction(tx: ByteArray) + /** + * Sets up the internal structure of the data database. + * + * If `seed` is `null`, database migrations will be attempted without it. + * + * @return 0 if successful, 1 if the seed must be provided in order to execute the requested migrations, or -1 + * otherwise. + * + * @throws RuntimeException as a common indicator of the operation failure + */ + @Throws(RuntimeException::class) suspend fun initDataDb(seed: ByteArray?): Int /** diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index 53a8bd2c..ef0a1d37 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -24,7 +24,6 @@ import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator import cash.z.ecc.android.sdk.internal.db.derived.DbDerivedDataRepository import cash.z.ecc.android.sdk.internal.db.derived.DerivedDataDb import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty -import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.ext.tryNull import cash.z.ecc.android.sdk.internal.jni.RustBackend import cash.z.ecc.android.sdk.internal.model.Checkpoint @@ -185,7 +184,7 @@ class SdkSynchronizer private constructor( override val transactions get() = combine(processor.networkHeight, storage.allTransactions) { networkHeight, allTransactions -> - val latestBlockHeight = networkHeight ?: storage.lastScannedHeight() + val latestBlockHeight = networkHeight ?: backend.getMaxScannedHeight() allTransactions.map { TransactionOverview.new(it, latestBlockHeight) } } @@ -341,14 +340,6 @@ class SdkSynchronizer private constructor( // to do with the underlying data // TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682 - suspend fun findBlockHash(height: BlockHeight): ByteArray? { - return storage.findBlockHash(height) - } - - suspend fun findBlockHashAsHex(height: BlockHeight): String? { - return findBlockHash(height)?.toHexReversed() - } - suspend fun getTransactionCount(): Int { return storage.getTransactionCount().toInt() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt index 5e894a18..e301663b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt @@ -4,6 +4,7 @@ import androidx.annotation.VisibleForTesting import cash.z.ecc.android.sdk.BuildConfig import cash.z.ecc.android.sdk.annotation.OpenForTesting import cash.z.ecc.android.sdk.block.processor.model.BatchSyncProgress +import cash.z.ecc.android.sdk.block.processor.model.GetMaxScannedHeightResult import cash.z.ecc.android.sdk.block.processor.model.GetSubtreeRootsResult import cash.z.ecc.android.sdk.block.processor.model.PutSaplingSubtreeRootsResult import cash.z.ecc.android.sdk.block.processor.model.SbSPreparationResult @@ -218,7 +219,10 @@ class CompactBlockProcessor internal constructor( // Clear any undeleted left over block files from previous sync attempts deleteAllBlockFiles( downloader = downloader, - lastKnownHeight = getLastScannedHeight(repository) + lastKnownHeight = when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> null + } ) // Download note commitment tree data from lightwalletd to decide if we communicate with linear @@ -347,7 +351,7 @@ class CompactBlockProcessor internal constructor( stop() } - suspend fun checkErrorResult(failedHeight: BlockHeight) { + suspend fun checkErrorResult(failedHeight: BlockHeight?) { if (consecutiveChainErrors.get() >= RETRIES) { val errorMessage = "ERROR: unable to resolve reorg at height $failedHeight after " + "${consecutiveChainErrors.get()} correction attempts!" @@ -476,8 +480,13 @@ class CompactBlockProcessor internal constructor( // Continue with processing the rest of the ranges } else -> { // An error came - remove persisted but not scanned blocks - val lastScannedHeight = getLastScannedHeight(repository) - downloader.rewindToHeight(lastScannedHeight) + val lastScannedHeight = when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> null + } + lastScannedHeight?.let { + downloader.rewindToHeight(lastScannedHeight) + } deleteAllBlockFiles( downloader = downloader, lastKnownHeight = lastScannedHeight @@ -579,8 +588,13 @@ class CompactBlockProcessor internal constructor( return BlockProcessingResult.RestartSynchronization } else -> { // An error came - remove persisted but not scanned blocks - val lastScannedHeight = getLastScannedHeight(repository) - downloader.rewindToHeight(lastScannedHeight) + val lastScannedHeight = when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> null + } + lastScannedHeight?.let { + downloader.rewindToHeight(lastScannedHeight) + } deleteAllBlockFiles( downloader = downloader, lastKnownHeight = lastScannedHeight @@ -713,7 +727,7 @@ class CompactBlockProcessor internal constructor( object Success : BlockProcessingResult() object Reconnecting : BlockProcessingResult() object RestartSynchronization : BlockProcessingResult() - data class SyncFailure(val failedAtHeight: BlockHeight, val error: Throwable) : BlockProcessingResult() + data class SyncFailure(val failedAtHeight: BlockHeight?, val error: Throwable) : BlockProcessingResult() } /** @@ -767,10 +781,7 @@ class CompactBlockProcessor internal constructor( // TODO [#1127]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1127 @Suppress("NestedBlockDepth") private suspend fun verifySetup() { - // verify that the data is initialized - val error = if (!repository.isInitialized()) { - CompactBlockProcessorException.Uninitialized - } else if (repository.getAccountCount() == 0) { + val error = if (repository.getAccountCount() == 0) { CompactBlockProcessorException.NoAccount } else { // verify that the server is correct @@ -1531,7 +1542,7 @@ class CompactBlockProcessor internal constructor( @VisibleForTesting internal suspend fun deleteAllBlockFiles( downloader: CompactBlockDownloader, - lastKnownHeight: BlockHeight + lastKnownHeight: BlockHeight? ): SyncingResult { Twig.verbose { "Starting to delete all temporary block files" } return if (downloader.compactBlockRepository.deleteAllCompactBlockFiles()) { @@ -1691,8 +1702,26 @@ class CompactBlockProcessor internal constructor( * @return the last scanned height reported by the repository. */ @VisibleForTesting - internal suspend fun getLastScannedHeight(repository: DerivedDataRepository) = - repository.lastScannedHeight() + internal suspend fun getMaxScannedHeight(backend: TypesafeBackend): GetMaxScannedHeightResult { + return runCatching { + backend.getMaxScannedHeight() + }.onSuccess { + Twig.verbose { "Successfully called getMaxScannedHeight with result: $it" } + }.onFailure { + Twig.error { "Failed to call getMaxScannedHeight with result: $it" } + }.fold( + onSuccess = { + if (it == null) { + GetMaxScannedHeightResult.None + } else { + GetMaxScannedHeightResult.Success(it) + } + }, + onFailure = { + GetMaxScannedHeightResult.Failure(it) + } + ) + } /** * Get the height of the first un-enhanced transaction detail from the repository. @@ -1784,14 +1813,14 @@ class CompactBlockProcessor internal constructor( _state.value = newState } - private suspend fun handleChainError(errorHeight: BlockHeight) { - // TODO [#683]: Consider an error object containing hash information - // TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683 + private suspend fun handleChainError(errorHeight: BlockHeight?) { printValidationErrorInfo(errorHeight) - determineLowerBound(errorHeight).let { lowerBound -> - Twig.debug { "Handling chain error at $errorHeight by rewinding to block $lowerBound" } - onChainErrorListener?.invoke(errorHeight, lowerBound) - rewindToNearestHeight(lowerBound) + errorHeight?.let { + determineLowerBound(errorHeight).let { lowerBound -> + Twig.debug { "Handling chain error at $errorHeight by rewinding to block $lowerBound" } + onChainErrorListener?.invoke(errorHeight, lowerBound) + rewindToNearestHeight(lowerBound) + } } } @@ -1813,20 +1842,26 @@ class CompactBlockProcessor internal constructor( /** * Rewind back at least two weeks worth of blocks. */ - suspend fun quickRewind() { - val height = repository.lastScannedHeight() + suspend fun quickRewind(): Boolean { + val height = when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> return false + } val blocksPer14Days = 14.days.inWholeMilliseconds / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt() val twoWeeksBack = BlockHeight.new( network, (height.value - blocksPer14Days).coerceAtLeast(lowerBoundHeight.value) ) - rewindToNearestHeight(twoWeeksBack) + return rewindToNearestHeight(twoWeeksBack) } @Suppress("LongMethod") - suspend fun rewindToNearestHeight(height: BlockHeight) { + suspend fun rewindToNearestHeight(height: BlockHeight): Boolean { processingMutex.withLockLogged("rewindToHeight") { - val lastLocalBlock = repository.lastScannedHeight() + val lastLocalBlock = when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> return false + } val targetHeight = getNearestRewindHeight(height) Twig.debug { @@ -1854,40 +1889,37 @@ class CompactBlockProcessor internal constructor( } } } + return true } /** insightful function for debugging these critical errors */ - private suspend fun printValidationErrorInfo(errorHeight: BlockHeight, count: Int = 11) { + private suspend fun printValidationErrorInfo(errorHeight: BlockHeight?, count: Int = 11) { // Note: blocks are public information so it's okay to print them but, still, let's not unless we're // debugging something if (!BuildConfig.DEBUG) { return } - var errorInfo = fetchValidationErrorInfo(errorHeight) - Twig.debug { "validation failed at block ${errorInfo.errorHeight} with hash: ${errorInfo.hash}" } + if (errorHeight == null) { + Twig.debug { "Validation failed at unspecified block height" } + return + } - errorInfo = fetchValidationErrorInfo(errorHeight + 1) - Twig.debug { "the next block is ${errorInfo.errorHeight} with hash: ${errorInfo.hash}" } + var errorInfo = ValidationErrorInfo(errorHeight) + Twig.debug { "Validation failed at block ${errorInfo.errorHeight}" } + + errorInfo = ValidationErrorInfo(errorHeight + 1) + Twig.debug { "The next block is ${errorInfo.errorHeight}" } Twig.debug { "=================== BLOCKS [$errorHeight..${errorHeight.value + count - 1}]: START ========" } repeat(count) { i -> val height = errorHeight + i val block = downloader.compactBlockRepository.findCompactBlock(height) - // sometimes the initial block was inserted via checkpoint and will not appear in the cache. We can get - // the hash another way. - val checkedHash = block?.hash ?: repository.findBlockHash(height) - Twig.debug { "block: $height\thash=${checkedHash?.toHexReversed()}" } + Twig.debug { "block: $height\thash=${block?.hash?.toHexReversed()}" } } Twig.debug { "=================== BLOCKS [$errorHeight..${errorHeight.value + count - 1}]: END ========" } } - private suspend fun fetchValidationErrorInfo(errorHeight: BlockHeight): ValidationErrorInfo { - val hash = repository.findBlockHash(errorHeight + 1)?.toHexReversed() - - return ValidationErrorInfo(errorHeight, hash) - } - /** * Called for every noteworthy error. * @@ -2045,8 +2077,7 @@ class CompactBlockProcessor internal constructor( ) data class ValidationErrorInfo( - val errorHeight: BlockHeight, - val hash: String? + val errorHeight: BlockHeight ) // diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt new file mode 100644 index 00000000..2eba89b3 --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt @@ -0,0 +1,12 @@ +package cash.z.ecc.android.sdk.block.processor.model + +import cash.z.ecc.android.sdk.model.BlockHeight + +/** + * Internal class for sharing get max scanned height action result. + */ +internal sealed class GetMaxScannedHeightResult { + data class Success(val height: BlockHeight) : GetMaxScannedHeightResult() + data object None : GetMaxScannedHeightResult() + data class Failure(val exception: Throwable) : GetMaxScannedHeightResult() +} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt index 7fa47866..be8046ae 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt @@ -17,7 +17,7 @@ internal sealed class SyncingResult { override fun toString() = "${this::class.java.simpleName} with ${downloadedBlocks?.size ?: "none"} blocks" } interface Failure { - val failedAtHeight: BlockHeight + val failedAtHeight: BlockHeight? val exception: CompactBlockProcessorException fun toBlockProcessingResult(): CompactBlockProcessor.BlockProcessingResult = CompactBlockProcessor.BlockProcessingResult.SyncFailure( @@ -36,7 +36,7 @@ internal sealed class SyncingResult { ) : Failure, SyncingResult() object DeleteSuccess : SyncingResult() data class DeleteFailed( - override val failedAtHeight: BlockHeight, + override val failedAtHeight: BlockHeight?, override val exception: CompactBlockProcessorException ) : Failure, SyncingResult() object EnhanceSuccess : SyncingResult() diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt index 4e806eea..2630a669 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt @@ -80,10 +80,11 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = null ) class FailedReorgRepair(message: String) : CompactBlockProcessorException(message) - object Uninitialized : CompactBlockProcessorException( + class Uninitialized(cause: Throwable? = null) : CompactBlockProcessorException( "Cannot process blocks because the wallet has not been" + " initialized. Verify that the seed phrase was properly created or imported. If so, then this problem" + - " can be fixed by re-importing the wallet." + " can be fixed by re-importing the wallet.", + cause ) object NoAccount : CompactBlockProcessorException( "Attempting to scan without an account. This is probably a setup error or a race condition." @@ -294,7 +295,7 @@ sealed class TransactionEncoderException( " with id $transactionId, does not have any raw data. This is a scenario where the wallet should have " + "thrown an exception but failed to do so." ) - class IncompleteScanException(lastScannedHeight: BlockHeight) : TransactionEncoderException( + class IncompleteScanException(lastScannedHeight: BlockHeight?) : TransactionEncoderException( "Cannot" + " create spending transaction because scanning is incomplete. We must scan up to the" + " latest height to know which consensus rules to apply. However, the last scanned" + diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/BlockTable.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/BlockTable.kt deleted file mode 100644 index 9f04bb33..00000000 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/BlockTable.kt +++ /dev/null @@ -1,88 +0,0 @@ -package cash.z.ecc.android.sdk.internal.db.derived - -import androidx.sqlite.db.SupportSQLiteDatabase -import cash.z.ecc.android.sdk.internal.db.queryAndMap -import cash.z.ecc.android.sdk.model.BlockHeight -import cash.z.ecc.android.sdk.model.ZcashNetwork -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull -import java.util.Locale - -internal class BlockTable(private val zcashNetwork: ZcashNetwork, private val sqliteDatabase: SupportSQLiteDatabase) { - companion object { - - private val SELECTION_MIN_HEIGHT = arrayOf( - String.format( - Locale.ROOT, - "MIN(%s)", // $NON-NLS - BlockTableDefinition.COLUMN_LONG_HEIGHT - ) - ) - - private val SELECTION_MAX_HEIGHT = arrayOf( - String.format( - Locale.ROOT, - "MAX(%s)", // $NON-NLS - BlockTableDefinition.COLUMN_LONG_HEIGHT - ) - ) - - private val SELECTION_BLOCK_HEIGHT = String.format( - Locale.ROOT, - "%s = ?", // $NON-NLS - BlockTableDefinition.COLUMN_LONG_HEIGHT - ) - - private val PROJECTION_COUNT = arrayOf("COUNT(*)") // $NON-NLS - - private val PROJECTION_HASH = arrayOf(BlockTableDefinition.COLUMN_BLOB_HASH) - } - - suspend fun count() = sqliteDatabase.queryAndMap( - BlockTableDefinition.TABLE_NAME, - columns = PROJECTION_COUNT, - cursorParser = { it.getLong(0) } - ).first() - - suspend fun firstScannedHeight(): BlockHeight { - // Note that we assume the Rust layer will add the birthday height as the first block - val heightLong = - sqliteDatabase.queryAndMap( - table = BlockTableDefinition.TABLE_NAME, - columns = SELECTION_MIN_HEIGHT, - cursorParser = { it.getLong(0) } - ).first() - - return BlockHeight.new(zcashNetwork, heightLong) - } - - suspend fun lastScannedHeight(): BlockHeight { - // Note that we assume the Rust layer will add the birthday height as the first block - val heightLong = - sqliteDatabase.queryAndMap( - table = BlockTableDefinition.TABLE_NAME, - columns = SELECTION_MAX_HEIGHT, - cursorParser = { it.getLong(0) } - ).first() - - return BlockHeight.new(zcashNetwork, heightLong) - } - - suspend fun findBlockHash(blockHeight: BlockHeight): ByteArray? { - return sqliteDatabase.queryAndMap( - table = BlockTableDefinition.TABLE_NAME, - columns = PROJECTION_HASH, - selection = SELECTION_BLOCK_HEIGHT, - selectionArgs = arrayOf(blockHeight.value), - cursorParser = { it.getBlob(0) } - ).firstOrNull() - } -} - -internal object BlockTableDefinition { - const val TABLE_NAME = "blocks" // $NON-NLS - - const val COLUMN_LONG_HEIGHT = "height" // $NON-NLS - - const val COLUMN_BLOB_HASH = "hash" // $NON-NLS -} diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt index 04ce09e8..9d0e7710 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt @@ -18,22 +18,10 @@ internal class DbDerivedDataRepository( ) : DerivedDataRepository { private val invalidatingFlow = MutableStateFlow(UUID.randomUUID()) - override suspend fun lastScannedHeight(): BlockHeight { - return derivedDataDb.blockTable.lastScannedHeight() - } - override suspend fun firstUnenhancedHeight(): BlockHeight? { return derivedDataDb.allTransactionView.firstUnenhancedHeight() } - override suspend fun firstScannedHeight(): BlockHeight { - return derivedDataDb.blockTable.firstScannedHeight() - } - - override suspend fun isInitialized(): Boolean { - return derivedDataDb.blockTable.count() > 0 - } - override suspend fun findEncodedTransactionByTxId(txId: FirstClassByteArray): EncodedTransaction? { return derivedDataDb.transactionTable.findEncodedTransactionByTxId(txId) } @@ -49,8 +37,6 @@ internal class DbDerivedDataRepository( override suspend fun findMatchingTransactionId(rawTransactionId: ByteArray) = derivedDataDb.transactionTable .findDatabaseId(rawTransactionId) - override suspend fun findBlockHash(height: BlockHeight) = derivedDataDb.blockTable.findBlockHash(height) - override suspend fun getTransactionCount() = derivedDataDb.transactionTable.count() override fun invalidate() { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt index 8f16167c..0948a8c9 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt @@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.db.derived import android.content.Context import androidx.sqlite.db.SupportSQLiteDatabase +import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.TypesafeBackend @@ -19,8 +20,6 @@ internal class DerivedDataDb private constructor( ) { val accountTable = AccountTable(sqliteDatabase) - val blockTable = BlockTable(zcashNetwork, sqliteDatabase) - val transactionTable = TransactionTable(zcashNetwork, sqliteDatabase) val allTransactionView = AllTransactionView(zcashNetwork, sqliteDatabase) @@ -49,7 +48,14 @@ internal class DerivedDataDb private constructor( numberOfAccounts: Int, recoverUntil: BlockHeight? ): DerivedDataDb { - backend.initDataDb(seed) + runCatching { + val result = backend.initDataDb(seed) + if (result < 0) { + throw CompactBlockProcessorException.Uninitialized() + } + }.onFailure { + throw CompactBlockProcessorException.Uninitialized(it) + } val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly( NoBackupContextWrapper( diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt index b56004c2..afb02478 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt @@ -4,7 +4,7 @@ internal data class ScanProgress( val numerator: Long, val denominator: Long ) { - override fun toString() = "ScanProgress($numerator / $denominator)" + override fun toString() = "ScanProgress($numerator/$denominator) -> ${numerator / (denominator.toFloat())}" companion object { fun new(jni: JniScanProgress): ScanProgress { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt index ba802ad0..b0821771 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt @@ -13,13 +13,6 @@ import kotlinx.coroutines.flow.Flow @Suppress("TooManyFunctions") internal interface DerivedDataRepository { - /** - * The last height scanned by this repository. - * - * @return the last height scanned by this repository. - */ - suspend fun lastScannedHeight(): BlockHeight - /** * The height of the first transaction that hasn't been enhanced yet. * @@ -28,18 +21,6 @@ internal interface DerivedDataRepository { */ suspend fun firstUnenhancedHeight(): BlockHeight? - /** - * The height of the first block in this repository. This is typically the checkpoint that was - * used to initialize this wallet. If we overwrite this block, it breaks our ability to spend - * funds. - */ - suspend fun firstScannedHeight(): BlockHeight - - /** - * @return true when this repository has been initialized and seeded with the initial checkpoint. - */ - suspend fun isInitialized(): Boolean - /** * Find the encoded transaction associated with the given id. * @@ -76,11 +57,6 @@ internal interface DerivedDataRepository { suspend fun findMatchingTransactionId(rawTransactionId: ByteArray): Long? - // TODO [#681]: begin converting these into Data Access API. For now, just collect the desired - // operations and iterate/refactor, later - // TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681 - suspend fun findBlockHash(height: BlockHeight): ByteArray? - suspend fun getTransactionCount(): Long /** diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 7630551c..8a4c626e 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -98,8 +98,8 @@ internal class TransactionEncoderImpl( backend.isValidUnifiedAddr(address) override suspend fun getConsensusBranchId(): Long { - val height = repository.lastScannedHeight() - if (height < backend.network.saplingActivationHeight) { + val height = backend.getMaxScannedHeight() + if (height == null || height < backend.network.saplingActivationHeight) { throw TransactionEncoderException.IncompleteScanException(height) } return backend.getBranchIdForHeight(height) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt index 0a783572..6bac5fe6 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt @@ -33,7 +33,7 @@ data class TransactionOverview internal constructor( companion object { internal fun new( dbTransactionOverview: DbTransactionOverview, - latestBlockHeight: BlockHeight + latestBlockHeight: BlockHeight? ): TransactionOverview { return TransactionOverview( dbTransactionOverview.id, @@ -69,11 +69,13 @@ enum class TransactionState { private const val MIN_CONFIRMATIONS = 10 internal fun new( - latestBlockHeight: BlockHeight, + latestBlockHeight: BlockHeight?, minedHeight: BlockHeight?, expiryHeight: BlockHeight? ): TransactionState { - return if (minedHeight != null && (latestBlockHeight.value - minedHeight.value) >= MIN_CONFIRMATIONS) { + return if (latestBlockHeight == null) { + Pending + } else if (minedHeight != null && (latestBlockHeight.value - minedHeight.value) >= MIN_CONFIRMATIONS) { Confirmed } else if (minedHeight != null && (latestBlockHeight.value - minedHeight.value) < MIN_CONFIRMATIONS) { Pending