[#1213] Remove `BlockTable` and its APIs
* [#1213] Remove `count()` from BlockTable API * [#1213] Remove `firstScannedHeight()` from BlockTable API * [#1213] Remove `findBlockHash()` from BlockTable * [#1213] Remove `lastScannedHeight()` from BlockTable * [#1213] Remove `BlockTable` entirely
This commit is contained in:
parent
14d854f96e
commit
fc14082a1c
22
CHANGELOG.md
22
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
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
//
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue