diff --git a/.gitignore b/.gitignore index 90af866e..fc0bfdd3 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ captures/ .idea/workspace.xml .idea/protoeditor.xml .idea/appInsightsSettings.xml +.idea/migrations.xml *.iml # Keystore files diff --git a/LICENSE b/LICENSE index 51ce0d39..4e6dfe52 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017-2021 Electric Coin Company +Copyright (c) 2017-2023 Electric Coin Company Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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 af26c40e..591e8315 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 @@ -6,6 +6,7 @@ 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.GetSubtreeRootsResult import cash.z.ecc.android.sdk.block.processor.model.PutSaplingSubtreeRootsResult +import cash.z.ecc.android.sdk.block.processor.model.SbSPreparationResult import cash.z.ecc.android.sdk.block.processor.model.SuggestScanRangesResult import cash.z.ecc.android.sdk.block.processor.model.SyncStageResult import cash.z.ecc.android.sdk.block.processor.model.SyncingResult @@ -27,8 +28,8 @@ import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty import cash.z.ecc.android.sdk.internal.ext.isScanContinuityError import cash.z.ecc.android.sdk.internal.ext.length -import cash.z.ecc.android.sdk.internal.ext.retryUpTo import cash.z.ecc.android.sdk.internal.ext.retryUpToAndContinue +import cash.z.ecc.android.sdk.internal.ext.retryUpToAndThrow import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.model.BlockBatch @@ -75,6 +76,7 @@ import kotlin.math.max import kotlin.math.min import kotlin.time.Duration import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -149,6 +151,20 @@ class CompactBlockProcessor internal constructor( private val _networkHeight = MutableStateFlow(null) private val processingMutex = Mutex() + /** + * The synchronization-related variable that holds the all batch count computed in the first initial synchronization + * loop. It is supposed to keep the same value across the synchronization refreshes with [runSbSSyncingPreparation] + * as happens in [SyncAlgorithm.SPEND_BEFORE_SYNC] synchronization function. + */ + private var allBatchCount: Long = 0 + + /** + * Another synchronization-related variable that holds the order of a currently processing batch of blocks. It + * is supposed to preserve its value across the synchronization refreshes with [runSbSSyncingPreparation] as + * happens in [SyncAlgorithm.SPEND_BEFORE_SYNC] synchronization function. + */ + private var lastBatchOrder: Long = 0 + /** * Flow of birthday heights. The birthday is essentially the first block that the wallet cares * about. Any prior block can be ignored. This is not a fixed value because the height is @@ -225,13 +241,28 @@ class CompactBlockProcessor internal constructor( val result = processingMutex.withLockLogged("processNewBlocks") { when (subTreeRootResult) { is GetSubtreeRootsResult.UseSbS -> { + // Pass the commitment tree data to the database + when ( + val result = putSaplingSubtreeRoots( + backend = backend, + startIndex = 0, + subTreeRootList = (subTreeRootResult as GetSubtreeRootsResult.UseSbS) + .subTreeRootList, + lastValidHeight = lowerBoundHeight + ) + ) { + PutSaplingSubtreeRootsResult.Success -> { + // Lets continue with the next step + } + is PutSaplingSubtreeRootsResult.Failure -> { + BlockProcessingResult.SyncFailure(result.failedAtHeight, result.exception) + } + } processNewBlocksInSbSOrder( backend = backend, downloader = downloader, repository = repository, network = network, - subTreeRootList = (subTreeRootResult as GetSubtreeRootsResult.UseSbS) - .subTreeRootList, lastValidHeight = lowerBoundHeight, firstUnenhancedHeight = _processorInfo.value.firstUnenhancedHeight ) @@ -251,7 +282,8 @@ class CompactBlockProcessor internal constructor( } } } - // immediately process again after failures in order to download new blocks right away + + // Immediately process again after failures in order to download new blocks right away when (result) { BlockProcessingResult.Reconnecting -> { setState(State.Disconnected) @@ -264,6 +296,10 @@ class CompactBlockProcessor internal constructor( } delay(napTime) } + BlockProcessingResult.RestartSynchronization -> { + Twig.info { "Planned restarting of block synchronization..." } + // No nap time set to immediately continue with refreshed block synchronization + } BlockProcessingResult.NoBlocksToProcess -> { setState(State.Synced(_processorInfo.value.overallSyncRange)) val noWorkDone = _processorInfo.value.overallSyncRange?.isEmpty() ?: true @@ -371,93 +407,43 @@ class CompactBlockProcessor internal constructor( downloader: CompactBlockDownloader, repository: DerivedDataRepository, network: ZcashNetwork, - subTreeRootList: List, lastValidHeight: BlockHeight, firstUnenhancedHeight: BlockHeight? ): BlockProcessingResult { Twig.info { - "Beginning to process new blocks with Spend-before-Sync approach (with roots: $subTreeRootList, and lower" + - "bound: $lastValidHeight)..." + "Beginning to process new blocks with Spend-before-Sync approach with lower bound: $lastValidHeight)..." } - // Pass the commitment tree data to the database. - when ( - val result = - putSaplingSubtreeRoots( - backend = backend, - startIndex = 0, - subTreeRootList = subTreeRootList, - lastValidHeight = lastValidHeight - ) - ) { - PutSaplingSubtreeRootsResult.Success -> { /* Lets continue to the next step */ } - is PutSaplingSubtreeRootsResult.Failure -> { - return BlockProcessingResult.SyncFailure(result.failedAtHeight, result.exception) - } - } - - // Download chain tip metadata from lightwalletd - val chainTip = fetchLatestBlockHeight( + // This step covers these operations fetchLatestBlockHeight, updateChainTip, suggestScanRanges, updateRange, + // and shouldVerifySuggestedScanRanges + val preparationResult = runSbSSyncingPreparation( + backend = backend, downloader = downloader, - network = network - ) ?: let { - Twig.warn { "Disconnection detected. Attempting to reconnect." } - return BlockProcessingResult.Reconnecting - } - - // Notify the wallet of the updated chain tip - when ( - val result = - updateChainTip( - backend = backend, - chainTip = chainTip, - lastValidHeight = lastValidHeight - ) - ) { - is UpdateChainTipResult.Success -> { /* Lets continue to the next step */ } - is UpdateChainTipResult.Failure -> { - return BlockProcessingResult.SyncFailure(result.failedAtHeight, result.exception) - } - } - - // Get the suggested scan ranges from the wallet database - var suggestedRangesResult = suggestScanRanges( - backend, - lastValidHeight + network = network, + lastValidHeight = lastValidHeight ) - val updateRangeResult = when (suggestedRangesResult) { - is SuggestScanRangesResult.Success -> { - updateRange(suggestedRangesResult.ranges) + when (preparationResult) { + is SbSPreparationResult.ProcessFailure -> { + return preparationResult.toBlockProcessingResult() } - is SuggestScanRangesResult.Failure -> { - Twig.error { - "Process suggested scan ranges failure: " + - "${(suggestedRangesResult as SuggestScanRangesResult.Failure).exception}" - } - return BlockProcessingResult.SyncFailure( - suggestedRangesResult.failedAtHeight, - suggestedRangesResult.exception - ) + SbSPreparationResult.ConnectionFailure -> { + return BlockProcessingResult.Reconnecting + } + SbSPreparationResult.NoMoreBlocksToProcess -> { + return BlockProcessingResult.NoBlocksToProcess + } + is SbSPreparationResult.Success -> { + Twig.info { "Preparation phase done with result: $preparationResult" } + // Continue processing ranges } } - if (!updateRangeResult) { - Twig.warn { "Disconnection detected. Attempting to reconnect." } - return BlockProcessingResult.Reconnecting - } else if (_processorInfo.value.overallSyncRange.isNullOrEmpty()) { - Twig.info { "No more blocks to process." } - return BlockProcessingResult.NoBlocksToProcess - } - - setState(State.Syncing) - val allBatchCount = getBatchCount(suggestedRangesResult.ranges.map { it.range }).toFloat() - var lastBatchOrder: Long = 0 - - // Parse and process ranges. If it recognizes a range with Priority.Verify, it runs the verification part. - var verifyRangeResult = shouldVerifySuggestedScanRanges(suggestedRangesResult) - - Twig.info { "Check for verification of ranges resulted with: $verifyRangeResult" } + var verifyRangeResult = preparationResult.verifyRangeResult + var suggestedRangesResult = preparationResult.suggestedRangesResult + val allBatchCountLocal = preparationResult.allBatchCount + val lastPreparationTime = System.currentTimeMillis() + // Running synchronization for the [ScanRange.SuggestScanRangePriority.Verify] range while (verifyRangeResult is VerifySuggestedScanRange.ShouldVerify) { Twig.info { "Starting verification of range: $verifyRangeResult" } @@ -479,9 +465,11 @@ class CompactBlockProcessor internal constructor( enhanceStartHeight = firstUnenhancedHeight, lastBatchOrder = lastBatchOrder ).collect { rangeSyncProgress -> - setProgress(PercentDecimal(rangeSyncProgress.overallOrder / allBatchCount)) - // We need to update lastBatchOrder for the next ranges processing - lastBatchOrder = rangeSyncProgress.overallOrder + // We need to update lastBatchOrder for the processing of the following range. It can occasionally + // be over the precomputed all-batch count in case of inter-syncing failure. + lastBatchOrder = min(rangeSyncProgress.overallOrder, allBatchCountLocal) + + setProgress(PercentDecimal(lastBatchOrder / allBatchCountLocal.toFloat())) when (rangeSyncProgress.resultState) { SyncingResult.UpdateBirthday -> { @@ -496,15 +484,19 @@ class CompactBlockProcessor internal constructor( } } - if (syncingResult != SyncingResult.AllSuccess) { - // Remove persisted but not scanned blocks in case of any failure - val lastScannedHeight = getLastScannedHeight(repository) - downloader.rewindToHeight(lastScannedHeight) - deleteAllBlockFiles( - downloader = downloader, - lastKnownHeight = lastScannedHeight - ) - return (syncingResult as SyncingResult.Failure).toBlockProcessingResult() + when (syncingResult) { + is SyncingResult.AllSuccess -> { + // 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) + deleteAllBlockFiles( + downloader = downloader, + lastKnownHeight = lastScannedHeight + ) + return (syncingResult as SyncingResult.Failure).toBlockProcessingResult() + } } // Re-request suggested scan ranges @@ -514,10 +506,7 @@ class CompactBlockProcessor internal constructor( verifyRangeResult = shouldVerifySuggestedScanRanges(suggestedRangesResult) } is SuggestScanRangesResult.Failure -> { - Twig.error { - "Process suggested scan ranges failure: " + - "${(suggestedRangesResult as SuggestScanRangesResult.Failure).exception}" - } + Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } return BlockProcessingResult.SyncFailure( suggestedRangesResult.failedAtHeight, suggestedRangesResult.exception @@ -527,12 +516,6 @@ class CompactBlockProcessor internal constructor( } // Process the rest of ranges - - // Get the suggested scan ranges from the wallet database - suggestedRangesResult = suggestScanRanges( - backend, - lastValidHeight - ) val scanRanges = when (suggestedRangesResult) { is SuggestScanRangesResult.Success -> { suggestedRangesResult.ranges } is SuggestScanRangesResult.Failure -> { @@ -558,36 +541,138 @@ class CompactBlockProcessor internal constructor( withDownload = true, enhanceStartHeight = firstUnenhancedHeight, lastBatchOrder = lastBatchOrder - ).collect { rangeSyncProgress -> - setProgress(PercentDecimal(rangeSyncProgress.overallOrder / allBatchCount)) - lastBatchOrder = rangeSyncProgress.overallOrder + ).map { rangeSyncProgress -> + // We need to update lastBatchOrder for the processing of the following range. It can occasionally + // be over the precomputed all-batch count in case of inter-syncing failure. + lastBatchOrder = min(rangeSyncProgress.overallOrder, allBatchCountLocal) + + setProgress(PercentDecimal(lastBatchOrder / allBatchCountLocal.toFloat())) when (rangeSyncProgress.resultState) { SyncingResult.UpdateBirthday -> { updateBirthdayHeight() + SyncingResult.AllSuccess } is SyncingResult.Failure -> { - syncingResult = rangeSyncProgress.resultState - return@collect + rangeSyncProgress.resultState } else -> { - // Continue with processing + // First, check the time and refresh the prepare phase inputs, if needed + val currentTimeMillis = System.currentTimeMillis() + if (shouldRefreshPreparation( + lastPreparationTime, + currentTimeMillis, + SBS_SYNCHRONIZATION_RESTART_TIMEOUT + ) + ) { + SyncingResult.RestartSynchronization + } else { + // Continue with processing + SyncingResult.AllSuccess + } } } - } + }.takeWhile { + syncingResult = it + it == SyncingResult.AllSuccess + }.collect() - if (syncingResult != SyncingResult.AllSuccess) { - // Remove persisted but not scanned blocks in case of any failure - val lastScannedHeight = getLastScannedHeight(repository) - downloader.rewindToHeight(lastScannedHeight) - deleteAllBlockFiles( - downloader = downloader, - lastKnownHeight = lastScannedHeight + when (syncingResult) { + is SyncingResult.AllSuccess -> { + // Continue with processing the rest of the ranges + } + is SyncingResult.RestartSynchronization -> { + // Restarting the synchronization process + return BlockProcessingResult.RestartSynchronization + } else -> { + // An error came - remove persisted but not scanned blocks + val lastScannedHeight = getLastScannedHeight(repository) + downloader.rewindToHeight(lastScannedHeight) + deleteAllBlockFiles( + downloader = downloader, + lastKnownHeight = lastScannedHeight + ) + return (syncingResult as SyncingResult.Failure).toBlockProcessingResult() + } + } + } + return BlockProcessingResult.Success + } + + @Suppress("ReturnCount") + internal suspend fun runSbSSyncingPreparation( + backend: TypesafeBackend, + downloader: CompactBlockDownloader, + network: ZcashNetwork, + lastValidHeight: BlockHeight + ): SbSPreparationResult { + // Download chain tip metadata from lightwalletd + val chainTip = fetchLatestBlockHeight( + downloader = downloader, + network = network + ) ?: let { + Twig.warn { "Disconnection detected. Attempting to reconnect." } + return SbSPreparationResult.ConnectionFailure + } + + // Notify the underlying rust layer about the updated chain tip + when ( + val result = + updateChainTip( + backend = backend, + chainTip = chainTip, + lastValidHeight = lastValidHeight + ) + ) { + is UpdateChainTipResult.Success -> { /* Lets continue to the next step */ } + is UpdateChainTipResult.Failure -> { + return SbSPreparationResult.ProcessFailure( + result.failedAtHeight, + result.exception ) - return (syncingResult as SyncingResult.Failure).toBlockProcessingResult() } } - return BlockProcessingResult.Success + // Get the suggested scan ranges from the wallet database + val suggestedRangesResult = suggestScanRanges( + backend, + lastValidHeight + ) + val updateRangeResult = when (suggestedRangesResult) { + is SuggestScanRangesResult.Success -> { + updateRange(suggestedRangesResult.ranges) + } + is SuggestScanRangesResult.Failure -> { + Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } + return SbSPreparationResult.ProcessFailure( + suggestedRangesResult.failedAtHeight, + suggestedRangesResult.exception + ) + } + } + + if (!updateRangeResult) { + Twig.warn { "Disconnection detected. Attempting to reconnect." } + return SbSPreparationResult.ConnectionFailure + } else if (_processorInfo.value.overallSyncRange.isNullOrEmpty()) { + Twig.info { "No more blocks to process." } + return SbSPreparationResult.NoMoreBlocksToProcess + } + + setState(State.Syncing) + allBatchCount = max(allBatchCount, getBatchCount(suggestedRangesResult.ranges.map { it.range })) + lastBatchOrder = max(lastBatchOrder, 0) + + // Parse and process ranges. If it recognizes a range with Priority.Verify, it runs the verification part. + val verifyRangeResult = shouldVerifySuggestedScanRanges(suggestedRangesResult) + + Twig.info { "Check for verification of ranges resulted with: $verifyRangeResult" } + + return SbSPreparationResult.Success( + suggestedRangesResult = suggestedRangesResult, + verifyRangeResult = verifyRangeResult, + allBatchCount = allBatchCount, + lastBatchOrder = lastBatchOrder + ) } @Suppress("ReturnCount") @@ -597,7 +682,7 @@ class CompactBlockProcessor internal constructor( enhanceStartHeight: BlockHeight? ): BlockProcessingResult { var syncingResult: SyncingResult = SyncingResult.AllSuccess - val allBatchCount = getBatchCount(listOf(syncRange)).toFloat() + allBatchCount = getBatchCount(listOf(syncRange)) // Syncing last blocks and enhancing transactions runSyncingAndEnhancingOnRange( @@ -610,7 +695,7 @@ class CompactBlockProcessor internal constructor( enhanceStartHeight = enhanceStartHeight, lastBatchOrder = 0 ).collect { rangeSyncProgress -> - setProgress(PercentDecimal(rangeSyncProgress.overallOrder / allBatchCount)) + setProgress(PercentDecimal(rangeSyncProgress.overallOrder / allBatchCount.toFloat())) when (rangeSyncProgress.resultState) { SyncingResult.UpdateBirthday -> { @@ -643,6 +728,7 @@ class CompactBlockProcessor internal constructor( object NoBlocksToProcess : BlockProcessingResult() object Success : BlockProcessingResult() object Reconnecting : BlockProcessingResult() + object RestartSynchronization : BlockProcessingResult() data class SyncFailure(val failedAtHeight: BlockHeight, val error: Throwable) : BlockProcessingResult() } @@ -657,7 +743,6 @@ class CompactBlockProcessor internal constructor( * * @return true when the update succeeds. */ - @OptIn(ExperimentalStdlibApi::class) private suspend fun updateRange(ranges: List?): Boolean { // This fetches the latest height each time this method is called, which can be very inefficient // when downloading all of the blocks from the server @@ -804,7 +889,7 @@ class CompactBlockProcessor internal constructor( if (failedUtxoFetches < 9) { // there are 3 attempts per block @Suppress("TooGenericExceptionCaught") try { - retryUpTo(UTXO_FETCH_RETRIES) { + retryUpToAndThrow(UTXO_FETCH_RETRIES) { val tAddresses = backend.listTransparentReceivers(account) downloader.fetchUtxos( @@ -952,6 +1037,24 @@ class CompactBlockProcessor internal constructor( */ internal const val REWIND_DISTANCE = 10 + /** + * Limit millis value for restarting currently running block synchronization that runs under the + * [SyncAlgorithm.SPEND_BEFORE_SYNC] synchronization. + */ + internal val SBS_SYNCHRONIZATION_RESTART_TIMEOUT = 10.minutes.inWholeMilliseconds + + /** + * Check for the next restart of the block synchronization preparation phase. This function is only SbS + * synchronization algorithm-related. + */ + internal fun shouldRefreshPreparation( + lastPreparationTime: Long, + currentTimeMillis: Long, + limitTime: Long + ): Boolean { + return (currentTimeMillis - lastPreparationTime) >= limitTime + } + /** * This operation fetches and returns the latest block height (chain tip) * @@ -1173,6 +1276,8 @@ class CompactBlockProcessor internal constructor( /** * Requests, processes and persists all blocks from the given range. * + * Works the same for both [SyncAlgorithm.LINEAR] and [SyncAlgorithm.SPEND_BEFORE_SYNC] algorithms. + * * @param backend the Rust backend component * @param downloader the compact block downloader component * @param repository the derived data repository component @@ -1302,10 +1407,10 @@ class CompactBlockProcessor internal constructor( backend = backend, downloader = downloader ).collect { enhancingResult -> - Twig.debug { "Enhancing result: $enhancingResult" } + Twig.info { "Enhancing result: $enhancingResult" } resultState = when (enhancingResult) { is SyncingResult.UpdateBirthday -> { - Twig.debug { "Birthday height update reporting" } + Twig.info { "Birthday height update reporting" } enhancingResult } is SyncingResult.EnhanceFailed -> { @@ -1594,7 +1699,7 @@ class CompactBlockProcessor internal constructor( downloader: CompactBlockDownloader ): ByteArray { var transactionDataResult: ByteArray? = null - retryUpTo(TRANSACTION_FETCH_RETRIES) { failedAttempts -> + retryUpToAndThrow(TRANSACTION_FETCH_RETRIES) { failedAttempts -> if (failedAttempts == 0) { Twig.debug { "Starting to fetch transaction (id:$id, block:$minedHeight)" } } else { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt new file mode 100644 index 00000000..21401dfc --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt @@ -0,0 +1,28 @@ +package cash.z.ecc.android.sdk.block.processor.model + +import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor +import cash.z.ecc.android.sdk.model.BlockHeight + +/** + * Internal class for sharing pre-synchronization steps result. + */ +internal sealed class SbSPreparationResult { + object ConnectionFailure : SbSPreparationResult() + data class ProcessFailure( + val failedAtHeight: BlockHeight, + val exception: Throwable + ) : SbSPreparationResult() { + fun toBlockProcessingResult(): CompactBlockProcessor.BlockProcessingResult = + CompactBlockProcessor.BlockProcessingResult.SyncFailure( + this.failedAtHeight, + this.exception + ) + } + data class Success( + val suggestedRangesResult: SuggestScanRangesResult, + val verifyRangeResult: VerifySuggestedScanRange, + val allBatchCount: Long, + val lastBatchOrder: Long + ) : SbSPreparationResult() + object NoMoreBlocksToProcess : SbSPreparationResult() +} 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 6b32a7a5..7fa47866 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 @@ -9,11 +9,12 @@ import cash.z.ecc.android.sdk.model.BlockHeight * Internal class for the overall synchronization process result reporting. */ internal sealed class SyncingResult { + override fun toString(): String = this::class.java.simpleName + object AllSuccess : SyncingResult() + object RestartSynchronization : SyncingResult() data class DownloadSuccess(val downloadedBlocks: List?) : SyncingResult() { - override fun toString(): String { - return "DownloadSuccess with ${downloadedBlocks?.size ?: "none"} blocks" - } + override fun toString() = "${this::class.java.simpleName} with ${downloadedBlocks?.size ?: "none"} blocks" } interface Failure { val failedAtHeight: BlockHeight diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt index f961de8e..2b633215 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt @@ -2,7 +2,7 @@ package cash.z.ecc.android.sdk.internal.block import cash.z.ecc.android.sdk.exception.LightWalletException import cash.z.ecc.android.sdk.internal.Twig -import cash.z.ecc.android.sdk.internal.ext.retryUpTo +import cash.z.ecc.android.sdk.internal.ext.retryUpToAndThrow import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.ext.from import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository @@ -111,7 +111,7 @@ open class CompactBlockDownloader private constructor(val compactBlockRepository compactBlockRepository.getLatestHeight() suspend fun getServerInfo(): LightWalletEndpointInfoUnsafe? = withContext(IO) { - retryUpTo(GET_SERVER_INFO_RETRIES) { + retryUpToAndThrow(GET_SERVER_INFO_RETRIES) { when (val response = lightWalletClient.getServerInfo()) { is Response.Success -> return@withContext response.result else -> { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt index 743b39df..3f470546 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt @@ -17,7 +17,7 @@ import kotlin.random.Random * @param block the code to execute, which will be wrapped in a try/catch and retried whenever an * exception is thrown up to [retries] attempts. */ -suspend inline fun retryUpTo( +suspend inline fun retryUpToAndThrow( retries: Int, exceptionWrapper: (Throwable) -> Throwable = { it }, initialDelayMillis: Long = 500L, diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt new file mode 100644 index 00000000..13526cde --- /dev/null +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt @@ -0,0 +1,30 @@ +package cash.z.ecc.android.sdk.block.processor + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class CompactBlockProcessorTest { + + @Test + fun should_refresh_preparation_test() { + assertTrue { + CompactBlockProcessor.shouldRefreshPreparation( + lastPreparationTime = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT, + currentTimeMillis = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT * 2, + limitTime = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT + ) + } + } + + @Test + fun should_not_refresh_preparation_test() { + assertFalse { + CompactBlockProcessor.shouldRefreshPreparation( + lastPreparationTime = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT, + currentTimeMillis = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT, + limitTime = CompactBlockProcessor.SBS_SYNCHRONIZATION_RESTART_TIMEOUT + ) + } + } +}