New: Add the ability to rewind and rescan blocks.

This one feature can fix lots of problems that wallets encounter and it is particularly useful for the 'Max Transaction' bug. See this iOS issue for more details: https://github.com/zcash/ZcashLightClientKit/issues/255
This commit is contained in:
Kevin Gorham 2021-03-31 09:16:06 -04:00
parent ad1f2438fb
commit 997f6988a4
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
3 changed files with 35 additions and 5 deletions

View File

@ -300,6 +300,10 @@ class SdkSynchronizer internal constructor(
)
}
override suspend fun rewindToHeight(height: Int, alsoClearBlockCache: Boolean) {
processor.rewindToHeight(height, alsoClearBlockCache)
}
//
// Storage APIs
//

View File

@ -277,6 +277,8 @@ interface Synchronizer {
*/
suspend fun getTransparentBalance(tAddr: String): WalletBalance
suspend fun rewindToHeight(height: Int, alsoClearBlockCache: Boolean = false)
//
// Error Handling
//

View File

@ -228,13 +228,15 @@ class CompactBlockProcessor(
downloader.lightWalletService.reconnect()
ERROR_CODE_RECONNECT
} else if (currentInfo.lastDownloadRange.isEmpty() && currentInfo.lastScanRange.isEmpty()) {
twig("Nothing to process: no new blocks to download or scan, right now.")
twig("Nothing to process: no new blocks to download or scan, right now (latest: ${currentInfo.networkBlockHeight}).")
setState(Scanned(currentInfo.lastScanRange))
ERROR_CODE_NONE
} else {
downloadNewBlocks(currentInfo.lastDownloadRange)
val error = validateAndScanNewBlocks(currentInfo.lastScanRange)
if (error != ERROR_CODE_NONE) error else enhanceTransactionDetails(currentInfo.lastScanRange)
if (error != ERROR_CODE_NONE) error else {
enhanceTransactionDetails(currentInfo.lastScanRange)
}
}
}
@ -561,14 +563,36 @@ class CompactBlockProcessor(
_processorInfo.send(currentInfo)
}
private suspend fun handleChainError(errorHeight: Int) = withContext(IO) {
private suspend fun handleChainError(errorHeight: Int) {
// TODO consider an error object containing hash information
printValidationErrorInfo(errorHeight)
determineLowerBound(errorHeight).let { lowerBound ->
twig("handling chain error at $errorHeight by rewinding to block $lowerBound")
onChainErrorListener?.invoke(errorHeight, lowerBound)
rustBackend.rewindToHeight(lowerBound)
downloader.rewindToHeight(lowerBound)
rewindToHeight(lowerBound)
}
}
/**
* @param alsoClearBlockCache when true, also clear the block cache which forces a redownload of
* blocks. Otherwise, the cached blocks will be used in the rescan, which in most cases, is fine.
*/
suspend fun rewindToHeight(height: Int, alsoClearBlockCache: Boolean = false) = withContext(IO) {
val lastHeight = currentInfo.lastScannedHeight
twig("Rewinding from $lastHeight to height: $height")
// TODO: think about how we might pause all processing during a rewind
if (height < lastHeight) {
rustBackend.rewindToHeight(height)
val range = (height + 1)..lastHeight
if (alsoClearBlockCache) {
twig("Rewound blocks will download in the next scheduled scan")
downloader.rewindToHeight(height)
// communicate that the wallet is no longer synced because it might remain this way for 20+ seconds because we only download on 20s time boundaries so we can't trigger any immediate action
setState(Downloading)
} else {
twig("Revalidating blocks that were rewound")
if (validateAndScanNewBlocks(range) == ERROR_CODE_NONE) enhanceTransactionDetails(range)
}
}
}