First pass at adding documentation.
This commit is contained in:
parent
785f9d5421
commit
de0d85c20d
|
@ -6,6 +6,10 @@ import cash.z.wallet.sdk.demoapp.App
|
|||
import cash.z.wallet.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.wallet.sdk.demoapp.databinding.FragmentGetAddressBinding
|
||||
|
||||
/**
|
||||
* Displays the address associated with the seed defined by the default config. To modify the seed
|
||||
* that is used, update the `DemoConfig.seedWords` value.
|
||||
*/
|
||||
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
||||
|
||||
private var seed: ByteArray = App.instance.defaultConfig.seed
|
||||
|
|
|
@ -8,6 +8,11 @@ import cash.z.wallet.sdk.demoapp.databinding.FragmentGetBlockBinding
|
|||
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
||||
import cash.z.wallet.sdk.service.LightWalletService
|
||||
|
||||
/**
|
||||
* Retrieves a compact block from the lightwalletd service and displays basic information about it.
|
||||
* This demonstrates the basic ability to connect to the server, request a compact block and parse
|
||||
* the response.
|
||||
*/
|
||||
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||
private val host = App.instance.defaultConfig.host
|
||||
private val port = App.instance.defaultConfig.port
|
||||
|
|
|
@ -8,6 +8,12 @@ import cash.z.wallet.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
|||
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
||||
import cash.z.wallet.sdk.service.LightWalletService
|
||||
|
||||
/**
|
||||
* Retrieves a range of compact block from the lightwalletd service and displays basic information
|
||||
* about them. This demonstrates the basic ability to connect to the server, request a range of
|
||||
* compact block and parse the response. This could be augmented to display metadata about certain
|
||||
* block ranges for instance, to find the block with the most shielded transactions in a range.
|
||||
*/
|
||||
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||
|
||||
private val host = App.instance.defaultConfig.host
|
||||
|
|
|
@ -8,6 +8,11 @@ import cash.z.wallet.sdk.demoapp.databinding.FragmentGetLatestHeightBinding
|
|||
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
||||
import cash.z.wallet.sdk.service.LightWalletService
|
||||
|
||||
/**
|
||||
* Retrieves the latest block height from the lightwalletd server. This is the simplest test for
|
||||
* connectivity with the server. Modify the `host` and the `port` to check the SDK's ability to
|
||||
* communicate with a given lightwalletd instance.
|
||||
*/
|
||||
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
|
||||
private val host = App.instance.defaultConfig.host
|
||||
private val port = App.instance.defaultConfig.port
|
||||
|
|
|
@ -6,6 +6,12 @@ import cash.z.wallet.sdk.demoapp.App
|
|||
import cash.z.wallet.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.wallet.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
|
||||
|
||||
/**
|
||||
* Displays the viewing key and spending key associated with the seed defined by the default config.
|
||||
* To modify the seed that is used, update the `DemoConfig.seedWords` value. This demo takes two
|
||||
* approaches to deriving the seed, one that is stateless and another that is not. In most cases, a
|
||||
* wallet instance will call `new` on an initializer and then store the result.
|
||||
*/
|
||||
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||
private var seed: ByteArray = App.instance.defaultConfig.seed
|
||||
private val initializer: Initializer = Initializer(App.instance)
|
||||
|
@ -20,8 +26,10 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
|||
/*
|
||||
* Initialize with the seed and retrieve one private key for each account specified (by
|
||||
* default, only 1 account is created). In a normal circumstance, a wallet app would then
|
||||
* store these keys in its secure storage for retrieval, later. Private keys are only needed
|
||||
* for sending funds.
|
||||
* store these keys in its secure storage for retrieval, later. Spending keys are only
|
||||
* needed when sending funds. Viewing keys can be derived from spending keys. In most cases,
|
||||
* a call to `initializer.new` or `initializer.import` are the only time a wallet passes the
|
||||
* seed to the SDK. From that point forward, only spending or viewing keys are needed.
|
||||
*/
|
||||
spendingKeys = initializer.new(seed, birthday)
|
||||
|
||||
|
|
|
@ -8,11 +8,15 @@ import android.widget.TextView
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import cash.z.wallet.sdk.demoapp.App
|
||||
import cash.z.wallet.sdk.demoapp.R
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
|
||||
/**
|
||||
* The landing page for the demo. Every time the app returns to this screen, it clears all demo
|
||||
* data just for sanity. The goal is for each demo to be self-contained so that the behavior is
|
||||
* repeatable and independent of pre-existing state.
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
|
||||
private lateinit var homeViewModel: HomeViewModel
|
||||
|
|
|
@ -18,8 +18,11 @@ import kotlinx.coroutines.flow.collect
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* List all transactions from a given seed and birthdate, defined in the Injector class which is
|
||||
* intended to mimic dependency injection.
|
||||
* List all transactions related to the given seed, since the given birthday. This begins by
|
||||
* downloading any missing blocks and then validating and scanning their contents. Once scan is
|
||||
* complete, the transactions are available in the database and can be accessed by any SQL tool.
|
||||
* By default, the SDK uses a PagedTransactionRepository to provide transaction contents from the
|
||||
* database in a paged format that works natively with RecyclerViews.
|
||||
*/
|
||||
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
|
||||
private val config = App.instance.defaultConfig
|
||||
|
|
|
@ -7,6 +7,9 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import cash.z.wallet.sdk.demoapp.R
|
||||
import cash.z.wallet.sdk.entity.ConfirmedTransaction
|
||||
|
||||
/**
|
||||
* Simple adapter implementation that knows how to bind a recyclerview to ClearedTransactions.
|
||||
*/
|
||||
class TransactionAdapter<T : ConfirmedTransaction> :
|
||||
PagedListAdapter<T, TransactionViewHolder<T>>(
|
||||
object : DiffUtil.ItemCallback<T>() {
|
||||
|
|
|
@ -9,6 +9,9 @@ import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Simple view holder for displaying confirmed transactions in the recyclerview.
|
||||
*/
|
||||
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
||||
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
||||
|
|
|
@ -15,6 +15,14 @@ import cash.z.wallet.sdk.demoapp.util.SampleStorageBridge
|
|||
import cash.z.wallet.sdk.entity.*
|
||||
import cash.z.wallet.sdk.ext.*
|
||||
|
||||
/**
|
||||
* Demonstrates sending funds to an address. This is the most complex example that puts all of the
|
||||
* pieces of the SDK together, including monitoring transactions for completion. It begins by
|
||||
* downloading, validating and scanning any missing blocks. Once that is complete, the wallet is
|
||||
* in a SYNCED state and available to send funds. Calling `sendToAddress` produces a flow of
|
||||
* PendingTransaction objects which represent the active state of the transaction that was sent.
|
||||
* Any time the state of that transaction changes, a new instance will be emitted.
|
||||
*/
|
||||
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
||||
private val config = App.instance.defaultConfig
|
||||
private val initializer = Initializer(App.instance, host = config.host, port = config.port)
|
||||
|
|
|
@ -7,6 +7,13 @@ import io.github.novacrypto.bip39.Words
|
|||
import io.github.novacrypto.bip39.wordlists.English
|
||||
import java.security.SecureRandom
|
||||
|
||||
/**
|
||||
* A sample implementation of a plugin for handling Mnemonic phrases. Any library can easily be
|
||||
* plugged into the SDK in this manner. In this case, we are wrapping a few example 3rd party
|
||||
* libraries with a thin layer that converts from their API to ours via the MnemonicPlugin
|
||||
* interface. We do not endorse these libraries, rather we just use them as an example of how to
|
||||
* take existing infrastructure and plug it into the SDK.
|
||||
*/
|
||||
class SimpleMnemonics : MnemonicPlugin {
|
||||
|
||||
override fun nextEntropy(): ByteArray {
|
||||
|
|
|
@ -16,7 +16,6 @@ data class DemoConfig(
|
|||
val toAddress: String = "zs1lcdmue7rewgvzh3jd09sfvwq3sumu6hkhpk53q94kcneuffjkdg9e3tyxrugkmpza5c3c5e6eqh"
|
||||
) {
|
||||
val seed: ByteArray get() = SimpleMnemonics().toSeed(seedWords.toCharArray())
|
||||
|
||||
fun newWalletBirthday() = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance)
|
||||
fun loadBirthday(height: Int = birthdayHeight) = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance, height)
|
||||
}
|
|
@ -19,6 +19,15 @@ import kotlin.reflect.KProperty
|
|||
* synchronizing begins. This begins with one of three actions, a call to either [new], [import] or
|
||||
* [open], where the last option is the most common case--when a user is opening a wallet they have
|
||||
* used before on this device.
|
||||
*
|
||||
* @param appContext the application context, used to extract the storage paths for the databases
|
||||
* and param files. A reference to the context is not held beyond initialization.
|
||||
* @param host the host that the synchronizer should use.
|
||||
* @param port the port that the synchronizer should use when connecting to the host.
|
||||
* @param alias the alias to use for this synchronizer. Think of it as a unique name that allows
|
||||
* multiple synchronizers to function in the same app. The alias is mapped to database names for the
|
||||
* cache and data DBs. This value is optional and is usually not required because most apps only
|
||||
* need one synchronizer.
|
||||
*/
|
||||
class Initializer(
|
||||
appContext: Context,
|
||||
|
@ -86,6 +95,8 @@ class Initializer(
|
|||
* DB.
|
||||
* @throws InitializerException.AlreadyInitializedException when the blocks table already exists
|
||||
* and [clearDataDb] is false.
|
||||
*
|
||||
* @return the spending key(s) associated with this wallet, for convenience.
|
||||
*/
|
||||
fun new(
|
||||
seed: ByteArray,
|
||||
|
@ -116,6 +127,8 @@ class Initializer(
|
|||
* DB.
|
||||
* @throws InitializerException.AlreadyInitializedException when the blocks table already exists
|
||||
* and [clearDataDb] is false.
|
||||
*
|
||||
* @return the spending key(s) associated with this wallet, for convenience.
|
||||
*/
|
||||
fun import(
|
||||
seed: ByteArray,
|
||||
|
@ -136,6 +149,10 @@ class Initializer(
|
|||
* wallet will use. This height helps with determining where to start downloading as well as how
|
||||
* far back to go during a rewind. Every wallet has a birthday and the initializer depends on
|
||||
* this value but does not own it.
|
||||
*
|
||||
* @return an instance of this class so that the function can be used fluidly. Spending keys are
|
||||
* not returned because the SDK does not store them and this function is for opening a wallet
|
||||
* that was created previously.
|
||||
*/
|
||||
fun open(birthday: WalletBirthday): Initializer {
|
||||
twig("Opening wallet with birthday ${birthday.height}")
|
||||
|
@ -150,6 +167,25 @@ class Initializer(
|
|||
* simply hold the address and viewing key for each account, which simplifies the process of
|
||||
* scanning and decrypting compact blocks.
|
||||
*
|
||||
* @param seed the seed to use for initializing accounts. We derive the address and the viewing
|
||||
* key(s) from this seed and also return the related spending key(s). Only the viewing key is
|
||||
* retained in the database in order to simplify scanning for the wallet.
|
||||
* @param birthday the birthday to use for this wallet. This is used in order to seed the data
|
||||
* DB with the first sapling tree, which also determines where the SDK begins downloading and
|
||||
* scanning. Any blocks lower than the height represented by this birthday can safely be ignored
|
||||
* since a wallet cannot have transactions prior to it's creation.
|
||||
* @param numberOfAccounts the number of accounts to create. Only 1 account is tested and
|
||||
* supported at this time. It is possible, although unlikely that multiple accounts would behave
|
||||
* as expected. Due to the nature of shielded address, the official Zcash recommendation is to
|
||||
* only use one address for shielded transactions. Unlike transparent coins, address rotation is
|
||||
* not necessary for shielded Zcash transactions because the sensitive information is private.
|
||||
* @param clearCacheDb when true, the cache DB will be deleted prior to initializing accounts.
|
||||
* This is useful for preventing errors when the database already exists, which happens often
|
||||
* in tests, demos and proof of concepts.
|
||||
* @param clearDataDb when true, the cache DB will be deleted prior to initializing accounts.
|
||||
* This is useful for preventing errors when the database already exists, which happens often
|
||||
* in tests, demos and proof of concepts.
|
||||
*
|
||||
* @return the spending keys for each account, ordered by index. These keys are only needed for
|
||||
* spending funds.
|
||||
*/
|
||||
|
@ -207,7 +243,9 @@ class Initializer(
|
|||
/**
|
||||
* Internal function used to initialize the [rustBackend] before use. Initialization should only
|
||||
* happen as a result of [new], [import] or [open] being called or as part of stand-alone key
|
||||
* derivation.
|
||||
* derivation. This involves loading the shared object file via `System.loadLibrary`.
|
||||
*
|
||||
* @return the rustBackend that was loaded by this initializer.
|
||||
*/
|
||||
private fun requireRustBackend(): RustBackend {
|
||||
if (!isInitialized) {
|
||||
|
@ -226,6 +264,10 @@ class Initializer(
|
|||
* Given a seed and a number of accounts, return the associated spending keys. These keys can
|
||||
* be used to derive the viewing keys.
|
||||
*
|
||||
* @param seed the seed from which to derive spending keys.
|
||||
* @param numberOfAccounts the number of accounts to use. Multiple accounts are not fully
|
||||
* supported so the default value of 1 is recommended.
|
||||
*
|
||||
* @return the spending keys that correspond to the seed, formatted as Strings.
|
||||
*/
|
||||
fun deriveSpendingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<String> =
|
||||
|
@ -234,6 +276,10 @@ class Initializer(
|
|||
/**
|
||||
* Given a seed and a number of accounts, return the associated viewing keys.
|
||||
*
|
||||
* @param seed the seed from which to derive viewing keys.
|
||||
* @param numberOfAccounts the number of accounts to use. Multiple accounts are not fully
|
||||
* supported so the default value of 1 is recommended.
|
||||
*
|
||||
* @return the viewing keys that correspond to the seed, formatted as Strings.
|
||||
*/
|
||||
fun deriveViewingKeys(seed: ByteArray, numberOfAccounts: Int = 1): Array<String> =
|
||||
|
@ -242,6 +288,8 @@ class Initializer(
|
|||
/**
|
||||
* Given a spending key, return the associated viewing key.
|
||||
*
|
||||
* @param spendingKey the key from which to derive the viewing key.
|
||||
*
|
||||
* @return the viewing key that corresponds to the spending key.
|
||||
*/
|
||||
fun deriveViewingKey(spendingKey: String): String =
|
||||
|
@ -250,6 +298,10 @@ class Initializer(
|
|||
/**
|
||||
* Given a seed and account index, return the associated address.
|
||||
*
|
||||
* @param seed the seed from which to derive the address.
|
||||
* @param accountIndex the index of the account to use for deriving the address. Multiple
|
||||
* accounts are not fully supported so the default value of 1 is recommended.
|
||||
*
|
||||
* @return the address that corresponds to the seed and account index.
|
||||
*/
|
||||
fun deriveAddress(seed: ByteArray, accountIndex: Int = 0) =
|
||||
|
@ -258,6 +310,9 @@ class Initializer(
|
|||
/**
|
||||
* Given a viewing key string, return the associated address.
|
||||
*
|
||||
* @param viewingKey the viewing key to use for deriving the address. The viewing key is tied to
|
||||
* a specific account so no account index is required.
|
||||
*
|
||||
* @return the address that corresponds to the viewing key.
|
||||
*/
|
||||
fun deriveAddress(viewingKey: String) =
|
||||
|
@ -270,9 +325,20 @@ class Initializer(
|
|||
// Path Helpers
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns the path to the cache database that would correspond to the given alias.
|
||||
*
|
||||
* @param appContext the application context
|
||||
* @param alias the alias to convert into a database path
|
||||
*/
|
||||
fun cacheDbPath(appContext: Context, alias: String): String =
|
||||
aliasToPath(appContext, alias, ZcashSdk.DB_CACHE_NAME)
|
||||
|
||||
/**
|
||||
* Returns the path to the data database that would correspond to the given alias.
|
||||
* @param appContext the application context
|
||||
* @param alias the alias to convert into a database path
|
||||
*/
|
||||
fun dataDbPath(appContext: Context, alias: String): String =
|
||||
aliasToPath(appContext, alias, ZcashSdk.DB_DATA_NAME)
|
||||
|
||||
|
@ -287,7 +353,12 @@ class Initializer(
|
|||
|
||||
|
||||
/**
|
||||
* Model object for holding wallet birthday. It is only used by this class.
|
||||
* Model object for holding a wallet birthday. It is only used by this class.
|
||||
*
|
||||
* @param height the height at the time the wallet was born.
|
||||
* @param hash the hash of the block at the height.
|
||||
* @param time the block time at the height.
|
||||
* @param tree the sapling tree corresponding to the height.
|
||||
*/
|
||||
data class WalletBirthday(
|
||||
val height: Int = -1,
|
||||
|
@ -296,27 +367,66 @@ class Initializer(
|
|||
val tree: String = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* Interface for classes that can handle birthday storage. This makes it possible to bridge into
|
||||
* existing storage logic. Instances of this interface can also be used as property delegates,
|
||||
* which enables the syntax `val birthday by birthdayStore`
|
||||
*/
|
||||
interface WalletBirthdayStore : ReadWriteProperty<R, WalletBirthday> {
|
||||
val newWalletBirthday: WalletBirthday
|
||||
|
||||
/**
|
||||
* Get the birthday of the wallet, saved in this store.
|
||||
*/
|
||||
fun getBirthday(): WalletBirthday
|
||||
|
||||
/**
|
||||
* Set the birthday of the wallet to be saved in this store.
|
||||
*/
|
||||
fun setBirthday(value: WalletBirthday)
|
||||
|
||||
/**
|
||||
* Load a birthday matching the given height. This is most commonly used during import to
|
||||
* find the first available checkpoint that is lower than the requested height.
|
||||
*
|
||||
* @param birthdayHeight the height to use as an upper bound for loading.
|
||||
*/
|
||||
fun loadBirthday(birthdayHeight: Int): WalletBirthday
|
||||
|
||||
/**
|
||||
* Return true when a birthday has been stored in this instance.
|
||||
*/
|
||||
fun hasExistingBirthday(): Boolean
|
||||
|
||||
/**
|
||||
* Return true when a birthday was imported into this instance.
|
||||
*/
|
||||
fun hasImportedBirthday(): Boolean
|
||||
|
||||
/* Property implementation that allows this interface to be used as a property delegate */
|
||||
|
||||
/**
|
||||
* Implement readable interface in order to be able to use instances of this interface as
|
||||
* property delegates.
|
||||
*/
|
||||
override fun getValue(thisRef: R, property: KProperty<*>): WalletBirthday {
|
||||
return getBirthday()
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement writable interface in order to be able to use instances of this interface as
|
||||
* property delegates.
|
||||
*/
|
||||
override fun setValue(thisRef: R, property: KProperty<*>, value: WalletBirthday) {
|
||||
setBirthday(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the [WalletBirthdayStore] interface that loads checkpoints from the
|
||||
* assets directory, in JSON format and stores the current birthday in shared preferences.
|
||||
*/
|
||||
class DefaultBirthdayStore(
|
||||
private val appContext: Context,
|
||||
private val importedBirthdayHeight: Int? = null,
|
||||
|
@ -370,6 +480,8 @@ class Initializer(
|
|||
* is there, the rest will be too. If that's not the case, a call to this function will
|
||||
* result in an exception.
|
||||
*
|
||||
* @param prefs the shared preference to use for loading the birthday.
|
||||
*
|
||||
* @return a birthday from preferences if one exists and null, otherwise null
|
||||
*/
|
||||
private fun loadBirthdayFromPrefs(prefs: SharedPreferences?): WalletBirthday? {
|
||||
|
@ -390,7 +502,7 @@ class Initializer(
|
|||
/**
|
||||
* Save the given birthday to the given preferences.
|
||||
*
|
||||
* @param prefs the shared preferences to use
|
||||
* @param prefs the shared preferences to use for saving the birthday.
|
||||
* @param birthday the birthday to save. It will be split into primitives.
|
||||
*/
|
||||
private fun saveBirthdayToPrefs(prefs: SharedPreferences, birthday: WalletBirthday) {
|
||||
|
@ -423,16 +535,37 @@ class Initializer(
|
|||
*/
|
||||
private const val BIRTHDAY_DIRECTORY = "zcash/saplingtree"
|
||||
|
||||
/**
|
||||
* The default alias to use for naming the preference file used for storage.
|
||||
*/
|
||||
const val DEFAULT_ALIAS = "default_prefs"
|
||||
|
||||
// Constructor function
|
||||
/**
|
||||
* A convenience constructor function for creating an instance of this class to use for
|
||||
* new wallets. It sets the stored birthday to match the `newWalletBirthday` checkpoint
|
||||
* which is typically the most recent checkpoint available.
|
||||
*
|
||||
* @param appContext the application context.
|
||||
* @param alias the alias to use when naming the preferences file used for storage.
|
||||
*/
|
||||
fun NewWalletBirthdayStore(appContext: Context, alias: String = DEFAULT_ALIAS): WalletBirthdayStore {
|
||||
return DefaultBirthdayStore(appContext, alias = alias).apply {
|
||||
setBirthday(newWalletBirthday)
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor function
|
||||
/**
|
||||
* A convenience constructor function for creating an instance of this class to use for
|
||||
* imported wallets. It sets the stored birthday to match the given
|
||||
* `importedBirthdayHeight` by finding the highest checkpoint that is below that height.
|
||||
*
|
||||
* @param appContext the application context.
|
||||
* @param importedBirthdayHeight the height corresponding to the birthday of the wallet
|
||||
* being imported. A checkpoint will be generated that allows scanning to start as close
|
||||
* to this height as possible because any blocks before this height can safely be
|
||||
* ignored since a wallet cannot have transactions before it is born.
|
||||
* @param alias the alias to use when naming the preferences file used for storage.
|
||||
*/
|
||||
fun ImportedWalletBirthdayStore(appContext: Context, importedBirthdayHeight: Int?, alias: String = DEFAULT_ALIAS): WalletBirthdayStore {
|
||||
return DefaultBirthdayStore(appContext, alias = alias).apply {
|
||||
if (importedBirthdayHeight != null) {
|
||||
|
@ -504,6 +637,8 @@ class Initializer(
|
|||
* permit the alias to be used as part of a file name for the preferences and databases. This
|
||||
* enables multiple wallets to exist on one device, which is also helpful for sweeping funds.
|
||||
*
|
||||
* @param alias the alias to validate.
|
||||
*
|
||||
* @throws IllegalArgumentException whenever the alias is not less than 100 characters or
|
||||
* contains something other than alphanumeric characters. Underscores are allowed but aliases
|
||||
* must start with a letter.
|
||||
|
|
|
@ -32,9 +32,9 @@ import kotlin.coroutines.CoroutineContext
|
|||
* pieces can be tied together. Its goal is to allow a developer to focus on their app rather than
|
||||
* the nuances of how Zcash works.
|
||||
*
|
||||
* @param ledger exposes flows of wallet transaction information.
|
||||
* @param manager manages and tracks outbound transactions.
|
||||
* @param processor saves the downloaded compact blocks to the cache and then scans those blocks for
|
||||
* @property ledger exposes flows of wallet transaction information.
|
||||
* @property manager manages and tracks outbound transactions.
|
||||
* @property processor saves the downloaded compact blocks to the cache and then scans those blocks for
|
||||
* data related to this wallet.
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
|
@ -144,6 +144,8 @@ class SdkSynchronizer internal constructor(
|
|||
* scope is only used for launching this synchronzer's job as a child. If no scope is provided,
|
||||
* then this synchronizer and all of its coroutines will run until stop is called, which is not
|
||||
* recommended since it can leak resources. That type of behavior is more useful for tests.
|
||||
*
|
||||
* @return an instance of this class so that this function can be used fluidly.
|
||||
*/
|
||||
override fun start(parentScope: CoroutineScope?): Synchronizer {
|
||||
if (::coroutineScope.isInitialized) throw SynchronizerException.FalseStart
|
||||
|
@ -181,6 +183,10 @@ class SdkSynchronizer internal constructor(
|
|||
ledger.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the latest balance, based on the blocks that have been scanned and transmit this
|
||||
* information into the flow of [balances].
|
||||
*/
|
||||
suspend fun refreshBalance() {
|
||||
twig("refreshing balance")
|
||||
_balances.send(processor.getBalanceInfo())
|
||||
|
@ -357,9 +363,19 @@ class SdkSynchronizer internal constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Simplest constructor possible. Useful for demos, sample apps or PoC's. Anything more complex
|
||||
* A convenience constructor that accepts the information most likely to change and uses defaults
|
||||
* for everything else. This is useful for demos, sample apps or PoC's. Anything more complex
|
||||
* will probably want to handle initialization, directly.
|
||||
*
|
||||
* @param appContext the application context. This is mostly used for finding databases and params
|
||||
* files within the apps secure storage area.
|
||||
* @param lightwalletdHost the lightwalletd host to use for connections.
|
||||
* @param lightwalletdPort the lightwalletd port to use for connections.
|
||||
* @param seed the seed to use for this wallet, when importing. Null when creating a new wallet.
|
||||
* @param birthdayStore the place to store the birthday of this wallet for future reference, which
|
||||
* allows something else to manage the state on behalf of the initializer.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
fun Synchronizer(
|
||||
appContext: Context,
|
||||
lightwalletdHost: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
|
||||
|
@ -387,6 +403,20 @@ fun Synchronizer(
|
|||
return Synchronizer(appContext, initializer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor function to use in most cases. This is a convenience function for when a wallet has
|
||||
* already created an initializer. Meaning, the basic flow is to call either [Initializer.new] or
|
||||
* [Initializer.import] on the first run and then [Initializer.open] for all subsequent launches of
|
||||
* the wallet. From there, the initializer is passed to this function in order to start syncing from
|
||||
* where the wallet left off.
|
||||
*
|
||||
* @param appContext the application context. This is mostly used for finding databases and params
|
||||
* files within the apps secure storage area.
|
||||
* @param initializer the helper that is leveraged for creating all the components that the
|
||||
* Synchronizer requires. It is mainly responsible for initializing the databases associated with
|
||||
* this synchronizer.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
fun Synchronizer(
|
||||
appContext: Context,
|
||||
initializer: Initializer
|
||||
|
@ -400,7 +430,24 @@ fun Synchronizer(
|
|||
|
||||
/**
|
||||
* Constructor function for building a Synchronizer in the most flexible way possible. This allows
|
||||
* a wallet maker to customize any subcomponent of the Synchronzier.
|
||||
* a wallet maker to customize any subcomponent of the Synchronzer.
|
||||
*
|
||||
* @param appContext the application context. This is mostly used for finding databases and params
|
||||
* files within the apps secure storage area.
|
||||
* @param lightwalletdHost the lightwalletd host to use for connections.
|
||||
* @param lightwalletdPort the lightwalletd port to use for connections.
|
||||
* @param ledger repository of wallet transactions, providing an agnostic interface to the
|
||||
* underlying information.
|
||||
* @param blockStore component responsible for storing compact blocks downloaded from lightwalletd.
|
||||
* @param service the lightwalletd service that can provide compact blocks and submit transactions.
|
||||
* @param encoder the component responsible for encoding transactions.
|
||||
* @param downloader the component responsible for downloading ranges of compact blocks.
|
||||
* @param manager the component that manages outbound transactions in order to report which ones are
|
||||
* still pending, particularly after failed attempts or dropped connectivity. The intent is to help
|
||||
* monitor outbound transactions status through to completion.
|
||||
* @param processor the component responsible for processing compact blocks. This is effectively the
|
||||
* brains of the synchronizer that implements most of the high-level business logic and determines
|
||||
* the current state of the wallet.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
fun Synchronizer(
|
||||
|
|
|
@ -25,6 +25,8 @@ interface Synchronizer {
|
|||
* @param parentScope the scope to use for this synchronizer, typically something with a
|
||||
* lifecycle such as an Activity. Implementations should leverage structured concurrency and
|
||||
* cancel all jobs when this scope completes.
|
||||
*
|
||||
* @return an instance of the class so that this function can be used fluidly.
|
||||
*/
|
||||
fun start(parentScope: CoroutineScope? = null): Synchronizer
|
||||
|
||||
|
@ -99,6 +101,8 @@ interface Synchronizer {
|
|||
*
|
||||
* @param accountId the optional accountId whose address is of interest. By default, the first
|
||||
* account is used.
|
||||
*
|
||||
* @return the address for the given account.
|
||||
*/
|
||||
suspend fun getAddress(accountId: Int = 0): String
|
||||
|
||||
|
@ -110,6 +114,11 @@ interface Synchronizer {
|
|||
* @param toAddress the recipient's address.
|
||||
* @param memo the optional memo to include as part of the transaction.
|
||||
* @param fromAccountId the optional account id to use. By default, the first account is used.
|
||||
*
|
||||
* @return a flow of PendingTransaction objects representing changes to the state of the
|
||||
* transaction. Any time the state changes a new instance will be emitted by this flow. This is
|
||||
* useful for updating the UI without needing to poll. Of course, polling is always an option
|
||||
* for any wallet that wants to ignore this return value.
|
||||
*/
|
||||
fun sendToAddress(
|
||||
spendingKey: String,
|
||||
|
@ -122,26 +131,38 @@ interface Synchronizer {
|
|||
|
||||
/**
|
||||
* Returns true when the given address is a valid z-addr. Invalid addresses will throw an
|
||||
* exception. Valid z-addresses have these characteristics: //TODO
|
||||
* exception. Valid z-addresses have these characteristics: //TODO copy info from related ZIP
|
||||
*
|
||||
* @param address the address to validate.
|
||||
*
|
||||
* @return true when the given address is a valid z-addr.
|
||||
*
|
||||
* @throws RuntimeException when the address is invalid.
|
||||
*/
|
||||
suspend fun isValidShieldedAddr(address: String): Boolean
|
||||
|
||||
/**
|
||||
* Returns true when the given address is a valid t-addr. Invalid addresses will throw an
|
||||
* exception. Valid t-addresses have these characteristics: //TODO
|
||||
* exception. Valid t-addresses have these characteristics: //TODO copy info from related ZIP
|
||||
*
|
||||
* @param address the address to validate.
|
||||
*
|
||||
* @return true when the given address is a valid t-addr.
|
||||
*
|
||||
* @throws RuntimeException when the address is invalid.
|
||||
*/
|
||||
suspend fun isValidTransparentAddr(address: String): Boolean
|
||||
|
||||
/**
|
||||
* Validates the given address, returning information about why it is invalid.
|
||||
* Validates the given address, returning information about why it is invalid. This is a
|
||||
* convenience method that combines the behavior of [isValidShieldedAddr] and
|
||||
* [isValidTransparentAddr] into one call so that the developer doesn't have to worry about
|
||||
* handling the exceptions that they throw. Rather, exceptions are converted to
|
||||
* [AddressType.Invalid] which has a `reason` property describing why it is invalid.
|
||||
*
|
||||
* @param address the address to validate.
|
||||
*
|
||||
* @return an instance of [AddressType] providing validation info regarding the given address.
|
||||
*/
|
||||
suspend fun validateAddress(address: String): AddressType
|
||||
|
||||
|
@ -150,6 +171,7 @@ interface Synchronizer {
|
|||
* an option if the transaction has not yet been submitted to the server.
|
||||
*
|
||||
* @param transaction the transaction to cancel.
|
||||
*
|
||||
* @return true when the cancellation request was successful. False when it is too late.
|
||||
*/
|
||||
suspend fun cancelSpend(transaction: PendingTransaction): Boolean
|
||||
|
@ -193,7 +215,9 @@ interface Synchronizer {
|
|||
*/
|
||||
var onChainErrorHandler: ((Int, Int) -> Any)?
|
||||
|
||||
|
||||
/**
|
||||
* Represents the status of this Synchronizer, which is useful for communicating to the user.
|
||||
*/
|
||||
enum class Status {
|
||||
/**
|
||||
* Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
|
||||
|
@ -231,12 +255,35 @@ interface Synchronizer {
|
|||
SYNCED
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the types of addresses, either Shielded, Transparent or Invalid.
|
||||
*/
|
||||
sealed class AddressType {
|
||||
/**
|
||||
* Marker interface for valid [AddressType] instances.
|
||||
*/
|
||||
interface Valid
|
||||
|
||||
/**
|
||||
* An instance of [AddressType] corresponding to a valid z-addr.
|
||||
*/
|
||||
object Shielded : Valid, AddressType()
|
||||
|
||||
/**
|
||||
* An instance of [AddressType] corresponding to a valid t-addr.
|
||||
*/
|
||||
object Transparent : Valid, AddressType()
|
||||
|
||||
/**
|
||||
* An instance of [AddressType] corresponding to an invalid address.
|
||||
*
|
||||
* @param reason a descrption of why the address was invalid.
|
||||
*/
|
||||
class Invalid(val reason: String = "Invalid") : AddressType()
|
||||
|
||||
/**
|
||||
* A convenience method that returns true when an instance of this class is invalid.
|
||||
*/
|
||||
val isNotValid get() = this !is Valid
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,20 @@ import androidx.room.RoomDatabase
|
|||
import cash.z.wallet.sdk.db.CompactBlockDao
|
||||
import cash.z.wallet.sdk.db.CompactBlockDb
|
||||
import cash.z.wallet.sdk.entity.CompactBlockEntity
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk.DB_CACHE_NAME
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* An implementation of CompactBlockStore that persists information to a database in the given
|
||||
* path. This represents the "cache db" or local cache of compact blocks waiting to be scanned.
|
||||
*
|
||||
* @param appContext the application context. This is used for creating the database.
|
||||
* @property dbPath the absolute path to the database.
|
||||
*/
|
||||
class CompactBlockDbStore(
|
||||
applicationContext: Context,
|
||||
appContext: Context,
|
||||
val dbPath: String
|
||||
) : CompactBlockStore {
|
||||
|
||||
|
@ -21,12 +27,12 @@ class CompactBlockDbStore(
|
|||
private val cacheDb: CompactBlockDb
|
||||
|
||||
init {
|
||||
cacheDb = createCompactBlockCacheDb(applicationContext)
|
||||
cacheDb = createCompactBlockCacheDb(appContext)
|
||||
cacheDao = cacheDb.complactBlockDao()
|
||||
}
|
||||
|
||||
private fun createCompactBlockCacheDb(applicationContext: Context): CompactBlockDb {
|
||||
return Room.databaseBuilder(applicationContext, CompactBlockDb::class.java, dbPath)
|
||||
private fun createCompactBlockCacheDb(appContext: Context): CompactBlockDb {
|
||||
return Room.databaseBuilder(appContext, CompactBlockDb::class.java, dbPath)
|
||||
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
|
||||
// this is a simple cache of blocks. destroying the db should be benign
|
||||
.fallbackToDestructiveMigration()
|
||||
|
|
|
@ -18,25 +18,52 @@ open class CompactBlockDownloader(
|
|||
val compactBlockStore: CompactBlockStore
|
||||
) {
|
||||
|
||||
/**
|
||||
* Requests the given range of blocks from the lightwalletService and then persists them to the
|
||||
* compactBlockStore.
|
||||
*
|
||||
* @param heightRange the inclusive range of heights to request. For example 10..20 would
|
||||
* request 11 blocks (including block 10 and block 20).
|
||||
*
|
||||
* @return the number of blocks that were returned in the results from the lightwalletService.
|
||||
*/
|
||||
suspend fun downloadBlockRange(heightRange: IntRange): Int = withContext(IO) {
|
||||
val result = lightwalletService.getBlockRange(heightRange)
|
||||
compactBlockStore.write(result)
|
||||
result.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the storage to the given height, usually to handle reorgs.
|
||||
*
|
||||
* @param height the height to which the data will rewind.
|
||||
*/
|
||||
suspend fun rewindToHeight(height: Int) = withContext(IO) {
|
||||
// TODO: cancel anything in flight
|
||||
compactBlockStore.rewindTo(height)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the latest block height known by the lightwalletService.
|
||||
*
|
||||
* @return the latest block height.
|
||||
*/
|
||||
suspend fun getLatestBlockHeight() = withContext(IO) {
|
||||
lightwalletService.getLatestBlockHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the latest block height that has been persisted into the [CompactBlockStore].
|
||||
*
|
||||
* @return the latest block height that has been persisted.
|
||||
*/
|
||||
suspend fun getLastDownloadedHeight() = withContext(IO) {
|
||||
compactBlockStore.getLatestHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop this downloader and cleanup any resources being used.
|
||||
*/
|
||||
fun stop() {
|
||||
lightwalletService.shutdown()
|
||||
compactBlockStore.close()
|
||||
|
|
|
@ -33,6 +33,10 @@ import kotlin.math.roundToInt
|
|||
* all the business logic required to validate and scan the blockchain and is therefore tightly coupled with
|
||||
* librustzcash.
|
||||
*
|
||||
* @property downloader the component responsible for downloading compact blocks and persisting them
|
||||
* locally for processing.
|
||||
* @property repository the repository holding transaction information.
|
||||
* @property rustBackend the librustzcash functionality available and exposed to the SDK.
|
||||
* @param minimumHeight the lowest height that we could care about. This is mostly used during
|
||||
* 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
|
||||
|
@ -45,7 +49,15 @@ class CompactBlockProcessor(
|
|||
private val rustBackend: RustBackendWelding,
|
||||
minimumHeight: Int = SAPLING_ACTIVATION_HEIGHT
|
||||
) {
|
||||
/**
|
||||
* Callback for any critical errors that occur while processing compact blocks.
|
||||
*/
|
||||
var onProcessorErrorListener: ((Throwable) -> Boolean)? = null
|
||||
|
||||
/**
|
||||
* Callbaqck for reorgs. This callback is invoked when validation fails with the height at which
|
||||
* an error was found and the lower bound to which the data will rewind, at most.
|
||||
*/
|
||||
var onChainErrorListener: ((Int, Int) -> Any)? = null
|
||||
|
||||
private val consecutiveChainErrors = AtomicInteger(0)
|
||||
|
@ -62,12 +74,26 @@ class CompactBlockProcessor(
|
|||
*/
|
||||
private var currentInfo = ProcessorInfo()
|
||||
|
||||
/**
|
||||
* The flow of state values so that a wallet can monitor the state of this class without needing
|
||||
* to poll.
|
||||
*/
|
||||
val state = _state.asFlow()
|
||||
|
||||
/**
|
||||
* The flow of progress values so that a wallet can monitor how much downloading remains
|
||||
* without needing to poll.
|
||||
*/
|
||||
val progress = _progress.asFlow()
|
||||
|
||||
/**
|
||||
* The flow of detailed processorInfo like the range of blocks that shall be downloaded and
|
||||
* scanned. This gives the wallet a lot of insight into the work of this processor.
|
||||
*/
|
||||
val processorInfo = _processorInfo.asFlow()
|
||||
|
||||
/**
|
||||
* Download compact blocks, verify and scan them.
|
||||
* Download compact blocks, verify and scan them until [stop] is called.
|
||||
*/
|
||||
suspend fun start() = withContext(IO) {
|
||||
twig("processor starting")
|
||||
|
@ -165,6 +191,8 @@ class CompactBlockProcessor(
|
|||
* in ascending order, with no gaps and are also chain-sequential. This means every block's
|
||||
* prevHash value matches the preceding block in the chain.
|
||||
*
|
||||
* @param lastScanRange the range to be validated and scanned.
|
||||
*
|
||||
* @return error code or -1 when there is no error.
|
||||
*/
|
||||
private suspend fun validateAndScanNewBlocks(lastScanRange: IntRange): Int = withContext(IO) {
|
||||
|
@ -194,7 +222,9 @@ class CompactBlockProcessor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Download all blocks in the given range.
|
||||
* Request all blocks in the given range and persist them locally for processing, later.
|
||||
*
|
||||
* @param range the range of blocks to download.
|
||||
*/
|
||||
@VisibleForTesting //allow mocks to verify how this is called, rather than the downloader, which is more complex
|
||||
internal suspend fun downloadNewBlocks(range: IntRange) = withContext<Unit>(IO) {
|
||||
|
@ -234,6 +264,11 @@ class CompactBlockProcessor(
|
|||
* Validate all blocks in the given range, ensuring that the blocks are in ascending order, with
|
||||
* no gaps and are also chain-sequential. This means every block's prevHash value matches the
|
||||
* preceding block in the chain.
|
||||
*
|
||||
* @param range the range of blocks to validate.
|
||||
*
|
||||
* @return -1 when there is not problem. Otherwise, return the lowest height where an error was
|
||||
* found. In other words, validation starts at the back of the chain and works toward the tip.
|
||||
*/
|
||||
private fun validateNewBlocks(range: IntRange?): Int {
|
||||
if (range?.isEmpty() != false) {
|
||||
|
@ -248,8 +283,13 @@ class CompactBlockProcessor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Scan all blocks in the given range, decrypting anything that matches our wallet and storing
|
||||
* the data.
|
||||
* Scan all blocks in the given range, decrypting and persisting anything that matches our
|
||||
* wallet.
|
||||
*
|
||||
* @param range the range of blocks to scan.
|
||||
*
|
||||
* @return -1 when there is not problem. Otherwise, return the lowest height where an error was
|
||||
* found. In other words, scanning starts at the back of the chain and works toward the tip.
|
||||
*/
|
||||
private suspend fun scanNewBlocks(range: IntRange?): Boolean = withContext(IO) {
|
||||
if (range?.isEmpty() != false) {
|
||||
|
@ -282,6 +322,13 @@ class CompactBlockProcessor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Emit an instance of processorInfo, corresponding to the provided data.
|
||||
*
|
||||
* @param networkBlockHeight the latest block available to lightwalletd that may or may not be
|
||||
* downloaded by this wallet yet.
|
||||
* @param lastScannedHeight the height up to which the wallet last scanned. This determines
|
||||
* where the next scan will begin.
|
||||
* @param lastDownloadedHeight the last compact block that was successfully downloaded.
|
||||
* @param lastScanRange the inclusive range to scan. This represents what we most recently
|
||||
* wanted to scan. In most cases, it will be an invalid range because we'd like to scan blocks
|
||||
* that we don't yet have.
|
||||
|
@ -326,14 +373,29 @@ class CompactBlockProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the last block that was downloaded by this processor.
|
||||
*
|
||||
* @return the last downloaded height reported by the downloader.
|
||||
*/
|
||||
suspend fun getLastDownloadedHeight() = withContext(IO) {
|
||||
downloader.getLastDownloadedHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the last block that was scanned by this processor.
|
||||
*
|
||||
* @return the last scanned height reported by the repository.
|
||||
*/
|
||||
suspend fun getLastScannedHeight() = withContext(IO) {
|
||||
repository.lastScannedHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address corresponding to the given account for this wallet.
|
||||
*
|
||||
* @return the address of this wallet.
|
||||
*/
|
||||
suspend fun getAddress(accountId: Int) = withContext(IO) {
|
||||
rustBackend.getAddress(accountId)
|
||||
}
|
||||
|
@ -342,6 +404,8 @@ class CompactBlockProcessor(
|
|||
* Calculates the latest balance info. Defaults to the first account.
|
||||
*
|
||||
* @param accountIndex the account to check for balance info.
|
||||
*
|
||||
* @return an instance of WalletBalance containing information about available and total funds.
|
||||
*/
|
||||
suspend fun getBalanceInfo(accountIndex: Int = 0): WalletBalance = withContext(IO) {
|
||||
twigTask("checking balance info") {
|
||||
|
@ -358,19 +422,65 @@ class CompactBlockProcessor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun setState(newState: State) {
|
||||
/**
|
||||
* Transmits the given state for this processor.
|
||||
*/
|
||||
private suspend fun setState(newState: State) {
|
||||
_state.send(newState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sealed class representing the various states of this processor.
|
||||
*/
|
||||
sealed class State {
|
||||
/**
|
||||
* Marker interface for [State] instances that represent when the wallet is connected.
|
||||
*/
|
||||
interface Connected
|
||||
|
||||
/**
|
||||
* Marker interface for [State] instances that represent when the wallet is syncing.
|
||||
*/
|
||||
interface Syncing
|
||||
|
||||
/**
|
||||
* [State] for when the wallet is actively downloading compact blocks because the latest
|
||||
* block height available from the server is greater than what we have locally. We move out
|
||||
* of this state once our local height matches the server.
|
||||
*/
|
||||
object Downloading : Connected, Syncing, State()
|
||||
|
||||
/**
|
||||
* [State] for when the blocks that have been downloaded are actively being validated to
|
||||
* ensure that there are no gaps and that every block is chain-sequential to the previous
|
||||
* block, which determines whether a reorg has happened on our watch.
|
||||
*/
|
||||
object Validating : Connected, Syncing, State()
|
||||
|
||||
/**
|
||||
* [State] for when the blocks that have been downloaded are actively being decrypted.
|
||||
*/
|
||||
object Scanning : Connected, Syncing, State()
|
||||
|
||||
/**
|
||||
* [State] for when we are done decrypting blocks, for now.
|
||||
*/
|
||||
class Scanned(val scannedRange:IntRange) : Connected, Syncing, State()
|
||||
|
||||
/**
|
||||
* [State] for when we have no connection to lightwalletd.
|
||||
*/
|
||||
object Disconnected : State()
|
||||
|
||||
/**
|
||||
* [State] for when [stop] has been called. For simplicity, processors should not be
|
||||
* restarted but they are not prevented from this behavior.
|
||||
*/
|
||||
object Stopped : State()
|
||||
|
||||
/**
|
||||
* [State] the initial state of the processor, once it is constructed.
|
||||
*/
|
||||
object Initialized : State()
|
||||
}
|
||||
|
||||
|
@ -390,6 +500,14 @@ class CompactBlockProcessor(
|
|||
)
|
||||
|
||||
/**
|
||||
* Data class for holding detailed information about the processor.
|
||||
*
|
||||
* @param networkBlockHeight the latest block available to lightwalletd that may or may not be
|
||||
* downloaded by this wallet yet.
|
||||
* @param lastScannedHeight the height up to which the wallet last scanned. This determines
|
||||
* where the next scan will begin.
|
||||
* @param lastDownloadedHeight the last compact block that was successfully downloaded.
|
||||
*
|
||||
* @param lastDownloadRange inclusive range to download. Meaning, if the range is 10..10,
|
||||
* then we will download exactly block 10. If the range is 11..10, then we want to download
|
||||
* block 11 but can't.
|
||||
|
@ -404,7 +522,9 @@ class CompactBlockProcessor(
|
|||
) {
|
||||
|
||||
/**
|
||||
* Returns false when all values match their defaults.
|
||||
* Determines whether this instance has data.
|
||||
*
|
||||
* @return false when all values match their defaults.
|
||||
*/
|
||||
val hasData get() = networkBlockHeight != -1
|
||||
|| lastScannedHeight != -1
|
||||
|
@ -413,13 +533,17 @@ class CompactBlockProcessor(
|
|||
|| lastScanRange != 0..-1
|
||||
|
||||
/**
|
||||
* Returns true when there are more than zero blocks remaining to download.
|
||||
* Determines whether this instance is actively downloading compact blocks.
|
||||
*
|
||||
* @return true when there are more than zero blocks remaining to download.
|
||||
*/
|
||||
val isDownloading: Boolean get() = !lastDownloadRange.isEmpty()
|
||||
&& lastDownloadedHeight < lastDownloadRange.last
|
||||
|
||||
/**
|
||||
* Returns true when downloading has completed and there are more than zero blocks remaining
|
||||
* Determines whether this instance is actively scanning or validating compact blocks.
|
||||
*
|
||||
* @return true when downloading has completed and there are more than zero blocks remaining
|
||||
* to be scanned.
|
||||
*/
|
||||
val isScanning: Boolean get() = !isDownloading
|
||||
|
|
|
@ -8,11 +8,15 @@ import cash.z.wallet.sdk.rpc.CompactFormats
|
|||
interface CompactBlockStore {
|
||||
/**
|
||||
* Gets the highest block that is currently stored.
|
||||
*
|
||||
* @return the latest block height.
|
||||
*/
|
||||
suspend fun getLatestHeight(): Int
|
||||
|
||||
/**
|
||||
* Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
|
||||
*
|
||||
* @param result the list of compact blocks to persist.
|
||||
*/
|
||||
suspend fun write(result: List<CompactFormats.CompactBlock>)
|
||||
|
||||
|
@ -21,6 +25,8 @@ interface CompactBlockStore {
|
|||
*
|
||||
* After this operation, the data store will look the same as one that has not yet stored the given block height.
|
||||
* Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
|
||||
*
|
||||
* @param height the target height to which to rewind.
|
||||
*/
|
||||
suspend fun rewindTo(height: Int)
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ import androidx.paging.PagedList
|
|||
import cash.z.wallet.sdk.entity.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Repository of wallet transactions, providing an agnostic interface to the underlying information.
|
||||
*/
|
||||
interface TransactionRepository {
|
||||
fun lastScannedHeight(): Int
|
||||
fun isInitialized(): Boolean
|
||||
|
|
Loading…
Reference in New Issue