2019-10-23 22:21:52 -07:00
package cash.z.wallet.sdk
2019-07-10 11:12:32 -07:00
2020-06-09 19:49:14 -07:00
import cash.z.wallet.sdk.validate.AddressType
import cash.z.wallet.sdk.validate.AddressType.Shielded
import cash.z.wallet.sdk.validate.AddressType.Transparent
import cash.z.wallet.sdk.validate.ConsensusMatchType
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
2020-06-09 19:31:00 -07:00
import cash.z.wallet.sdk.db.entity.*
2019-08-30 10:05:02 -07:00
import cash.z.wallet.sdk.exception.SynchronizerException
2020-06-09 19:05:30 -07:00
import cash.z.wallet.sdk.ext.*
import cash.z.wallet.sdk.rpc.Service
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.*
2020-06-09 19:05:30 -07:00
import io.grpc.ManagedChannel
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
2020-06-09 19:05:30 -07:00
import kotlin.coroutines.EmptyCoroutineContext
2019-07-10 11:12:32 -07:00
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
*
2020-06-09 18:35:40 -07:00
* @property storage exposes flows of wallet transaction information .
* @property txManager manages and tracks outbound transactions .
2020-02-27 00:25:07 -08:00
* @property 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 (
2020-06-09 18:35:40 -07:00
private val storage : TransactionRepository ,
private val txManager : 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
2020-06-09 19:05:30 -07:00
* parentScope stops . This coordinates with [ isStarted ] so that it fails early
2019-11-01 13:25:28 -07:00
* rather than silently , whenever the scope is used before the Synchronizer has been started .
2019-07-12 01:47:17 -07:00
* /
2020-06-09 19:05:30 -07:00
var coroutineScope : CoroutineScope = CoroutineScope ( EmptyCoroutineContext )
get ( ) {
if ( !is Started ) {
throw SynchronizerException . NotYetStarted
} else {
return field
}
}
set ( value ) {
field = value
if ( value . coroutineContext !is EmptyCoroutineContext ) isStarted = true
}
/ * *
* The channel that this Synchronizer uses to communicate with lightwalletd . In most cases , this
* should not be needed or used . Instead , APIs should be added to the synchronizer to
* enable the desired behavior . In the rare case , such as testing , it can be helpful to share
* the underlying channel to connect to the same service , and use other APIs
* ( such as darksidewalletd ) because channels are heavyweight .
* /
val channel : ManagedChannel get ( ) = ( processor . downloader . lightwalletService as LightWalletGrpcService ) . channel
var isStarted = false
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 ( )
2020-06-09 18:35:40 -07:00
override val clearedTransactions = storage . allTransactions
override val pendingTransactions = txManager . getAll ( )
override val sentTransactions = storage . sentTransactions
override val receivedTransactions = storage . 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
2020-01-14 09:52:41 -08:00
* inaccurate and outbound transactions should be prevented until this sync is complete . It is
* a simplified version of [ processorInfo ] .
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
2020-01-14 09:52:41 -08:00
/ * *
* Indicates the latest information about the blocks that have been processed by the SDK . This
* is very helpful for conveying detailed progress and status to the user .
* /
override val processorInfo : Flow < CompactBlockProcessor . ProcessorInfo > = processor . processorInfo
2019-07-12 01:47:17 -07:00
2020-06-09 19:05:30 -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
2020-02-21 15:14:34 -08:00
/ * *
* 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 ) .
* /
2020-06-09 19:05:30 -07:00
override var onChainErrorHandler : ( ( errorHeight : Int , rewindHeight : Int ) -> Any ) ? = null
2020-02-21 15:14:34 -08:00
2019-07-12 01:47:17 -07:00
2019-11-01 13:25:28 -07:00
//
// Public API
//
2020-06-09 19:05:30 -07:00
/ * *
* Convenience function for the latest balance . Instead of using this , a wallet will more likely
* want to consume the flow of balances using [ balances ] .
* /
override val latestBalance : WalletBalance get ( ) = _balances . value
/ * *
* Convenience function for the latest height . Specifically , this value represents the last
* height that the synchronizer has observed from the lightwalletd server . Instead of using
* this , a wallet will more likely want to consume the flow of processor info using
* [ processorInfo ] .
* /
override val latestHeight : Int get ( ) = processor . currentInfo . networkBlockHeight
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
2020-03-26 04:00:04 -07:00
* scope is only used for launching this synchronizer ' s job as a child . If no scope is provided ,
2019-11-26 12:46:31 -08:00
* 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 .
2020-02-27 00:25:07 -08:00
*
* @return an instance of this class so that this function can be used fluidly .
2019-08-30 10:05:02 -07:00
* /
2019-11-26 12:46:31 -08:00
override fun start ( parentScope : CoroutineScope ? ) : Synchronizer {
2020-06-09 19:05:30 -07:00
if ( isStarted ) 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 ) )
2020-06-09 19:05:30 -07:00
CoroutineScope ( supervisorJob + Dispatchers . Main ) . let { scope ->
coroutineScope = scope
scope . 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
}
2020-06-09 19:05:30 -07:00
/ * *
* Convenience function that exposes the underlying server information , like its name and
* consensus branch id . Most wallets should already have a different source of truth for the
* server ( s ) with which they operate .
* /
override suspend fun getServerInfo ( ) : Service . LightdInfo = processor . downloader . getServerInfo ( )
//
// Storage APIs
//
// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data
fun findBlockHash ( height : Int ) : ByteArray ? {
return ( storage as ? PagedTransactionRepository ) ?. findBlockHash ( height )
}
fun findBlockHashAsHex ( height : Int ) : String ? {
return findBlockHash ( height ) ?. toHexReversed ( )
}
fun getTransactionCount ( ) : Int {
return ( storage as ? PagedTransactionRepository ) ?. getTransactionCount ( ) ?: 0
}
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 ( ) {
2020-06-09 18:35:40 -07:00
storage . invalidate ( )
2019-07-10 11:12:32 -07:00
}
2020-02-27 00:25:07 -08:00
/ * *
* Calculate the latest balance , based on the blocks that have been scanned and transmit this
* information into the flow of [ balances ] .
* /
2019-12-23 11:50:52 -08:00
suspend fun refreshBalance ( ) {
2020-02-11 16:56:31 -08:00
twig ( " refreshing balance " )
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 ) ) {
2020-02-11 16:56:31 -08:00
twig ( " Synchronizer ( ${this@SdkSynchronizer} ) Ready. Starting processor! " )
2020-02-21 15:14:34 -08:00
processor . onProcessorErrorListener = :: onProcessorError
processor . onChainErrorListener = :: onChainError
2019-11-22 23:18:20 -08:00
processor . state . onEach {
when ( it ) {
is Scanned -> {
2020-01-15 04:10:22 -08:00
// do a bit of housekeeping and then report synced status
2019-11-22 23:18:20 -08:00
onScanComplete ( it . scannedRange )
SYNCED
}
is Stopped -> STOPPED
is Disconnected -> DISCONNECTED
2020-01-15 04:10:22 -08:00
is Downloading , Initialized -> DOWNLOADING
is Validating -> VALIDATING
is Scanning -> SCANNING
2020-03-25 14:58:08 -07:00
is Enhancing -> ENHANCING
2019-11-22 23:18:20 -08:00
} . let { synchronizerStatus ->
2020-03-25 14:58:08 -07:00
// ignore enhancing status for now
// TODO: clean this up and handle enhancing gracefully
if ( synchronizerStatus != ENHANCING ) _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
}
2020-02-21 15:14:34 -08:00
private fun onChainError ( errorHeight : Int , rewindHeight : Int ) {
twig ( " Chain error detected at height: $errorHeight . Rewinding to: $rewindHeight " )
if ( onChainErrorHandler == null ) {
twig (
" WARNING: a chain error occurred but no callback is registered to be notified of " +
" chain errors. To respond to these errors (perhaps to update the UI or alert the " +
" user) set synchronizer.onChainErrorHandler to a non-null value "
)
}
onChainErrorHandler ?. invoke ( errorHeight , rewindHeight )
}
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).
2020-06-09 18:35:40 -07:00
txManager . getAll ( ) . first ( ) . filter { it . isSubmitSuccess ( ) && ! it . isMined ( ) }
2019-11-23 15:07:28 -08:00
. forEach { pendingTx ->
twig ( " checking for updates on pendingTx id: ${pendingTx.id} " )
pendingTx . rawTransactionId ?. let { rawId ->
2020-06-09 18:35:40 -07:00
storage . findMinedHeight ( rawId ) ?. let { minedHeight ->
2019-11-23 15:07:28 -08:00
twig (
" found matching transaction for pending transaction with id " +
" ${pendingTx.id} mined at height ${minedHeight} ! "
)
2020-06-09 18:35:40 -07:00
txManager . applyMinedHeight ( pendingTx , minedHeight )
2019-11-23 15:07:28 -08:00
}
2019-11-22 23:18:20 -08:00
}
}
}
2019-07-10 11:12:32 -07:00
//
// Send / Receive
//
2020-06-09 18:35:40 -07:00
override suspend fun cancelSpend ( transaction : PendingTransaction ) = txManager . 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
2020-06-09 18:35:40 -07:00
txManager . initSpend ( zatoshi , toAddress , memo , fromAccountIndex ) . let { placeHolderTx ->
2019-11-22 23:18:20 -08:00
emit ( placeHolderTx )
2020-06-09 18:35:40 -07:00
txManager . encode ( spendingKey , placeHolderTx ) . let { encodedTx ->
2019-11-23 15:07:28 -08:00
if ( ! encodedTx . isFailedEncoding ( ) && ! encodedTx . isCancelled ( ) ) {
2020-06-09 18:35:40 -07:00
txManager . submit ( encodedTx )
2019-11-23 15:07:28 -08:00
}
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... " )
2020-06-09 18:35:40 -07:00
txManager . monitorById ( it . id )
2019-11-23 15:07:28 -08:00
} . distinctUntilChanged ( )
2020-01-08 00:57:42 -08:00
2020-06-09 18:35:40 -07:00
override suspend fun isValidShieldedAddr ( address : String ) = txManager . isValidShieldedAddress ( address )
2020-01-08 00:57:42 -08:00
override suspend fun isValidTransparentAddr ( address : String ) =
2020-06-09 18:35:40 -07:00
txManager . isValidTransparentAddress ( address )
2020-01-08 00:57:42 -08:00
2020-06-09 18:35:40 -07:00
override suspend fun validateAddress ( address : String ) : AddressType {
2020-01-08 00:57:42 -08:00
return try {
2020-01-14 09:57:39 -08:00
if ( isValidShieldedAddr ( address ) ) Shielded else Transparent
2020-01-08 00:57:42 -08:00
} catch ( zError : Throwable ) {
var message = zError . message
try {
2020-01-14 09:57:39 -08:00
if ( isValidTransparentAddr ( address ) ) Transparent else Shielded
2020-01-08 00:57:42 -08:00
} catch ( tError : Throwable ) {
2020-06-09 18:35:40 -07:00
AddressType . Invalid (
2020-01-08 00:57:42 -08:00
if ( message != tError . message ) " $message and ${tError.message} " else ( message
?: " Invalid " )
)
}
}
}
2019-11-01 13:25:28 -07:00
2020-06-09 18:35:40 -07:00
override suspend fun validateConsensusBranch ( ) : ConsensusMatchType {
val serverBranchId = tryNull { processor . downloader . getServerInfo ( ) . consensusBranchId }
val sdkBranchId = tryNull {
( txManager as PersistentTransactionManager ) . encoder . getConsensusBranchId ( )
2019-11-23 17:47:50 -08:00
}
2020-06-09 18:35:40 -07:00
return ConsensusMatchType (
sdkBranchId ?. let { ConsensusBranchId . fromId ( it ) } ,
serverBranchId ?. let { ConsensusBranchId . fromHex ( it ) }
)
2019-11-23 17:47:50 -08:00
}
}
2019-07-10 11:12:32 -07:00
2020-02-27 00:25:07 -08:00
/ * *
2020-06-09 18:35:40 -07:00
* Builder function for constructing a Synchronizer with flexibility for adding custom behavior . The
* Initializer is the only thing required because it takes care of loading the Rust libraries
* properly ; everything else has a reasonable default . For a wallet , the most common flow is to
* first 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 .
2020-02-27 00:25:07 -08:00
*
2020-06-09 18:35:40 -07:00
* The remaining parameters are all optional and they allow a wallet maker to customize any
* subcomponent of the Synchronizer . For example , this function could be used to inject an in - memory
* CompactBlockStore rather than a SQL implementation or a downloader that does not use gRPC :
*
* ` ` `
* val initializer = Initializer ( context , host , port ) . import ( seedPhrase , birthdayHeight )
* val synchronizer = Synchronizer ( initializer ,
* blockStore = MyInMemoryBlockStore ( ) ,
* downloader = MyRestfulServiceForBlocks ( )
* )
* ` ` `
2020-02-27 00:25:07 -08:00
*
2020-06-09 18:35:40 -07:00
* Note : alternatively , all the objects required to build a Synchronizer ( the object graph ) can be
* supplied by a dependency injection framework like Dagger or Koin . This builder just makes that
* process a bit easier so developers can get started syncing the blockchain without the overhead of
* configuring a bunch of objects , first .
*
* @param initializer the helper that is leveraged for creating all the components that the
* Synchronizer requires . It contains all information necessary to build a synchronizer and it is
* mainly responsible for initializing the databases associated with this synchronizer and loading
* the rust backend .
* @param repository repository of wallet data , providing an interface to the underlying info .
2020-02-27 00:25:07 -08:00
* @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 .
2020-06-09 18:35:40 -07:00
* @param txManager the component that manages outbound transactions in order to report which ones are
2020-02-27 00:25:07 -08:00
* still pending , particularly after failed attempts or dropped connectivity . The intent is to help
2020-03-26 04:00:04 -07:00
* monitor outbound transactions status through to completion .
2020-02-27 00:25:07 -08:00
* @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 .
2019-11-01 13:25:28 -07:00
* /
@Suppress ( " FunctionName " )
fun Synchronizer (
2020-06-09 18:35:40 -07:00
initializer : Initializer ,
repository : TransactionRepository =
PagedTransactionRepository ( initializer . context , 1000 , initializer . rustBackend . pathDataDb ) , // TODO: fix this pagesize bug, small pages should not crash the app. It crashes with: Uncaught Exception: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. and is probably related to FlowPagedList
blockStore : CompactBlockStore = CompactBlockDbStore ( initializer . context , initializer . rustBackend . pathCacheDb ) ,
service : LightWalletService = LightWalletGrpcService ( initializer . context , initializer . host , initializer . port ) ,
encoder : TransactionEncoder = WalletTransactionEncoder ( initializer . rustBackend , repository ) ,
2019-11-01 13:25:28 -07:00
downloader : CompactBlockDownloader = CompactBlockDownloader ( service , blockStore ) ,
2020-06-09 18:35:40 -07:00
txManager : OutboundTransactionManager =
PersistentTransactionManager ( initializer . context , encoder , service ) ,
2019-11-22 23:18:20 -08:00
processor : CompactBlockProcessor =
2020-06-09 18:35:40 -07:00
CompactBlockProcessor ( downloader , repository , initializer . rustBackend , initializer . rustBackend . birthdayHeight )
2019-11-01 13:25:28 -07:00
) : Synchronizer {
2020-06-09 18:35:40 -07:00
// call the actual constructor now that all dependencies have been injected
// alternatively, this entire object graph can be supplied by Dagger
// This builder just makes that easier.
2019-11-01 13:25:28 -07:00
return SdkSynchronizer (
2020-06-09 18:35:40 -07:00
repository ,
txManager ,
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
}