2019-10-23 22:21:52 -07:00
|
|
|
package cash.z.wallet.sdk
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-10-21 03:26:02 -07:00
|
|
|
import android.content.Context
|
2019-11-01 13:25:28 -07:00
|
|
|
import cash.z.wallet.sdk.Synchronizer.Status.*
|
2019-10-21 03:26:02 -07:00
|
|
|
import cash.z.wallet.sdk.block.CompactBlockDbStore
|
|
|
|
import cash.z.wallet.sdk.block.CompactBlockDownloader
|
2019-07-10 11:12:32 -07:00
|
|
|
import cash.z.wallet.sdk.block.CompactBlockProcessor
|
2019-10-21 03:26:02 -07:00
|
|
|
import cash.z.wallet.sdk.block.CompactBlockProcessor.State.*
|
2019-11-01 13:25:28 -07:00
|
|
|
import cash.z.wallet.sdk.block.CompactBlockProcessor.WalletBalance
|
2019-10-21 03:26:02 -07:00
|
|
|
import cash.z.wallet.sdk.block.CompactBlockStore
|
2019-11-01 13:25:28 -07:00
|
|
|
import cash.z.wallet.sdk.entity.*
|
2019-08-30 10:05:02 -07:00
|
|
|
import cash.z.wallet.sdk.exception.SynchronizerException
|
2019-11-23 17:47:50 -08:00
|
|
|
import cash.z.wallet.sdk.ext.ZcashSdk
|
2019-10-23 22:21:52 -07:00
|
|
|
import cash.z.wallet.sdk.ext.twig
|
2019-11-01 13:25:28 -07:00
|
|
|
import cash.z.wallet.sdk.ext.twigTask
|
|
|
|
import cash.z.wallet.sdk.jni.RustBackend
|
2019-10-21 03:26:02 -07:00
|
|
|
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
|
|
|
import cash.z.wallet.sdk.service.LightWalletService
|
2019-10-23 22:21:52 -07:00
|
|
|
import cash.z.wallet.sdk.transaction.*
|
2019-07-10 11:12:32 -07:00
|
|
|
import kotlinx.coroutines.*
|
|
|
|
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
2019-11-01 13:25:28 -07:00
|
|
|
import kotlinx.coroutines.flow.*
|
2019-07-10 11:12:32 -07:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
|
|
|
|
2019-10-21 03:26:02 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* A Synchronizer that attempts to remain operational, despite any number of errors that can occur.
|
|
|
|
* It acts as the glue that ties all the pieces of the SDK together. Each component of the SDK is
|
|
|
|
* designed for the potential of stand-alone usage but coordinating all the interactions is non-
|
|
|
|
* trivial. So the Synchronizer facilitates this, acting as reference that demonstrates how all the
|
|
|
|
* 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.
|
2019-08-30 10:05:02 -07:00
|
|
|
*
|
2019-11-01 13:25:28 -07:00
|
|
|
* @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
|
2019-08-30 10:05:02 -07:00
|
|
|
* data related to this wallet.
|
2019-07-10 11:12:32 -07:00
|
|
|
*/
|
|
|
|
@ExperimentalCoroutinesApi
|
2019-11-01 13:25:28 -07:00
|
|
|
class SdkSynchronizer internal constructor(
|
2019-11-22 23:18:20 -08:00
|
|
|
private val ledger: TransactionRepository,
|
2019-11-01 13:25:28 -07:00
|
|
|
private val manager: OutboundTransactionManager,
|
2020-01-06 22:26:10 -08:00
|
|
|
val processor: CompactBlockProcessor
|
2019-07-14 15:13:12 -07:00
|
|
|
) : Synchronizer {
|
2019-12-23 11:50:52 -08:00
|
|
|
private val _balances = ConflatedBroadcastChannel(WalletBalance())
|
|
|
|
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-07-12 01:47:17 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* The lifespan of this Synchronizer. This scope is initialized once the Synchronizer starts
|
|
|
|
* because it will be a child of the parentScope that gets passed into the [start] function.
|
|
|
|
* Everything launched by this Synchronizer will be cancelled once the Synchronizer or its
|
|
|
|
* parentScope stops. This is a lateinit rather than nullable property so that it fails early
|
|
|
|
* rather than silently, whenever the scope is used before the Synchronizer has been started.
|
2019-07-12 01:47:17 -07:00
|
|
|
*/
|
|
|
|
lateinit var coroutineScope: CoroutineScope
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-07-12 01:47:17 -07:00
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
//
|
|
|
|
// Transactions
|
|
|
|
//
|
|
|
|
|
2019-11-22 23:18:20 -08:00
|
|
|
override val balances: Flow<WalletBalance> = _balances.asFlow()
|
|
|
|
override val clearedTransactions = ledger.allTransactions
|
|
|
|
override val pendingTransactions = manager.getAll()
|
|
|
|
override val sentTransactions = ledger.sentTransactions
|
|
|
|
override val receivedTransactions = ledger.receivedTransactions
|
2019-11-01 13:25:28 -07:00
|
|
|
|
|
|
|
|
2019-07-12 01:47:17 -07:00
|
|
|
//
|
2019-08-30 10:05:02 -07:00
|
|
|
// Status
|
2019-07-12 01:47:17 -07:00
|
|
|
//
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-08-30 10:05:02 -07:00
|
|
|
/**
|
2019-10-21 03:26:02 -07:00
|
|
|
* Indicates the status of this Synchronizer. This implementation basically simplifies the
|
2019-11-22 23:18:20 -08:00
|
|
|
* status of the processor to focus only on the high level states that matter most. Whenever the
|
|
|
|
* processor is finished scanning, the synchronizer updates transaction and balance info and
|
|
|
|
* then emits a [SYNCED] status.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-11-22 23:18:20 -08:00
|
|
|
override val status = _status.asFlow()
|
2019-07-12 01:47:17 -07:00
|
|
|
|
2019-08-30 10:05:02 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* Indicates the download progress of the Synchronizer. When progress reaches 100, that
|
|
|
|
* signals that the Synchronizer is in sync with the network. Balances should be considered
|
|
|
|
* inaccurate and outbound transactions should be prevented until this sync is complete.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-11-01 13:25:28 -07:00
|
|
|
override val progress: Flow<Int> = processor.progress
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-07-12 01:47:17 -07:00
|
|
|
|
|
|
|
//
|
2019-07-14 15:13:12 -07:00
|
|
|
// Error Handling
|
2019-07-12 01:47:17 -07:00
|
|
|
//
|
|
|
|
|
2019-08-30 10:05:02 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* A callback to invoke whenever an uncaught error is encountered. By definition, the return
|
|
|
|
* value of the function is ignored because this error is unrecoverable. The only reason the
|
|
|
|
* function has a return value is so that all error handlers work with the same signature which
|
|
|
|
* allows one function to handle all errors in simple apps. This callback is not called on the
|
|
|
|
* main thread so any UI work would need to switch context to the main thread.
|
2019-07-14 15:13:12 -07:00
|
|
|
*/
|
|
|
|
override var onCriticalErrorHandler: ((Throwable?) -> Boolean)? = null
|
2019-08-30 10:05:02 -07:00
|
|
|
|
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* A callback to invoke whenever a processor error is encountered. Returning true signals that
|
|
|
|
* the error was handled and a retry attempt should be made, if possible. This callback is not
|
|
|
|
* called on the main thread so any UI work would need to switch context to the main thread.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-07-14 15:13:12 -07:00
|
|
|
override var onProcessorErrorHandler: ((Throwable?) -> Boolean)? = null
|
2019-08-30 10:05:02 -07:00
|
|
|
|
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* A callback to invoke whenever a server error is encountered while submitting a transaction to
|
|
|
|
* lightwalletd. Returning true signals that the error was handled and a retry attempt should be
|
|
|
|
* made, if possible. This callback is not called on the main thread so any UI work would need
|
|
|
|
* to switch context to the main thread.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-07-14 15:13:12 -07:00
|
|
|
override var onSubmissionErrorHandler: ((Throwable?) -> Boolean)? = null
|
2019-07-12 01:47:17 -07:00
|
|
|
|
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
//
|
|
|
|
// Public API
|
|
|
|
//
|
|
|
|
|
2019-08-30 10:05:02 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* Starts this synchronizer within the given scope. For simplicity, attempting to start an
|
|
|
|
* instance that has already been started will throw a [SynchronizerException.FalseStart]
|
|
|
|
* exception. This reduces the complexity of managing resources that must be recycled. Instead,
|
|
|
|
* each synchronizer is designed to have a long lifespan and should be started from an activity,
|
|
|
|
* application or session.
|
2019-08-30 10:05:02 -07:00
|
|
|
*
|
2019-11-01 13:25:28 -07:00
|
|
|
* @param parentScope the scope to use for this synchronizer, typically something with a
|
|
|
|
* lifecycle such as an Activity for single-activity apps or a logged in user session. This
|
2019-11-26 12:46:31 -08:00
|
|
|
* 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.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-11-26 12:46:31 -08:00
|
|
|
override fun start(parentScope: CoroutineScope?): Synchronizer {
|
2019-08-30 10:05:02 -07:00
|
|
|
if (::coroutineScope.isInitialized) throw SynchronizerException.FalseStart
|
|
|
|
|
2019-11-22 23:18:20 -08:00
|
|
|
// base this scope on the parent so that when the parent's job cancels, everything here
|
|
|
|
// cancels as well also use a supervisor job so that one failure doesn't bring down the
|
|
|
|
// whole synchronizer
|
2019-11-26 12:46:31 -08:00
|
|
|
val supervisorJob = SupervisorJob(parentScope?.coroutineContext?.get(Job))
|
2019-11-01 13:25:28 -07:00
|
|
|
coroutineScope =
|
2019-11-26 12:46:31 -08:00
|
|
|
CoroutineScope(supervisorJob + Dispatchers.Main)
|
2019-11-01 13:25:28 -07:00
|
|
|
coroutineScope.onReady()
|
2019-07-14 15:13:12 -07:00
|
|
|
return this
|
2019-07-12 01:47:17 -07:00
|
|
|
}
|
|
|
|
|
2019-08-30 10:05:02 -07:00
|
|
|
/**
|
2019-11-01 13:25:28 -07:00
|
|
|
* Stop this synchronizer and all of its child jobs. Once a synchronizer has been stopped it
|
|
|
|
* should not be restarted and attempting to do so will result in an error. Also, this function
|
|
|
|
* will throw an exception if the synchronizer was never previously started.
|
2019-08-30 10:05:02 -07:00
|
|
|
*/
|
2019-07-10 11:12:32 -07:00
|
|
|
override fun stop() {
|
2019-10-21 03:26:02 -07:00
|
|
|
coroutineScope.launch {
|
|
|
|
processor.stop()
|
|
|
|
coroutineScope.cancel()
|
2019-11-22 23:18:20 -08:00
|
|
|
_balances.cancel()
|
|
|
|
_status.cancel()
|
2019-10-21 03:26:02 -07:00
|
|
|
}
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
2019-11-01 13:25:28 -07:00
|
|
|
// Private API
|
2019-07-10 11:12:32 -07:00
|
|
|
//
|
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
private fun refreshTransactions() {
|
|
|
|
ledger.invalidate()
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
2019-12-23 11:50:52 -08:00
|
|
|
suspend fun refreshBalance() {
|
2019-11-22 23:18:20 -08:00
|
|
|
_balances.send(processor.getBalanceInfo())
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun CoroutineScope.onReady() = launch(CoroutineExceptionHandler(::onCriticalError)) {
|
|
|
|
twig("Synchronizer Ready. Starting processor!")
|
|
|
|
processor.onErrorListener = ::onProcessorError
|
2019-11-22 23:18:20 -08:00
|
|
|
processor.state.onEach {
|
|
|
|
when (it) {
|
|
|
|
is Scanned -> {
|
|
|
|
onScanComplete(it.scannedRange)
|
|
|
|
SYNCED
|
|
|
|
}
|
|
|
|
is Stopped -> STOPPED
|
|
|
|
is Disconnected -> DISCONNECTED
|
|
|
|
else -> SYNCING
|
|
|
|
}.let { synchronizerStatus ->
|
|
|
|
_status.send(synchronizerStatus)
|
2019-11-01 13:25:28 -07:00
|
|
|
}
|
2019-10-21 03:26:02 -07:00
|
|
|
}.launchIn(this)
|
2019-07-10 11:12:32 -07:00
|
|
|
processor.start()
|
|
|
|
twig("Synchronizer onReady complete. Processor start has exited!")
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun onCriticalError(unused: CoroutineContext, error: Throwable) {
|
|
|
|
twig("********")
|
|
|
|
twig("******** ERROR: $error")
|
|
|
|
if (error.cause != null) twig("******** caused by ${error.cause}")
|
|
|
|
if (error.cause?.cause != null) twig("******** caused by ${error.cause?.cause}")
|
|
|
|
twig("********")
|
|
|
|
|
2019-07-14 15:13:12 -07:00
|
|
|
onCriticalErrorHandler?.invoke(error)
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
2019-07-14 15:13:12 -07:00
|
|
|
private fun onFailedSend(error: Throwable): Boolean {
|
|
|
|
twig("ERROR while submitting transaction: $error")
|
|
|
|
return onSubmissionErrorHandler?.invoke(error)?.also {
|
|
|
|
if (it) twig("submission error handler signaled that we should try again!")
|
|
|
|
} == true
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
2019-07-14 15:13:12 -07:00
|
|
|
private fun onProcessorError(error: Throwable): Boolean {
|
|
|
|
twig("ERROR while processing data: $error")
|
2019-10-21 03:26:02 -07:00
|
|
|
if (onProcessorErrorHandler == null) {
|
2019-10-23 22:21:52 -07:00
|
|
|
twig(
|
|
|
|
"WARNING: falling back to the default behavior for processor errors. To add" +
|
|
|
|
" custom behavior, set synchronizer.onProcessorErrorHandler to" +
|
|
|
|
" a non-null value"
|
|
|
|
)
|
2019-10-21 03:26:02 -07:00
|
|
|
return true
|
|
|
|
}
|
2019-07-14 15:13:12 -07:00
|
|
|
return onProcessorErrorHandler?.invoke(error)?.also {
|
2019-10-23 22:21:52 -07:00
|
|
|
twig(
|
|
|
|
"processor error handler signaled that we should " +
|
|
|
|
"${if (it) "try again" else "abort"}!"
|
|
|
|
)
|
2019-07-14 15:13:12 -07:00
|
|
|
} == true
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|
|
|
|
|
2019-11-22 23:18:20 -08:00
|
|
|
private suspend fun onScanComplete(scannedRange: IntRange) {
|
|
|
|
// TODO: optimize to skip logic here if there are no new transactions with a block height
|
|
|
|
// within the given range
|
|
|
|
|
|
|
|
// TRICKY:
|
|
|
|
// Keep an eye on this section because there is a potential for concurrent DB
|
|
|
|
// modification. A change in transactions means a change in balance. Calculating the
|
|
|
|
// balance requires touching transactions. If both are done in separate threads, the
|
|
|
|
// database can have issues. On Android, this would manifest as a false positive for a
|
|
|
|
// "malformed database" exception when the database is not actually corrupt but rather
|
|
|
|
// locked (i.e. it's a bad error message).
|
|
|
|
// The balance refresh is done first because it is coroutine-based and will fully
|
|
|
|
// complete by the time the function returns.
|
|
|
|
// Ultimately, refreshing the transactions just invalidates views of data that
|
|
|
|
// already exists and it completes on another thread so it should come after the
|
|
|
|
// balance refresh is complete.
|
|
|
|
twigTask("Triggering balance refresh since the processor is synced!") {
|
|
|
|
refreshBalance()
|
|
|
|
}
|
|
|
|
twigTask("Triggering pending transaction refresh!") {
|
|
|
|
refreshPendingTransactions()
|
|
|
|
}
|
|
|
|
twigTask("Triggering transaction refresh since the processor is synced!") {
|
|
|
|
refreshTransactions()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private suspend fun refreshPendingTransactions() {
|
|
|
|
// TODO: this would be the place to clear out any stale pending transactions. Remove filter
|
|
|
|
// logic and then delete any pending transaction with sufficient confirmations (all in one
|
|
|
|
// db transaction).
|
2019-11-23 15:07:28 -08:00
|
|
|
manager.getAll().first().filter { it.isSubmitSuccess() && !it.isMined() }
|
|
|
|
.forEach { pendingTx ->
|
|
|
|
twig("checking for updates on pendingTx id: ${pendingTx.id}")
|
|
|
|
pendingTx.rawTransactionId?.let { rawId ->
|
|
|
|
ledger.findMinedHeight(rawId)?.let { minedHeight ->
|
|
|
|
twig(
|
|
|
|
"found matching transaction for pending transaction with id" +
|
|
|
|
" ${pendingTx.id} mined at height ${minedHeight}!"
|
|
|
|
)
|
|
|
|
manager.applyMinedHeight(pendingTx, minedHeight)
|
|
|
|
}
|
2019-11-22 23:18:20 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-10 11:12:32 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// Send / Receive
|
|
|
|
//
|
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
override suspend fun cancelSpend(transaction: PendingTransaction) = manager.cancel(transaction)
|
2019-07-14 15:13:12 -07:00
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
override suspend fun getAddress(accountId: Int): String = processor.getAddress(accountId)
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
override fun sendToAddress(
|
|
|
|
spendingKey: String,
|
2019-07-10 11:12:32 -07:00
|
|
|
zatoshi: Long,
|
|
|
|
toAddress: String,
|
|
|
|
memo: String,
|
2019-11-01 13:25:28 -07:00
|
|
|
fromAccountIndex: Int
|
2019-11-22 23:18:20 -08:00
|
|
|
): Flow<PendingTransaction> = flow {
|
|
|
|
twig("Initializing pending transaction")
|
|
|
|
// Emit the placeholder transaction, then switch to monitoring the database
|
|
|
|
manager.initSpend(zatoshi, toAddress, memo, fromAccountIndex).let { placeHolderTx ->
|
|
|
|
emit(placeHolderTx)
|
|
|
|
manager.encode(spendingKey, placeHolderTx).let { encodedTx ->
|
2019-11-23 15:07:28 -08:00
|
|
|
if (!encodedTx.isFailedEncoding() && !encodedTx.isCancelled()) {
|
|
|
|
manager.submit(encodedTx)
|
|
|
|
}
|
2019-11-22 23:18:20 -08:00
|
|
|
}
|
2019-11-01 13:25:28 -07:00
|
|
|
}
|
2019-11-22 23:18:20 -08:00
|
|
|
}.flatMapLatest {
|
2019-11-23 15:07:28 -08:00
|
|
|
twig("Monitoring pending transaction (id: ${it.id}) for updates...")
|
2019-11-22 23:18:20 -08:00
|
|
|
manager.monitorById(it.id)
|
2019-11-23 15:07:28 -08:00
|
|
|
}.distinctUntilChanged()
|
2019-11-01 13:25:28 -07:00
|
|
|
}
|
|
|
|
|
2019-11-23 17:47:50 -08:00
|
|
|
/**
|
|
|
|
* Simplest constructor possible. Useful for demos, sample apps or PoC's. Anything more complex
|
|
|
|
* will probably want to handle initialization, directly.
|
|
|
|
*/
|
|
|
|
fun Synchronizer(
|
|
|
|
appContext: Context,
|
|
|
|
lightwalletdHost: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
|
2019-12-18 20:28:06 -08:00
|
|
|
lightwalletdPort: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
|
2019-11-23 17:47:50 -08:00
|
|
|
seed: ByteArray? = null,
|
2020-01-06 21:37:09 -08:00
|
|
|
birthdayStore: Initializer.WalletBirthdayStore = Initializer.DefaultBirthdayStore(appContext)
|
2019-11-23 17:47:50 -08:00
|
|
|
): Synchronizer {
|
2019-12-23 11:50:52 -08:00
|
|
|
val initializer = Initializer(appContext, lightwalletdHost, lightwalletdPort)
|
2020-01-06 21:37:09 -08:00
|
|
|
if (seed != null && birthdayStore.hasExistingBirthday()) {
|
2019-11-26 12:46:31 -08:00
|
|
|
twig("Initializing existing wallet")
|
2020-01-06 21:37:09 -08:00
|
|
|
initializer.open(birthdayStore.getBirthday())
|
2019-11-26 12:46:31 -08:00
|
|
|
twig("${initializer.rustBackend.dbDataPath}")
|
2019-11-23 17:47:50 -08:00
|
|
|
} else {
|
2020-01-06 21:37:09 -08:00
|
|
|
require(seed != null) {
|
2019-11-23 17:47:50 -08:00
|
|
|
"Failed to initialize. A seed is required when no wallet exists on the device."
|
2020-01-06 21:37:09 -08:00
|
|
|
}
|
|
|
|
if (birthdayStore.hasImportedBirthday()) {
|
2019-11-26 12:46:31 -08:00
|
|
|
twig("Initializing new wallet")
|
2020-01-06 21:37:09 -08:00
|
|
|
initializer.new(seed, birthdayStore.newWalletBirthday, overwrite = true)
|
2019-11-23 17:47:50 -08:00
|
|
|
} else {
|
2019-11-26 12:46:31 -08:00
|
|
|
twig("Initializing imported wallet")
|
2020-01-06 21:37:09 -08:00
|
|
|
initializer.import(seed, birthdayStore.getBirthday(), overwrite = true)
|
2019-11-23 17:47:50 -08:00
|
|
|
}
|
|
|
|
}
|
2019-12-23 11:50:52 -08:00
|
|
|
return Synchronizer(appContext, initializer)
|
2019-11-23 17:47:50 -08:00
|
|
|
}
|
2019-07-10 11:12:32 -07:00
|
|
|
|
2019-12-18 20:28:06 -08:00
|
|
|
fun Synchronizer(
|
|
|
|
appContext: Context,
|
|
|
|
initializer: Initializer
|
2020-01-06 21:37:09 -08:00
|
|
|
): Synchronizer {
|
|
|
|
check(initializer.isInitialized) {
|
|
|
|
"Error: RustBackend must be loaded before creating the Synchronizer. Verify that either" +
|
|
|
|
" the 'open', 'new' or 'import' function has been called on the Initializer."
|
|
|
|
}
|
|
|
|
return Synchronizer(appContext, initializer.rustBackend, initializer.host, initializer.port)
|
|
|
|
}
|
2019-12-18 20:28:06 -08:00
|
|
|
|
2019-11-01 13:25:28 -07:00
|
|
|
/**
|
|
|
|
* Constructor function for building a Synchronizer in the most flexible way possible. This allows
|
|
|
|
* a wallet maker to customize any subcomponent of the Synchronzier.
|
|
|
|
*/
|
|
|
|
@Suppress("FunctionName")
|
|
|
|
fun Synchronizer(
|
|
|
|
appContext: Context,
|
|
|
|
rustBackend: RustBackend,
|
2019-12-18 20:28:06 -08:00
|
|
|
lightwalletdHost: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
|
|
|
|
lightwalletdPort: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
|
2019-11-22 23:18:20 -08:00
|
|
|
ledger: TransactionRepository =
|
|
|
|
PagedTransactionRepository(appContext, 10, rustBackend.dbDataPath),
|
2019-11-01 13:25:28 -07:00
|
|
|
blockStore: CompactBlockStore = CompactBlockDbStore(appContext, rustBackend.dbCachePath),
|
2019-12-18 20:28:06 -08:00
|
|
|
service: LightWalletService = LightWalletGrpcService(appContext, lightwalletdHost, lightwalletdPort),
|
2019-11-01 13:25:28 -07:00
|
|
|
encoder: TransactionEncoder = WalletTransactionEncoder(rustBackend, ledger),
|
|
|
|
downloader: CompactBlockDownloader = CompactBlockDownloader(service, blockStore),
|
2019-11-22 23:18:20 -08:00
|
|
|
manager: OutboundTransactionManager =
|
|
|
|
PersistentTransactionManager(appContext, encoder, service),
|
|
|
|
processor: CompactBlockProcessor =
|
|
|
|
CompactBlockProcessor(downloader, ledger, rustBackend, rustBackend.birthdayHeight)
|
2019-11-01 13:25:28 -07:00
|
|
|
): Synchronizer {
|
|
|
|
// ties everything together
|
|
|
|
return SdkSynchronizer(
|
|
|
|
ledger,
|
|
|
|
manager,
|
2020-01-06 22:26:10 -08:00
|
|
|
processor
|
2019-11-01 13:25:28 -07:00
|
|
|
)
|
2019-07-10 11:12:32 -07:00
|
|
|
}
|