2021-10-04 04:18:37 -07:00
|
|
|
package cash.z.ecc.android.sdk.internal.block
|
2023-02-01 02:14:55 -08:00
|
|
|
|
2023-04-14 03:55:51 -07:00
|
|
|
import cash.z.ecc.android.sdk.exception.LightWalletException
|
2023-02-06 14:36:28 -08:00
|
|
|
import cash.z.ecc.android.sdk.internal.Twig
|
2021-10-04 04:18:37 -07:00
|
|
|
import cash.z.ecc.android.sdk.internal.ext.retryUpTo
|
2023-05-10 03:45:23 -07:00
|
|
|
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
|
2023-04-13 04:36:24 -07:00
|
|
|
import cash.z.ecc.android.sdk.internal.model.ext.from
|
2022-10-19 13:52:54 -07:00
|
|
|
import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository
|
2022-07-12 05:40:09 -07:00
|
|
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
2023-04-13 04:36:24 -07:00
|
|
|
import co.electriccoin.lightwallet.client.LightWalletClient
|
2023-02-01 02:14:55 -08:00
|
|
|
import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe
|
2023-04-13 04:36:24 -07:00
|
|
|
import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe
|
2023-02-01 02:14:55 -08:00
|
|
|
import co.electriccoin.lightwallet.client.model.LightWalletEndpointInfoUnsafe
|
|
|
|
import co.electriccoin.lightwallet.client.model.Response
|
2021-10-21 13:05:02 -07:00
|
|
|
import kotlinx.coroutines.Dispatchers
|
2019-06-14 16:24:52 -07:00
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
2023-04-13 04:36:24 -07:00
|
|
|
import kotlinx.coroutines.flow.filterIsInstance
|
|
|
|
import kotlinx.coroutines.flow.map
|
|
|
|
import kotlinx.coroutines.flow.onCompletion
|
|
|
|
import kotlinx.coroutines.flow.onEach
|
2019-06-14 16:24:52 -07:00
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Serves as a source of compact blocks received from the light wallet server. Once started, it will use the given
|
2023-02-01 02:14:55 -08:00
|
|
|
* lightWallet client to request all the appropriate blocks and compact block store to persist them. By delegating to
|
2019-06-14 16:24:52 -07:00
|
|
|
* these dependencies, the downloader remains agnostic to the particular implementation of how to retrieve and store
|
|
|
|
* data; although, by default the SDK uses gRPC and SQL.
|
|
|
|
*
|
2023-02-01 02:14:55 -08:00
|
|
|
* @property lightWalletClient the client used for requesting compact blocks
|
|
|
|
* @property compactBlockStore responsible for persisting the compact blocks that are received
|
2019-06-14 16:24:52 -07:00
|
|
|
*/
|
2022-10-19 13:52:54 -07:00
|
|
|
open class CompactBlockDownloader private constructor(val compactBlockRepository: CompactBlockRepository) {
|
2020-09-23 08:12:49 -07:00
|
|
|
|
2023-04-13 04:36:24 -07:00
|
|
|
lateinit var lightWalletClient: LightWalletClient
|
2020-09-23 08:12:49 -07:00
|
|
|
private set
|
|
|
|
|
|
|
|
constructor(
|
2023-04-13 04:36:24 -07:00
|
|
|
lightWalletClient: LightWalletClient,
|
2022-10-19 13:52:54 -07:00
|
|
|
compactBlockRepository: CompactBlockRepository
|
|
|
|
) : this(compactBlockRepository) {
|
2023-02-01 02:14:55 -08:00
|
|
|
this.lightWalletClient = lightWalletClient
|
2020-09-23 08:12:49 -07:00
|
|
|
}
|
2019-06-14 16:24:52 -07:00
|
|
|
|
2020-02-27 00:25:07 -08:00
|
|
|
/**
|
2023-02-01 02:14:55 -08:00
|
|
|
* Requests the given range of blocks from the lightWalletClient and then persists them to the
|
2020-02-27 00:25:07 -08:00
|
|
|
* compactBlockStore.
|
|
|
|
*
|
|
|
|
* @param heightRange the inclusive range of heights to request. For example 10..20 would
|
|
|
|
* request 11 blocks (including block 10 and block 20).
|
2023-04-14 03:55:51 -07:00
|
|
|
* @throws LightWalletException.DownloadBlockException if any error while downloading the blocks occurs
|
2023-05-10 03:45:23 -07:00
|
|
|
* @return List of JniBlockMeta objects, which describe the original CompactBlock objects, which were just
|
|
|
|
* downloaded and persisted on the device disk
|
2020-02-27 00:25:07 -08:00
|
|
|
*/
|
2023-04-14 03:55:51 -07:00
|
|
|
@Throws(LightWalletException.DownloadBlockException::class)
|
2023-05-10 03:45:23 -07:00
|
|
|
suspend fun downloadBlockRange(heightRange: ClosedRange<BlockHeight>): List<JniBlockMeta> {
|
2023-04-13 04:36:24 -07:00
|
|
|
val filteredFlow = lightWalletClient.getBlockRange(
|
2023-02-01 02:14:55 -08:00
|
|
|
BlockHeightUnsafe.from(heightRange.start)..BlockHeightUnsafe.from(heightRange.endInclusive)
|
2023-04-13 04:36:24 -07:00
|
|
|
).onEach { response ->
|
|
|
|
when (response) {
|
|
|
|
is Response.Success -> {
|
|
|
|
Twig.verbose { "Downloading block at height: ${response.result.height} succeeded." }
|
2023-04-14 03:55:51 -07:00
|
|
|
}
|
|
|
|
is Response.Failure -> {
|
|
|
|
Twig.warn { "Downloading blocks in range: $heightRange failed with: ${response.description}." }
|
|
|
|
throw LightWalletException.DownloadBlockException(
|
|
|
|
response.code,
|
|
|
|
response.description,
|
|
|
|
response.toThrowable()
|
|
|
|
)
|
2023-04-13 04:36:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.filterIsInstance<Response.Success<CompactBlockUnsafe>>()
|
|
|
|
.map { response ->
|
|
|
|
response.result
|
|
|
|
}
|
|
|
|
.onCompletion {
|
|
|
|
if (it != null) {
|
|
|
|
Twig.warn { "Blocks in range $heightRange failed to download with: $it" }
|
|
|
|
} else {
|
|
|
|
Twig.verbose { "All blocks in range $heightRange downloaded successfully" }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return compactBlockRepository.write(filteredFlow)
|
2019-06-14 16:24:52 -07:00
|
|
|
}
|
|
|
|
|
2020-02-27 00:25:07 -08:00
|
|
|
/**
|
2021-04-09 18:19:33 -07:00
|
|
|
* Rewind the storage to the given height, usually to handle reorgs. Deletes all blocks above
|
|
|
|
* the given height.
|
2020-02-27 00:25:07 -08:00
|
|
|
*
|
|
|
|
* @param height the height to which the data will rewind.
|
|
|
|
*/
|
2022-07-12 05:40:09 -07:00
|
|
|
suspend fun rewindToHeight(height: BlockHeight) =
|
2022-08-23 06:49:00 -07:00
|
|
|
// TODO [#685]: cancel anything in flight
|
|
|
|
// TODO [#685]: https://github.com/zcash/zcash-android-wallet-sdk/issues/685
|
2022-10-19 13:52:54 -07:00
|
|
|
compactBlockRepository.rewindTo(height)
|
2019-06-14 16:24:52 -07:00
|
|
|
|
2020-02-27 00:25:07 -08:00
|
|
|
/**
|
2023-02-01 02:14:55 -08:00
|
|
|
* Return the latest block height known by the lightWalletClient.
|
2020-02-27 00:25:07 -08:00
|
|
|
*
|
|
|
|
* @return the latest block height.
|
|
|
|
*/
|
2023-04-14 03:55:51 -07:00
|
|
|
suspend fun getLatestBlockHeight() = lightWalletClient.getLatestBlockHeight()
|
2019-06-14 16:24:52 -07:00
|
|
|
|
2020-02-27 00:25:07 -08:00
|
|
|
/**
|
2022-10-19 13:52:54 -07:00
|
|
|
* Return the latest block height that has been persisted into the [CompactBlockRepository].
|
2020-02-27 00:25:07 -08:00
|
|
|
*
|
|
|
|
* @return the latest block height that has been persisted.
|
|
|
|
*/
|
2022-01-19 10:39:07 -08:00
|
|
|
suspend fun getLastDownloadedHeight() =
|
2022-10-19 13:52:54 -07:00
|
|
|
compactBlockRepository.getLatestHeight()
|
2019-06-14 16:24:52 -07:00
|
|
|
|
2023-02-01 02:14:55 -08:00
|
|
|
suspend fun getServerInfo(): LightWalletEndpointInfoUnsafe? = withContext(IO) {
|
|
|
|
retryUpTo(GET_SERVER_INFO_RETRIES) {
|
|
|
|
when (val response = lightWalletClient.getServerInfo()) {
|
|
|
|
is Response.Success -> return@withContext response.result
|
|
|
|
else -> {
|
|
|
|
lightWalletClient.reconnect()
|
2023-02-06 14:36:28 -08:00
|
|
|
Twig.warn { "WARNING: reconnecting to server in response to failure (retry #${it + 1})" }
|
2023-02-01 02:14:55 -08:00
|
|
|
}
|
2021-06-18 21:25:05 -07:00
|
|
|
}
|
2021-05-25 09:43:42 -07:00
|
|
|
}
|
2020-09-23 08:12:49 -07:00
|
|
|
|
2023-02-01 02:14:55 -08:00
|
|
|
null
|
2020-06-09 19:05:30 -07:00
|
|
|
}
|
|
|
|
|
2020-02-27 00:25:07 -08:00
|
|
|
/**
|
|
|
|
* Stop this downloader and cleanup any resources being used.
|
|
|
|
*/
|
2021-10-21 13:05:02 -07:00
|
|
|
suspend fun stop() {
|
|
|
|
withContext(Dispatchers.IO) {
|
2023-02-01 02:14:55 -08:00
|
|
|
lightWalletClient.shutdown()
|
2021-10-21 13:05:02 -07:00
|
|
|
}
|
2020-02-21 15:22:04 -08:00
|
|
|
}
|
|
|
|
|
2020-03-25 14:58:08 -07:00
|
|
|
/**
|
|
|
|
* Fetch the details of a known transaction.
|
|
|
|
*
|
|
|
|
* @return the full transaction info.
|
|
|
|
*/
|
2023-04-13 04:36:24 -07:00
|
|
|
suspend fun fetchTransaction(txId: ByteArray) = lightWalletClient.fetchTransaction(txId)
|
2022-08-23 06:49:00 -07:00
|
|
|
|
|
|
|
companion object {
|
|
|
|
private const val GET_SERVER_INFO_RETRIES = 6
|
|
|
|
}
|
2019-06-14 16:24:52 -07:00
|
|
|
}
|