[#1180] Frequent SbS synchronization restarting
* [#1180] Frequent SbS synchronization restarting - Processing blocks with SbS split into preparation and processing functions, which can be called repeatedly. - Refactored other parts of the synchronization mechanism - Closes #1180 - This also partly solves #1137 * Update .gitignore * Update LINCENSE documentation * [#1177] Checkpoints update * Fix Ktlint warning
This commit is contained in:
parent
f23aca38a6
commit
10a7aa7f3f
|
@ -51,6 +51,7 @@ captures/
|
|||
.idea/workspace.xml
|
||||
.idea/protoeditor.xml
|
||||
.idea/appInsightsSettings.xml
|
||||
.idea/migrations.xml
|
||||
*.iml
|
||||
|
||||
# Keystore files
|
||||
|
|
2
LICENSE
2
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
|
||||
|
|
|
@ -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<BlockHeight?>(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<SubtreeRoot>,
|
||||
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<ScanRange>?): 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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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<JniBlockMeta>?) : 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
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue