From 90feb5348e5d5c07f9f894a307e374a3b75695f9 Mon Sep 17 00:00:00 2001 From: Kevin Gorham Date: Tue, 24 Nov 2020 00:36:23 -0500 Subject: [PATCH] New: Improve birthday configuration. Make it easier to restore wallets without knowing their birthday. Those wallets will sync from SaplingActivation without the developer having to hardcode that height into their wallet. We achieve this by allowing the Initializer Config object to specify what to do when the birthdayHeight is null. In general, it is probably easier to use the importWallet and newWallet functions to achieve the same goal with more clarity. --- .../cash/z/ecc/android/sdk/Initializer.kt | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/src/main/java/cash/z/ecc/android/sdk/Initializer.kt b/src/main/java/cash/z/ecc/android/sdk/Initializer.kt index 9de431c5..905f12d5 100644 --- a/src/main/java/cash/z/ecc/android/sdk/Initializer.kt +++ b/src/main/java/cash/z/ecc/android/sdk/Initializer.kt @@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk import android.content.Context import cash.z.ecc.android.sdk.exception.InitializerException import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.ext.ZcashSdk.SAPLING_ACTIVATION_HEIGHT import cash.z.ecc.android.sdk.ext.tryWarn import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.jni.RustBackend @@ -32,7 +33,9 @@ class Initializer constructor(appContext: Context, config: Config): SdkSynchron init { config.validate() - val loadedBirthday = WalletBirthdayTool.loadNearest(context, config.birthdayHeight) + val heightToUse = config.birthdayHeight + ?: (if (config.defaultToOldestHeight == true) SAPLING_ACTIVATION_HEIGHT else null) + val loadedBirthday = WalletBirthdayTool.loadNearest(context, heightToUse) birthday = loadedBirthday viewingKeys = config.viewingKeys alias = config.alias @@ -126,23 +129,83 @@ class Initializer constructor(appContext: Context, config: Config): SdkSynchron class Config private constructor ( - val viewingKeys: MutableList = mutableListOf(), - var birthdayHeight: Int? = null, - var alias: String = ZcashSdk.DEFAULT_ALIAS, - var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, - var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT + val viewingKeys: MutableList = mutableListOf(), + var alias: String = ZcashSdk.DEFAULT_ALIAS, + var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST, + var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT, ) { + var birthdayHeight: Int? = null + private set + + /** + * Determines the default behavior for null birthdays. When null, nothing has been specified + * so a null birthdayHeight value is an error. When false, null birthdays will be replaced + * with the most recent checkpoint height available (typically, the latest `*.json` file in + * `assets/zcash/saplingtree/`). When true, null birthdays will be replaced with the oldest + * reasonable height where a transaction could exist (typically, sapling activation but + * better approximations could be devised in the future, such as the date when the first + * BIP-39 zcash wallets came online). + */ + var defaultToOldestHeight: Boolean? = null + private set + constructor(block: (Config) -> Unit) : this() { block(this) - validate() } + + // + // Birthday functions + // + + /** + * Set the birthday height for this configuration. When the height is not known, the wallet + * can either default to the latest known birthday (in order to sync new wallets faster) or + * the oldest possible birthday (in order to import a wallet with an unknown birthday + * without skipping old transactions). + * + * @param height nullable birthday height to use for this configuration. + * @param defaultToOldestHeight determines how a null birthday height will be + * interpreted. Typically, `false` for new wallets and `true` for restored wallets because + * new wallets want to load quickly but restored wallets want to find all possible + * transactions. + * + */ + fun setBirthdayHeight(height: Int?, defaultToOldestHeight: Boolean = false): Config = + apply { + this.birthdayHeight = height + this.defaultToOldestHeight = defaultToOldestHeight + } + + /** + * Load the most recent checkpoint available. This is useful for new wallets. + */ + fun newWalletBirthday() { + birthdayHeight = null + defaultToOldestHeight = false + } + + /** + * Load the birthday checkpoint closest to the given wallet birthday. This is useful when + * importing a pre-existing wallet. It is the same as calling + * `birthdayHeight = importedHeight`. + */ + fun importedWalletBirthday(importedHeight: Int) { + birthdayHeight = importedHeight + defaultToOldestHeight = true + } + + + // + // Viewing key functions + // + /** * Add viewing keys to the set of accounts to monitor. Note: Using more than one viewing key * is not currently well supported. Consider it an alpha-preview feature that might work but * probably has serious bugs. */ - fun setViewingKeys(vararg extendedFullViewingKeys: String) { + fun setViewingKeys(vararg extendedFullViewingKeys: String): Config = apply { viewingKeys.apply { clear() addAll(extendedFullViewingKeys) @@ -154,34 +217,10 @@ class Initializer constructor(appContext: Context, config: Config): SdkSynchron * is not currently well supported. Consider it an alpha-preview feature that might work but * probably has serious bugs. */ - fun addViewingKey(extendedFullViewingKey: String) { + fun addViewingKey(extendedFullViewingKey: String): Config = apply { viewingKeys.add(extendedFullViewingKey) } - /** - * Load the most recent checkpoint available. This is useful for new wallets. - */ - fun newWalletBirthday() { - birthdayHeight = null - } - - /** - * Load the birthday checkpoint closest to the given wallet birthday. This is useful when - * importing a pre-existing wallet. It is the same as calling - * `birthdayHeight = importedHeight`. - */ - fun importedWalletBirthday(importedHeight: Int) { - birthdayHeight = importedHeight - } - - /** - * Theoretically, the oldest possible birthday a wallet could have. Useful for searching - * all transactions on the chain. In reality, no wallets were born at this height. - */ - fun saplingBirthday() { - birthdayHeight = ZcashSdk.SAPLING_ACTIVATION_HEIGHT - } - // // Convenience functions @@ -248,14 +287,17 @@ class Initializer constructor(appContext: Context, config: Config): SdkSynchron } private fun validateBirthday() { + // if birthday is missing then we need to know how to interpret it + // so defaultToOldestHeight ought to be set, in that case + if (birthdayHeight == null && defaultToOldestHeight == null) { + throw InitializerException.MissingDefaultBirthdayException + } // allow either null or a value greater than the activation height - require( - (birthdayHeight ?: ZcashSdk.SAPLING_ACTIVATION_HEIGHT) - >= ZcashSdk.SAPLING_ACTIVATION_HEIGHT + if ( + (birthdayHeight ?: SAPLING_ACTIVATION_HEIGHT) + < SAPLING_ACTIVATION_HEIGHT ) { - "Invalid birthday height of $birthdayHeight. The birthday" + - " height must be at least the height of Sapling activation on" + - " ${ZcashSdk.NETWORK} (${ZcashSdk.SAPLING_ACTIVATION_HEIGHT})." + throw InitializerException.InvalidBirthdayHeightException(birthdayHeight) } }