[#1039] Retry enhancing fetch transaction

Enhancing transaction details mechanism was refactored to provide more clarity about its inner phases (fetching and decrypting with storing) and to be able to retry when the transaction fetching fails due to flaky network condition
This commit is contained in:
Honza Rychnovsky 2023-05-22 18:03:50 +03:00 committed by GitHub
parent b1901c0c8f
commit 68e7e1f7de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 72 additions and 29 deletions

View File

@ -541,7 +541,7 @@ class CompactBlockProcessor internal constructor(
if (failedUtxoFetches < 9) { // there are 3 attempts per block
@Suppress("TooGenericExceptionCaught")
try {
retryUpTo(3) {
retryUpTo(UTXO_FETCH_RETRIES) {
val tAddresses = backend.listTransparentReceivers(account)
downloader.lightWalletClient.fetchUtxos(
@ -648,6 +648,16 @@ class CompactBlockProcessor internal constructor(
*/
internal const val RETRIES = 5
/**
* Transaction fetching default attempts at retrying.
*/
internal const val TRANSACTION_FETCH_RETRIES = 1
/**
* UTXOs fetching default attempts at retrying.
*/
internal const val UTXO_FETCH_RETRIES = 3
/**
* The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
*/
@ -892,7 +902,7 @@ class CompactBlockProcessor internal constructor(
if (failedAttempts == 0) {
Twig.verbose { "Starting to download batch $batch" }
} else {
Twig.verbose { "Retrying to download batch $batch after $failedAttempts failure(s)..." }
Twig.warn { "Retrying to download batch $batch after $failedAttempts failure(s)..." }
}
downloadedBlocks = downloader.downloadBlockRange(batch.range)
@ -985,7 +995,7 @@ class CompactBlockProcessor internal constructor(
}
newTxs.filter { it.minedHeight != null }.onEach { newTransaction ->
val trEnhanceResult = enhance(newTransaction, rustBackend, downloader)
val trEnhanceResult = enhanceTransaction(newTransaction, rustBackend, downloader)
if (trEnhanceResult is BlockProcessingResult.FailedEnhance) {
Twig.error { "Encountered transaction enhancing error: ${trEnhanceResult.error}" }
emit(trEnhanceResult)
@ -998,53 +1008,86 @@ class CompactBlockProcessor internal constructor(
emit(BlockProcessingResult.Success)
}
private suspend fun enhance(
private suspend fun enhanceTransaction(
transaction: DbTransactionOverview,
backend: Backend,
downloader: CompactBlockDownloader
): BlockProcessingResult {
return if (transaction.minedHeight != null) {
enhanceHelper(
Twig.debug { "Starting enhancing transaction (id:${transaction.id} block:${transaction.minedHeight})" }
if (transaction.minedHeight == null) {
return BlockProcessingResult.Success
}
return try {
// Fetching transaction is done with retries to eliminate a bad network condition
Twig.verbose { "Fetching transaction (id:${transaction.id} block:${transaction.minedHeight})" }
val transactionData = fetchTransaction(
id = transaction.id,
rawTransactionId = transaction.rawId.byteArray,
minedHeight = transaction.minedHeight,
backend = backend,
downloader = downloader
)
} else {
// Decrypting and storing transaction is run just once, since we consider it more stable
Twig.verbose {
"Decrypting and storing transaction " +
"(id:${transaction.id} block:${transaction.minedHeight})"
}
decryptTransaction(
transactionData = transactionData,
minedHeight = transaction.minedHeight,
backend = backend
)
Twig.debug { "Done enhancing transaction (id:${transaction.id} block:${transaction.minedHeight})" }
BlockProcessingResult.Success
} catch (e: CompactBlockProcessorException.EnhanceTransactionError) {
BlockProcessingResult.FailedEnhance(e)
}
}
private suspend fun enhanceHelper(
@Throws(EnhanceTxDownloadError::class)
private suspend fun fetchTransaction(
id: Long,
rawTransactionId: ByteArray,
minedHeight: BlockHeight,
backend: Backend,
downloader: CompactBlockDownloader
): BlockProcessingResult {
Twig.debug { "START: enhancing transaction (id:$id block:$minedHeight)" }
val fetchResponse = downloader.fetchTransaction(rawTransactionId)
val enhancingResult = when (fetchResponse) {
is Response.Success -> {
@Suppress("TooGenericExceptionCaught") // RuntimeException comes from the Rust layer
try {
Twig.debug { "Decrypting and storing transaction (id:$id block:$minedHeight)" }
backend.decryptAndStoreTransaction(fetchResponse.result.data)
Twig.debug { "DONE: enhancing transaction (id:$id block:$minedHeight)" }
BlockProcessingResult.Success
} catch (exception: RuntimeException) {
BlockProcessingResult.FailedEnhance(EnhanceTxDecryptError(minedHeight, exception))
): ByteArray {
var transactionDataResult: ByteArray? = null
retryUpTo(TRANSACTION_FETCH_RETRIES) { failedAttempts ->
if (failedAttempts == 0) {
Twig.debug { "Starting to fetch transaction (id:$id, block:$minedHeight)" }
} else {
Twig.warn {
"Retrying to fetch transaction (id:$id, block:$minedHeight) after $failedAttempts " +
"failure(s)..."
}
}
is Response.Failure -> {
BlockProcessingResult.FailedEnhance(
EnhanceTxDownloadError(minedHeight, fetchResponse.toThrowable())
)
when (val response = downloader.fetchTransaction(rawTransactionId)) {
is Response.Success -> {
transactionDataResult = response.result.data
}
is Response.Failure -> {
throw EnhanceTxDownloadError(minedHeight, response.toThrowable())
}
}
}
return enhancingResult
// Result is fetched or EnhanceTxDownloadError is thrown after all attempts failed at this point
return transactionDataResult!!
}
@Throws(EnhanceTxDecryptError::class)
private suspend fun decryptTransaction(
transactionData: ByteArray,
minedHeight: BlockHeight,
backend: Backend,
) {
@Suppress("TooGenericExceptionCaught") // RuntimeException comes from the Rust layer
try {
backend.decryptAndStoreTransaction(transactionData)
} catch (exception: RuntimeException) {
throw EnhanceTxDecryptError(minedHeight, exception)
}
}
/**