[#1160] New SyncAlgorithm type API

This commit is contained in:
Honza Rychnovský 2023-08-09 10:59:15 +02:00 committed by Honza
parent 54d016e401
commit 6b487e979e
6 changed files with 83 additions and 28 deletions

View File

@ -1,8 +1,18 @@
# Change Log
## Unreleased
- `CompactBlockProcessor` now processes compact blocks from the lightwalletd server in a non-linear order. This
feature shortens the time after which a wallet's spendable balance can be used.
### Added
- New `syncAlgorithm` parameter of `Synchronizer.new()` and `WalletCoordinator()` to select preferred
`CompactBlockProcessor` block synchronizing algorithm. It can be of `CompactBlockProcessor.SyncAlgorithm.LINEAR`
or `NON_LINEAR`. The LINEAR type is automatically used if the client app does not specify otherwise. Please note
that the NON_LINEAR type is currently unstable and still under development.
## 1.21.0-beta01
Note: This is the last _1.x_ version release. The upcoming version _2.0_ brings the **Spend-before-Sync** feature,
which speeds up discovering the wallet's spendable balance.
Note: This is the last _1.x_ version release. The upcoming version _2.0_ brings the **Spend-before-Sync** feature,
which speeds up discovering the wallet's spendable balance.
### Changed
- Updated dependencies:

View File

@ -53,3 +53,12 @@ Note that we aim for the main branch of this repository to be stable and releasa
1. Intel-based machines may have trouble building in Android Studio. The workaround is to add the following line to `~/.gradle/gradle.properties`: `ZCASH_IS_DEPENDENCY_LOCKING_ENABLED=false`
1. During builds, a warning will be printed that says "Unable to detect AGP versions for included builds. All projects in the build should use the same AGP version." This can be safely ignored. The version under build-conventions is the same as the version used elsewhere in the application.
1. Android Studio will warn about the Gradle checksum. This is a [known issue](https://github.com/gradle/gradle/issues/9361) and can be safely ignored.
## Unstable Features
### Non-linear compact blocks synchronization
- CompactBlockProcessor now processes compact blocks from the lightwalletd server in a **non-linear** order. This
feature shortens the time after which a wallet's spendable balance can be used.
- Use the new `syncAlgorithm` parameter of `Synchronizer.new()` or `WalletCoordinator()` to select preferred
`CompactBlockProcessor` block synchronizing algorithm. It can be of `CompactBlockProcessor.SyncAlgorithm.LINEAR`
or `NON_LINEAR`. The LINEAR type is automatically used if the client app does not specify otherwise. Please note
that the NON_LINEAR type is currently unstable and still under development.

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk
import android.content.Context
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.ext.onFirst
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.PersistableWallet
@ -33,6 +34,8 @@ import java.util.UUID
/**
* @param persistableWallet flow of the user's stored wallet. Null indicates that no wallet has been stored.
*
* @param syncAlgorithm The CompactBlockProcess's type of block syncing algorithm
*/
/*
* One area where this class needs to change before it can be moved out of the incubator is that we need to be able to
@ -43,7 +46,8 @@ import java.util.UUID
*/
class WalletCoordinator(
context: Context,
val persistableWallet: Flow<PersistableWallet?>
val persistableWallet: Flow<PersistableWallet?>,
val syncAlgorithm: CompactBlockProcessor.SyncAlgorithm = CompactBlockProcessor.SyncAlgorithm.NON_LINEAR
) {
private val applicationContext = context.applicationContext
@ -80,6 +84,7 @@ class WalletCoordinator(
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(persistableWallet.network),
birthday = persistableWallet.birthday,
seed = persistableWallet.seedPhrase.toByteArray(),
syncAlgorithm = syncAlgorithm
)
trySend(InternalSynchronizerStatus.Available(closeableSynchronizer))

View File

@ -109,6 +109,10 @@ class SdkSynchronizer private constructor(
private val mutex = Mutex()
/**
* Convenience method to create new SdkSynchronizer instance.
*
* @return Synchronizer instance as CloseableSynchronizer
*
* @throws IllegalStateException If multiple instances of synchronizer with the same network+alias are
* active at the same time. Call `close` to finish one synchronizer before starting another one with the same
* network+alias.
@ -708,12 +712,14 @@ internal object DefaultSynchronizerFactory {
backend: TypesafeBackend,
downloader: CompactBlockDownloader,
repository: DerivedDataRepository,
birthdayHeight: BlockHeight
birthdayHeight: BlockHeight,
syncAlgorithm: CompactBlockProcessor.SyncAlgorithm
): CompactBlockProcessor = CompactBlockProcessor(
downloader,
repository,
backend,
birthdayHeight
downloader = downloader,
repository = repository,
backend = backend,
minimumHeight = birthdayHeight,
syncAlgorithm = syncAlgorithm
)
}

View File

@ -398,20 +398,28 @@ interface Synchronizer {
* Primary method that SDK clients will use to construct a synchronizer.
*
* @param zcashNetwork the network to use.
*
* @param alias A string used to segregate multiple wallets in the filesystem. This implies the string
* should not contain characters unsuitable for the platform's filesystem. The default value is
* generally used unless an SDK client needs to support multiple wallets.
*
* @param lightWalletEndpoint Server endpoint. See [cash.z.ecc.android.sdk.model.defaultForNetwork]. If a
* client wishes to change the server endpoint, the active synchronizer will need to be stopped and a new
* instance created with a new value.
*
* @param seed the wallet's seed phrase. This is required the first time a new wallet is set up. For
* subsequent calls, seed is only needed if [InitializerException.SeedRequired] is thrown.
*
* @param birthday Block height representing the "birthday" of the wallet. When creating a new wallet, see
* [BlockHeight.ofLatestCheckpoint]. When restoring an existing wallet, use block height that was first used
* to create the wallet. If that value is unknown, null is acceptable but will result in longer
* sync times. After sync completes, the birthday can be determined from [Synchronizer.latestBirthdayHeight].
*
* @param syncAlgorithm The CompactBlockProcess's type of block syncing algorithm
*
* @throws InitializerException.SeedRequired Indicates clients need to call this method again, providing the
* seed bytes.
*
* @throws IllegalStateException If multiple instances of synchronizer with the same network+alias are
* active at the same time. Call `close` to finish one synchronizer before starting another one with the same
* network+alias.
@ -427,7 +435,8 @@ interface Synchronizer {
alias: String = ZcashSdk.DEFAULT_ALIAS,
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?
birthday: BlockHeight?,
syncAlgorithm: CompactBlockProcessor.SyncAlgorithm = CompactBlockProcessor.SyncAlgorithm.LINEAR
): CloseableSynchronizer {
val applicationContext = context.applicationContext
@ -482,20 +491,20 @@ interface Synchronizer {
service
)
val processor = DefaultSynchronizerFactory.defaultProcessor(
backend,
downloader,
repository,
birthday
?: zcashNetwork.saplingActivationHeight
backend = backend,
downloader = downloader,
repository = repository,
birthdayHeight = birthday ?: zcashNetwork.saplingActivationHeight,
syncAlgorithm = syncAlgorithm
)
return SdkSynchronizer.new(
zcashNetwork,
alias,
repository,
txManager,
processor,
backend
zcashNetwork = zcashNetwork,
alias = alias,
repository = repository,
txManager = txManager,
processor = processor,
backend = backend
)
}
@ -513,9 +522,10 @@ interface Synchronizer {
alias: String = ZcashSdk.DEFAULT_ALIAS,
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?
birthday: BlockHeight?,
syncAlgorithm: CompactBlockProcessor.SyncAlgorithm = CompactBlockProcessor.SyncAlgorithm.LINEAR
): CloseableSynchronizer = runBlocking {
new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday)
new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday, syncAlgorithm)
}
/**

View File

@ -85,6 +85,8 @@ import kotlin.time.toDuration
* reorgs as a backstop to make sure we do not rewind beyond sapling activation. It also is factored
* in when considering initial range to download. In most cases, this should be the birthday height
* of the current wallet--the height before which we do not need to scan for transactions.
*
* @property syncAlgorithm The type of block syncing algorithm which should be preferably used
*/
@OpenForTesting
@Suppress("TooManyFunctions", "LargeClass")
@ -92,7 +94,8 @@ class CompactBlockProcessor internal constructor(
val downloader: CompactBlockDownloader,
private val repository: DerivedDataRepository,
private val backend: TypesafeBackend,
minimumHeight: BlockHeight
minimumHeight: BlockHeight,
private val syncAlgorithm: SyncAlgorithm
) {
/**
* Callback for any non-trivial errors that occur while processing compact blocks.
@ -196,8 +199,12 @@ class CompactBlockProcessor internal constructor(
)
// Download note commitment tree data from lightwalletd to decide if we communicate with linear
// or non-linear node
val subTreeRootList = getSubtreeRoots(downloader, network)
// or non-linear node. It depends on the syncAlgorithm property on the first place.
val subTreeRootList = if (syncAlgorithm == SyncAlgorithm.LINEAR) {
emptyList()
} else {
getSubtreeRoots(downloader, network)
}
Twig.info { "Fetched SubTreeRoot list size: ${subTreeRootList?.size ?: 0}" }
Twig.debug { "Setup verified. Processor starting..." }
@ -205,7 +212,10 @@ class CompactBlockProcessor internal constructor(
// Using do/while makes it easier to execute exactly one loop which helps with testing this processor quickly
// (because you can start and then immediately set isStopped=true to always get precisely one loop)
do {
retryWithBackoff(::onProcessorError, maxDelayMillis = MAX_BACKOFF_INTERVAL) {
retryWithBackoff(
onErrorListener = ::onProcessorError,
maxDelayMillis = MAX_BACKOFF_INTERVAL
) {
val result = processingMutex.withLockLogged("processNewBlocks") {
if (subTreeRootList.isNullOrEmpty()) {
processNewBlocksInLinearOrder()
@ -366,7 +376,7 @@ class CompactBlockProcessor internal constructor(
firstUnenhancedHeight: BlockHeight?
): BlockProcessingResult {
Twig.debug {
"Beginning to process new blocks with DAG approach (with roots: $subTreeRootList, and lower " +
"Beginning to process new blocks with Non-linear approach (with roots: $subTreeRootList, and lower " +
"bound: $lastValidHeight)..."
}
@ -952,7 +962,7 @@ class CompactBlockProcessor internal constructor(
retryUpToAndContinue(GET_SUBTREE_ROOTS_RETRIES) {
subTreeRootList = downloader.getSubtreeRoots(
// TODO [#1133]: DAG: Set the correct getSubtreeRoots inputs
// TODO [#1133]: SbS: Set the correct getSubtreeRoots inputs
// TODO [#1133]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1133
startIndex = 0,
maxEntries = if (network.isTestnet()) {
@ -2043,6 +2053,11 @@ class CompactBlockProcessor internal constructor(
val hash: String?
)
enum class SyncAlgorithm {
LINEAR,
NON_LINEAR
}
//
// Helper Extensions
//