Docs: Update README to draw attention to the demo app.
This commit is contained in:
parent
a7b6d00cf7
commit
ad1f2438fb
22
README.md
22
README.md
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue