Skip to content

Commit

Permalink
[Electric-Coin-Company#1334] WIP / Proposal Allow SDKSynchronizer to …
Browse files Browse the repository at this point in the history
…user arbitrary checkpoints instead of bundled ones

This is a draft PR intended to show the implementation I'd like to
propose. It does not compile for many reasons, like visibility of
checkpoint

would eventually close Electric-Coin-Company#1334
  • Loading branch information
pacu committed Jan 5, 2024
1 parent 8c6c2c2 commit 23eb28d
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.WalletInitMode
import cash.z.ecc.android.sdk.darkside.tools.DarksideCheckpointProvider
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
Expand Down Expand Up @@ -62,13 +63,14 @@ class TestWallet(
private val shieldedSpendingKey =
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer =
Synchronizer.newBlocking(
Synchronizer.buildNewBlocking(
context,
network,
alias,
endpoint,
seed,
startHeight,
DarksideCheckpointProvider(),
// Using existing wallet init mode as simplification for the test
walletInitMode = WalletInitMode.ExistingWallet
) as SdkSynchronizer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cash.z.ecc.android.sdk.darkside.tools
import android.content.Context
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork

internal class DarksideCheckpointProvider: CheckpointProvider {
suspend fun loadNearest(
context: Context,
network: ZcashNetwork,
birthdayHeight: BlockHeight?
): Checkpoint {
Checkpoint.getDarksideTestWalletBirthday()
}

suspend fun loadExact(
context: Context,
network: ZcashNetwork,
birthday: BlockHeight
): Checkpoint {
Checkpoint.getDarksideTestWalletBirthday()
}
}

fun Checkpoint.getDarksideTestWalletBirthday(): Checkpoint
{
"network": "main",
"height": "663150",
"hash": "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc",
"time": 1576821833,
"tree": "01ec6278a1bed9e1b080fd60ef50eb17411645e3746ff129283712bc4757ecc833001001b4e1d4a26ac4a2810b57a14f4ffb69395f55dde5674ecd2462af96f9126e054701a36afb68534f640938bdffd80dfcb3f4d5e232488abbf67d049b33a761e7ed6901a16e35205fb7fe626a9b13fc43e1d2b98a9c241f99f93d5e93a735454073025401f5b9bcbf3d0e3c83f95ee79299e8aeadf30af07717bda15ffb7a3d00243b58570001fa6d4c2390e205f81d86b85ace0b48f3ce0afb78eeef3e14c70bcfd7c5f0191c0000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13"
}

97 changes: 95 additions & 2 deletions sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointProvider
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.type.AddressType
import cash.z.ecc.android.sdk.type.ConsensusMatchType
Expand Down Expand Up @@ -429,6 +430,7 @@ interface Synchronizer {
}

companion object {

/**
* Primary method that SDK clients will use to construct a synchronizer.
*
Expand Down Expand Up @@ -475,6 +477,66 @@ interface Synchronizer {
seed: ByteArray?,
birthday: BlockHeight?,
walletInitMode: WalletInitMode
): CloseableSynchronizer {
return buildNew(
context,
zcashNetwork,
alias,
lightWalletEndpoint,
seed,
birthday,
CheckpointProvider.fromLocalAssets(),
walletInitMode
)
}

/**
* An internal method method that SDK tests and implementations 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 walletInitMode a required parameter with one of [WalletInitMode] values. Use
* [WalletInitMode.NewWallet] when starting synchronizer for a newly created wallet. Or use
* [WalletInitMode.RestoreWallet] when restoring an existing wallet that was created at some point in the
* past. Or use the last [WalletInitMode.ExistingWallet] type for a wallet which is already initialized
* and needs follow-up block synchronization.
*
* @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.
*
* If customized initialization is required (e.g. for dependency injection or testing), see
* [DefaultSynchronizerFactory].
*/
@Suppress("LongParameterList", "LongMethod")
internal suspend fun buildNew(
context: Context,
zcashNetwork: ZcashNetwork,
alias: String = ZcashSdk.DEFAULT_ALIAS,
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?,
checkpointProvider: CheckpointProvider = CheckpointProvider.fromLocalAssets(),
walletInitMode: WalletInitMode
): CloseableSynchronizer {
val applicationContext = context.applicationContext

Expand All @@ -483,7 +545,7 @@ interface Synchronizer {
val saplingParamTool = SaplingParamTool.new(applicationContext)

val loadedCheckpoint =
CheckpointTool.loadNearest(
checkpointProvider.loadNearest(
applicationContext,
zcashNetwork,
birthday ?: zcashNetwork.saplingActivationHeight
Expand Down Expand Up @@ -583,7 +645,38 @@ interface Synchronizer {
walletInitMode: WalletInitMode
): CloseableSynchronizer =
runBlocking {
new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday, walletInitMode)
buildNew(
context,
zcashNetwork,
alias,
lightWalletEndpoint,
seed,
birthday,
CheckpointProvider.fromLocalAssets(),
walletInitMode
)
}

/**
* Effectively the same as [new] although designed to be a blocking call with better
* interoperability with Java clients.
*
* This is a blocking call, so it should not be called from the main thread.
*/
@Suppress("LongParameterList")
internal fun buildNewBlocking(
context: Context,
zcashNetwork: ZcashNetwork,
alias: String = ZcashSdk.DEFAULT_ALIAS,
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?,
checkpointProvider: CheckpointProvider = CheckpointProvider.fromLocalAssets(),
walletInitMode: WalletInitMode
): CloseableSynchronizer =
runBlocking {
buildNew(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday, checkpointProvider,
walletInitMode)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.model

import cash.z.ecc.android.sdk.internal.ext.isInUIntRange
import cash.z.ecc.android.sdk.model.BlockHeight
import org.jetbrains.annotations.VisibleForTesting

/**
* Represents a checkpoint, which is used to speed sync times.
Expand All @@ -11,6 +12,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight
* @param epochSeconds the time of the block at [height].
* @param tree the sapling tree corresponding to [height].
*/
@VisibleForTesting
internal data class Checkpoint(
val height: BlockHeight,
val hash: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cash.z.ecc.android.sdk.tool;

import android.content.Context;

import cash.z.ecc.android.sdk.internal.model.Checkpoint;
import cash.z.ecc.android.sdk.model.BlockHeight;
import cash.z.ecc.android.sdk.model.ZcashNetwork;

/**
* Public interface for loading checkpoints for the wallet, based on the height at which the wallet was born.
*/
internal interface CheckpointProvider {

/**
* Load the nearest checkpoint to the given birthday height. If null is given, then this
* will load the most recent checkpoint available.
*/
suspend fun loadNearest(
context: Context,
network: ZcashNetwork,
birthdayHeight: BlockHeight?
): Checkpoint

/**
* Useful for when an exact checkpoint is needed, like for SAPLING_ACTIVATION_HEIGHT. In
* most cases, loading the nearest checkpoint is preferred for privacy reasons.
*/
suspend fun loadExact(
context: Context,
network: ZcashNetwork,
birthday: BlockHeight
): Checkpoint

companion object {
fun fromLocalAssets(): CheckpointProvider {
CheckpointTool
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.util.Locale
/**
* Tool for loading checkpoints for the wallet, based on the height at which the wallet was born.
*/
internal object CheckpointTool {
internal object CheckpointTool: CheckpointProvider {
// Behavior change implemented as a fix for issue #270. Temporarily adding a boolean
// that allows the change to be rolled back quickly if needed, although long-term
// this flag should be removed.
Expand Down

0 comments on commit 23eb28d

Please sign in to comment.