Docs: Update README to draw attention to the demo app.

This commit is contained in:
Kevin Gorham 2021-03-31 09:07:37 -04:00
parent a7b6d00cf7
commit ad1f2438fb
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
5 changed files with 125 additions and 5 deletions

View File

@ -22,10 +22,12 @@ This lightweight SDK connects Android to Zcash. It welds together Rust and Kotli
## Contents
- [Requirements](#requirements)
- [Structure](#structure)
- [Overview](#overview)
- [Components](#components)
- [Quickstart](#quickstart)
- [Examples](#examples)
- [Compiling Sources](#compiling-sources)
- [Versioning](#versioning)
- [Examples](#examples)
@ -126,6 +128,26 @@ Send funds to another address
synchronizer.sendToAddress(spendingKey, zatoshi, address, memo)
```
[Back to contents](#contents)
## Examples
Full working examples can be found in the [demo app](https://github.com/zcash/zcash-android-wallet-sdk/tree/master/samples/demo-app), covering all major functionality of the SDK. Each demo strives to be self-contained so that a developer can understand everything required for it to work. Testnet builds of the demo app will soon be available to [download as github releases](https://github.com/zcash/zcash-android-wallet-sdk/releases).
### Demos
Menu Item|Related Code|Description
:-----|:-----|:-----
Get Private Key|[GetPrivateKeyFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt)|Given a seed, display its viewing key and spending key
Get Address|[GetAddressFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt)|Given a seed, display its z-addr
Get Balance|[GetBalanceFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt)|Display the balance
Get Latest Height|[GetLatestHeightFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt)|Given a lightwalletd server, retrieve the latest block height
Get Block|[GetBlockFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt)|Given a lightwalletd server, retrieve a compact block
Get Block Range|[GetBlockRangeFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt)|Given a lightwalletd server, retrieve a range of compact blocks
List Transactions|[ListTransactionsFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt)|Given a seed, list all related shielded transactions
Send|[SendFragment.kt](app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt)|Send and monitor a transaction, the most complex demo
[Back to contents](#contents)
## Compiling Sources

View File

@ -196,6 +196,14 @@ class SdkSynchronizer internal constructor(
*/
override var onSubmissionErrorHandler: ((Throwable?) -> Boolean)? = null
/**
* A callback to invoke whenever a processor is not setup correctly. Returning true signals that
* the invalid setup should be ignored. If no handler is set, then any setup error will result
* in a critical error. This callback is not called on the main thread so any UI work would need
* to switch context to the main thread.
*/
override var onSetupErrorHandler: ((Throwable?) -> Boolean)? = null
/**
* A callback to invoke whenever a chain error is encountered. These occur whenever the
* processor detects a missing or non-chain-sequential block (i.e. a reorg).
@ -331,6 +339,7 @@ class SdkSynchronizer internal constructor(
twig("Synchronizer (${this@SdkSynchronizer}) Ready. Starting processor!")
var lastScanTime = 0L
processor.onProcessorErrorListener = ::onProcessorError
processor.onSetupErrorListener = ::onSetupError
processor.onChainErrorListener = ::onChainError
processor.state.onEach {
when (it) {
@ -401,6 +410,17 @@ class SdkSynchronizer internal constructor(
} == true
}
private fun onSetupError(error: Throwable): Boolean {
if (onSetupErrorHandler == null) {
twig(
"WARNING: falling back to the default behavior for setup errors. To add custom" +
" behavior, set synchronizer.onSetupErrorHandler to a non-null value"
)
return false
}
return onSetupErrorHandler?.invoke(error) == true
}
private fun onChainError(errorHeight: Int, rewindHeight: Int) {
twig("Chain error detected at height: $errorHeight. Rewinding to: $rewindHeight")
if (onChainErrorHandler == null) {

View File

@ -309,6 +309,18 @@ interface Synchronizer {
*/
var onSubmissionErrorHandler: ((Throwable?) -> Boolean)?
/**
* Callback for setup errors that occur prior to processing compact blocks. Can be used to
* override any errors encountered during setup. When this listener is missing then all setup
* errors will result in the synchronizer not starting. This is particularly useful for wallets
* to receive a callback right before the SDK will reject a lightwalletd server because it
* appears not to match.
*
* @return true when the setup error should be ignored and processing should be allowed to
* start. Otherwise, processing will not begin.
*/
var onSetupErrorHandler: ((Throwable?) -> Boolean)?
/**
* A callback to invoke whenever a chain error is encountered. These occur whenever the
* processor detects a missing or non-chain-sequential block (i.e. a reorg).

View File

@ -15,9 +15,12 @@ import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxDecryptError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxDownloadError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedBranch
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedNetwork
import cash.z.ecc.android.sdk.exception.RustLayerException
import cash.z.ecc.android.sdk.ext.BatchMetrics
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.ZcashSdk.DOWNLOAD_BATCH_SIZE
import cash.z.ecc.android.sdk.ext.ZcashSdk.MAX_BACKOFF_INTERVAL
import cash.z.ecc.android.sdk.ext.ZcashSdk.MAX_REORG_SIZE
@ -41,7 +44,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicInteger
@ -84,6 +89,18 @@ class CompactBlockProcessor(
*/
var onChainErrorListener: ((errorHeight: Int, rewindHeight: Int) -> Any)? = null
/**
* Callback for setup errors that occur prior to processing compact blocks. Can be used to
* override any errors from [verifySetup]. When this listener is missing then all setup errors
* will result in the processor not starting. This is particularly useful for wallets to receive
* a callback right before the SDK will reject a lightwalletd server because it appears not to
* match.
*
* @return true when the setup error should be ignored and processing should be allowed to
* start. Otherwise, processing will not begin.
*/
var onSetupErrorListener: ((Throwable) -> Boolean)? = null
/**
* Callback for apps to report scan times. As blocks are scanned in batches, this listener is
* invoked at the end of every batch and the second parameter is only true when all batches are
@ -143,8 +160,9 @@ class CompactBlockProcessor(
* Download compact blocks, verify and scan them until [stop] is called.
*/
suspend fun start() = withContext(IO) {
twig("processor starting")
verifySetup()
updateBirthdayHeight()
twig("setup verified. processor starting")
// 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)
@ -202,7 +220,6 @@ class CompactBlockProcessor(
* return the block height where an error was found.
*/
private suspend fun processNewBlocks(): Int = withContext(IO) {
verifySetup()
twig("beginning to process new blocks (with lower bound: $lowerBoundHeight)...")
if (!updateRanges()) {
@ -340,8 +357,30 @@ class CompactBlockProcessor(
/**
* Confirm that the wallet data is properly setup for use.
*/
private fun verifySetup() {
if (!repository.isInitialized()) throw CompactBlockProcessorException.Uninitialized
private suspend fun verifySetup() {
// verify that the data is initialized
var error = if (!repository.isInitialized()) {
CompactBlockProcessorException.Uninitialized
} else {
// verify that the server is correct
downloader.getServerInfo().let { info ->
val clientBranch = "%x".format(rustBackend.getBranchIdForHeight(info.blockHeight.toInt()))
when {
!info.matchingNetwork(ZcashSdk.NETWORK) -> MismatchedNetwork(clientNetwork = ZcashSdk.NETWORK, serverNetwork = info.chainName)
!info.matchingConsensusBranchId(clientBranch) -> MismatchedBranch(clientBranch = clientBranch, serverBranch = info.consensusBranchId)
else -> null
}
}
}
if (error != null) {
// give listener a chance to override
if (onSetupErrorListener?.invoke(error) != true) {
throw error
} else {
twig("Warning: An ${error::class.java.simpleName} was encountered while verifying setup but it was ignored by the onSetupErrorHandler. Ignoring message: ${error.message}")
}
}
}
internal suspend fun downloadUtxos(tAddress: String, startHeight: Int): Int = withContext(IO) {
@ -594,7 +633,7 @@ class CompactBlockProcessor(
val interval = POLL_INTERVAL
val now = System.currentTimeMillis()
val deltaToNextInteral = interval - (now + interval).rem(interval)
twig("sleeping for ${deltaToNextInteral}ms from $now in order to wake at ${now + deltaToNextInteral}")
// twig("sleeping for ${deltaToNextInteral}ms from $now in order to wake at ${now + deltaToNextInteral}")
return deltaToNextInteral
}
@ -830,6 +869,25 @@ class CompactBlockProcessor(
val actualPrevHash: String?
)
//
// Helper Extensions
//
private fun Service.LightdInfo.matchingConsensusBranchId(clientBranch: String): Boolean {
return consensusBranchId.equals(clientBranch, true)
}
private fun Service.LightdInfo.matchingNetwork(network: String): Boolean {
fun String.toId() = toLowerCase().run {
when {
contains("main") -> "mainnet"
contains("test") -> "testnet"
else -> this
}
}
return chainName.toId() == network.toId()
}
companion object {
const val ERROR_CODE_NONE = -1
const val ERROR_CODE_RECONNECT = 20

View File

@ -88,6 +88,14 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
class EnhanceTxDownloadError(height: Int, cause: Throwable) : EnhanceTransactionError("Error while attempting to download a transaction to enhance", height, cause)
class EnhanceTxDecryptError(height: Int, cause: Throwable) : EnhanceTransactionError("Error while attempting to decrypt and store a transaction to enhance", height, cause)
}
class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."
)
class MismatchedBranch(clientBranch: String?, serverBranch: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server following consensus branch $clientBranch but it was $serverBranch! Try updating the client or switching servers."
)
}
/**