Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,16 @@ class BlockchairApiSyncer(
private fun scanSingle(): Single<Unit> = Single.create { emitter ->
try {
val allKeys = storage.getPublicKeys()
val stopHeight = storage.downloadedTransactionsBestBlockHeight()
// A height-based cutoff is only valid for incremental re-scans of a wallet
// whose initial restore has completed. During the initial restore the storage
// may already hold block hashes from an earlier failed attempt; using their
// height as a cutoff would skip all older history for keys discovered later
// (gap expansion), permanently losing transactions.
val stopHeight = if (apiSyncStateManager.restored) {
storage.downloadedTransactionsBestBlockHeight()
} else {
null
}
fetchRecursive(allKeys, allKeys, stopHeight)
fetchLastBlock()

Expand All @@ -88,7 +97,7 @@ class BlockchairApiSyncer(
private fun fetchRecursive(
keys: List<PublicKey>,
allKeys: List<PublicKey>,
stopHeight: Int
stopHeight: Int?
) {
val publicKeyMap = mutableMapOf<String, PublicKey>()
val addresses = mutableListOf<String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@ package io.horizontalsystems.bitcoincore.apisync.blockchair

import io.horizontalsystems.bitcoincore.apisync.model.TransactionItem
import io.horizontalsystems.bitcoincore.core.IApiTransactionProvider
import io.horizontalsystems.bitcoincore.managers.ApiManagerException

class BlockchairTransactionProvider(
val blockchairApi: BlockchairApi,
private val blockHashFetcher: IBlockHashFetcher
) : IApiTransactionProvider {

private fun fillBlockHashes(items: List<TransactionItem>): List<TransactionItem> {
val hashesMap = blockHashFetcher.fetch(items.map { it.blockHeight }.distinct())
return items.mapNotNull { item ->
hashesMap[item.blockHeight]?.let {
item.copy(blockHash = it)
}
val heights = items.map { it.blockHeight }.distinct()
val hashesMap = blockHashFetcher.fetch(heights)

// Every item refers to a confirmed block, so every height must resolve. Failing
// the sync keeps it retryable; silently dropping items would lose transactions.
val missingHeights = heights.filter { it !in hashesMap }
if (missingHeights.isNotEmpty()) {
throw ApiManagerException.Other("Missing block hashes for heights: $missingHeights")
}

return items.map { item ->
item.copy(blockHash = hashesMap.getValue(item.blockHeight))
}
}

Expand Down
Loading