checkpoint: things are back functional after some heavy re-writing

This commit is contained in:
Kevin Gorham 2019-06-16 00:53:48 -04:00
parent 2e6531c860
commit 25945a5ef9
No known key found for this signature in database
GPG Key ID: ABA38E928749A19E
22 changed files with 403 additions and 229 deletions

View File

@ -6,15 +6,16 @@ import cash.z.android.wallet.BuildConfig
import cash.z.android.wallet.ChipBucket
import cash.z.android.wallet.InMemoryChipBucket
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.data.StaticTransactionRepository
import cash.z.android.wallet.data.*
import cash.z.android.wallet.extention.toDbPath
import cash.z.android.wallet.sample.*
import cash.z.android.wallet.sample.CarolWallet
import cash.z.android.wallet.sample.SampleProperties.COMPACT_BLOCK_PORT
import cash.z.android.wallet.sample.SampleProperties.DEFAULT_BLOCK_POLL_FREQUENCY_MILLIS
import cash.z.android.wallet.sample.SampleProperties.DEFAULT_SERVER
import cash.z.android.wallet.sample.SampleProperties.DEFAULT_TRANSACTION_POLL_FREQUENCY_MILLIS
import cash.z.android.wallet.sample.SampleProperties.PREFS_SERVER_NAME
import cash.z.android.wallet.sample.SampleProperties.PREFS_WALLET_DISPLAY_NAME
import cash.z.android.wallet.sample.Servers
import cash.z.android.wallet.sample.WalletConfig
import cash.z.android.wallet.ui.util.Broom
import cash.z.wallet.sdk.block.*
import cash.z.wallet.sdk.data.*
@ -28,6 +29,7 @@ import cash.z.wallet.sdk.service.LightWalletGrpcService
import cash.z.wallet.sdk.service.LightWalletService
import dagger.Module
import dagger.Provides
import dagger.android.DispatchingAndroidInjector
import javax.inject.Named
import javax.inject.Singleton
@ -168,30 +170,30 @@ internal object SynchronizerModule {
fun provideManager(wallet: Wallet, repository: TransactionRepository, service: LightWalletService): ActiveTransactionManager {
return ActiveTransactionManager(repository, service, wallet)
}
@JvmStatic
@Provides
@Singleton
fun provideSynchronizer(
processor: CompactBlockProcessor,
repository: TransactionRepository,
manager: ActiveTransactionManager,
wallet: Wallet
): Synchronizer {
return SdkSynchronizer(processor, repository, manager, wallet, DEFAULT_STALE_TOLERANCE)
}
//
// @JvmStatic
// @Provides
// @Singleton
// fun provideSynchronizer(
// processor: CompactBlockProcessor,
// repository: TransactionRepository,
// manager: ActiveTransactionManager,
// wallet: Wallet
// ): Synchronizer {
// return SdkSynchronizer(processor, repository, manager, wallet, DEFAULT_STALE_TOLERANCE)
// }
@JvmStatic
@Provides
@Singleton
fun provideBroom(
service: LightWalletService,
sender: TransactionSender,
wallet: Wallet,
rustBackend: RustBackendWelding,
walletConfig: WalletConfig
): Broom {
return Broom(
service,
sender,
rustBackend,
walletConfig.cacheDbName,
wallet
@ -204,4 +206,33 @@ internal object SynchronizerModule {
fun provideChipBucket(): ChipBucket {
return InMemoryChipBucket()
}
@JvmStatic
@Provides
@Singleton
fun provideTransactionManager(): TransactionManager {
return PersistentTransactionManager()
}
@JvmStatic
@Provides
@Singleton
fun provideTransactionSender(manager: TransactionManager, service: LightWalletService): TransactionSender {
return PersistentTransactionMonitor(manager, service)
}
@JvmStatic
@Provides
@Singleton
fun provideTransactionEncoder(wallet: Wallet, repository: TransactionRepository): RawTransactionEncoder {
return WalletTransactionEncoder(wallet, repository)
}
@JvmStatic
@Provides
@Singleton
fun provideDataSynchronizer(wallet: Wallet, encoder: RawTransactionEncoder, sender: TransactionSender) : DataSyncronizer {
return StableSynchronizer(wallet, encoder, sender)
}
}

View File

@ -0,0 +1,35 @@
package cash.z.android.wallet.data
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.service.LightWalletService
class PersistentTransactionManager: TransactionManager {
init {
}
override suspend fun manageCreation(encoder: RawTransactionEncoder, value: Long, toAddress: String, memo: String) {
twig("managing the creation of a transaction")
encoder.create(value, toAddress, memo)
}
override suspend fun manageSubmission(service: LightWalletService, rawTransaction: ByteArray) {
try {
// TODO: stuff before you send
twig("managing the preparation to submit transaction")
val response = service.submitTransaction(rawTransaction)
twig("management of submit transaction completed with response: ${response.errorCode}: ${response.errorMessage}")
// TODO: stuff after sending
if (response.errorCode < 0) {
} else {
}
} catch (t: Throwable) {
twig("error while managing submitting transaction: ${t.message}")
}
}
override suspend fun getAllPending(): List<ByteArray> {
return listOf()
}
}

View File

@ -1,51 +1,31 @@
package cash.z.android.wallet.data
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.service.LightWalletService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.launch
class PersistentTransactionSender(
private val manager: TransactionManager,
private val factory: RawTransactionFactory
) : TransactionSender {
/**
* Generates newly persisted information about a transaction so that other processes can send.
*/
override fun sendToAddress(zatoshi: Long, toAddress: String, memo: String, fromAccountId: Int) {
val txId = manager.new() // creates a new transaction with a lifecycle of CREATING
try {
val rawTransaction = factory.create(zatoshi, toAddress, memo)
manager.setRawTransaction(txId, rawTransaction) // returns new transaction with lifecycle of CREATED
} catch (t: Throwable) {
manager.setCreationError(txId, t.message.toTxError())
return
}
}
}
class PersistentTransactionMonitor (
private val scope: CoroutineScope,
private val manager: TransactionManager,
private val service: LightWalletService
) {
private val channel: SendChannel<TransactionUpdateRequest>
) : TransactionSender {
private lateinit var channel: SendChannel<TransactionUpdateRequest>
private var monitoringJob: Job? = null
private val initialMonitorDelay = 45_000L
init {
channel = scope.startActor()
}
fun update() = scope.launch {
channel.send(SubmitPending)
fun CoroutineScope.requestUpdate() = launch {
if (!channel.isClosedForSend) {
channel.send(SubmitPending)
}
}
/**
* Start an actor that listens for signals about what to do with transactions. This actor's lifespan is within the
* provided [scope] and it will live until the scope is cancelled.
*/
fun CoroutineScope.startActor() = actor<TransactionUpdateRequest> {
private fun CoroutineScope.startActor() = actor<TransactionUpdateRequest> {
var pendingTransactionDao = 0 // actor state:
for (msg in channel) { // iterate over incoming messages
when (msg) {
@ -54,27 +34,59 @@ class PersistentTransactionMonitor (
}
}
private fun submitPendingTransactions() {
val transactions = manager.getAllPendingRawTransactions().forEach { (txId, rawTransaction) ->
submitPendingTransaction(txId, rawTransaction)
private fun CoroutineScope.startMonitor() = launch {
while (!channel.isClosedForSend && isActive) {
delay(calculateDelay())
requestUpdate()
}
twig("TransactionMonitor stopping!")
}
private fun submitPendingTransaction(txId: Long, rawTransaction: ByteArray) {
try {
manager.setSubmissionStarted(txId)
val response = service.submitTransaction(rawTransaction)
if (response.errorCode < 0) {
manager.setSubmissionComplete(txId, false, response.errorMessage.toTxError())
} else {
manager.setSubmissionComplete(txId, true)
private fun calculateDelay(): Long {
return initialMonitorDelay
}
override fun start(scope: CoroutineScope) {
twig("TransactionMonitor starting!")
channel = scope.startActor()
monitoringJob?.cancel()
monitoringJob = scope.startMonitor()
}
override fun stop() {
channel.close()
monitoringJob?.cancel()?.also { monitoringJob = null }
}
/**
* Generates newly persisted information about a transaction so that other processes can send.
*/
override suspend fun sendToAddress(
encoder: RawTransactionEncoder,
zatoshi: Long,
toAddress: String,
memo: String,
fromAccountId: Int
) = withContext(IO) {
manager.manageCreation(encoder, zatoshi, toAddress, memo)
requestUpdate()
Unit
}
/**
* Submit all pending transactions that have not expired.
*/
private suspend fun submitPendingTransactions() = withContext(IO) {
twig("received request to submit pending transactions")
with(manager) {
getAllPending().also { twig("found ${it.size} pending txs to submit") }.forEach { rawTx ->
manageSubmission(service, rawTx)
}
} catch (t: Throwable) {
manager.setSubmissionComplete(txId, false, t.message.toTxError())
}
}
}
sealed class TransactionUpdateRequest
object SubmitPending : TransactionUpdateRequest()
@ -97,4 +109,38 @@ SUBMITTED
INVALID
** attempting submission
** attempted submission
bookkeeper, register, treasurer, mint, ledger
private fun checkTx(transactionId: Long) {
if (transactionId < 0) {
throw SweepException.Creation
} else {
twig("successfully created transaction!")
}
}
private fun checkRawTx(transactionRaw: ByteArray?) {
if (transactionRaw == null) {
throw SweepException.Disappeared
} else {
twig("found raw transaction in the dataDb")
}
}
private fun checkResponse(response: Service.SendResponse) {
if (response.errorCode < 0) {
throw SweepException.IncompletePass(response)
} else {
twig("successfully submitted. error code: ${response.errorCode}")
}
}
sealed class SweepException(val errorMessage: String) : RuntimeException(errorMessage) {
object Creation : SweepException("failed to create raw transaction")
object Disappeared : SweepException("unable to find a matching raw transaction. This means the rust backend said it created a TX but when we looked for it in the DB it was missing!")
class IncompletePass(response: Service.SendResponse) : SweepException("submit failed with error code: ${response.errorCode} and message ${response.errorMessage}")
}
*/

View File

@ -0,0 +1,8 @@
package cash.z.android.wallet.data
interface RawTransactionEncoder {
/**
* Creates a raw transaction that is unsigned.
*/
suspend fun create(zatoshi: Long, toAddress: String, memo: String = ""): ByteArray
}

View File

@ -1,8 +0,0 @@
package cash.z.android.wallet.data
interface RawTransactionFactory {
/**
* Creates a raw transaction that is unsigned.
*/
fun create(value: Long, toAddress: String, memo: String = ""): ByteArray
}

View File

@ -1,30 +1,71 @@
package cash.z.android.wallet.data
import androidx.lifecycle.LifecycleOwner
import cash.z.android.wallet.ui.util.BaseLifecycleObserver
import cash.z.wallet.sdk.data.ActiveTransaction
import cash.z.wallet.sdk.data.TransactionState
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.secure.Wallet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
* A synchronizer that attempts to remain operational, despite any number of errors that can occur.
*
* @param lifecycleOwner the lifecycle to observe while synchronizing
*/
class StableSynchronizer(
private val lifecycleOwner: LifecycleOwner,
sender: TransactionSender
) : BaseLifecycleObserver(), TransactionSender by sender {
init {
lifecycleOwner.lifecycle.addObserver(this)
@ExperimentalCoroutinesApi
class StableSynchronizer @Inject constructor(
private val wallet: Wallet,
private val encoder: RawTransactionEncoder,
private val sender: TransactionSender
) : DataSyncronizer {
private val balanceChannel = ConflatedBroadcastChannel<Wallet.WalletBalance>()
private val progressChannel = ConflatedBroadcastChannel<Int>()
private val pendingChannel = ConflatedBroadcastChannel<List<PendingTransaction>>()
override fun start(scope: CoroutineScope) {
twig("Staring sender!")
sender.start(scope)
}
//
// Lifecycle
//
override fun onCreate(owner: LifecycleOwner) {
override fun stop() {
sender.stop()
}
override fun onDestroy(owner: LifecycleOwner) {
//
// Channels
//
override fun balances(): ReceiveChannel<Wallet.WalletBalance> {
return balanceChannel.openSubscription()
}
override fun progress(): ReceiveChannel<Int> {
return progressChannel.openSubscription()
}
override fun pendingTransactions(): ReceiveChannel<List<PendingTransaction>> {
return pendingChannel.openSubscription()
}
//
// Send / Receive
//
override suspend fun getAddress(accountId: Int): String = withContext(IO) { wallet.getAddress() }
override suspend fun sendToAddress(
zatoshi: Long,
toAddress: String,
memo: String,
fromAccountId: Int
) = withContext(IO) {
sender.sendToAddress(encoder, zatoshi, toAddress, memo, fromAccountId)
}
@ -34,13 +75,22 @@ class StableSynchronizer(
// override fun transactions(): Flow<WalletTransaction> {
// }
//
// override fun balance(): Flow<Wallet.WalletBalance> {
// }
//
// override fun progress(): Flow<Int> {
// }
//
// override fun status(): Flow<FlowSynchronizer.SyncStatus> {
// }
}
interface DataSyncronizer {
fun start(scope: CoroutineScope)
fun stop()
suspend fun getAddress(accountId: Int = 0): String
suspend fun sendToAddress(zatoshi: Long, toAddress: String, memo: String = "", fromAccountId: Int = 0)
fun balances(): ReceiveChannel<Wallet.WalletBalance>
fun progress(): ReceiveChannel<Int>
fun pendingTransactions(): ReceiveChannel<List<PendingTransaction>>
}

View File

@ -1,37 +1,64 @@
package cash.z.android.wallet.data
import cash.z.wallet.sdk.service.LightWalletService
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
/**
* Manage transactions with the main purpose of reporting which ones are still pending, particularly after failed
* attempts or dropped connectivity. The intent is to help see transactions through to completion.
*/
interface TransactionManager {
/**
* Initialize a transaction and return its ID.
*
* @return the id of the transaction to use with subsequent calls to this manager instance.
*/
fun new(): Long
suspend fun manageCreation(encoder: RawTransactionEncoder, value: Long, toAddress: String, memo: String)
suspend fun manageSubmission(service: LightWalletService, rawTransaction: ByteArray)
suspend fun getAllPending(): List<ByteArray>
//
// /**
// * Initialize a transaction and return its ID.
// *
// * @return the id of the transaction to use with subsequent calls to this manager instance.
// */
// fun new(): Long
//
// /**
// * Set the rawTransaction data for the given transaction. Typically, this would transition the state of the
// * transaction to something like CREATED. Some implementations might derive the state, based on whether this raw
// * transaction data has been provided.
// *
// * @param txId the id of the transaction to update
// * @param rawTransaction the raw transaction data
// */
// fun setRawTransaction(txId: Long, rawTransaction: ByteArray)
//
// /**
// * Signal that there has been an error while attempting to create a transaction.
// *
// * @param txId the id of the transaction to update
// * @param error information about the error that occurred
// */
// fun setCreationError(txId: Long, error: TransactionError)
//
// fun setSubmissionStarted(txId: Long)
// fun setSubmissionComplete(txId: Long, isSuccess: Boolean, error: TransactionError? = null)
// fun getAllPendingRawTransactions(): Map<Long, ByteArray>
/**
* Set the rawTransaction data for the given transaction. Typically, this would transition the state of the
* transaction to something like CREATED. Some implementations might derive the state, based on whether this raw
* transaction data has been provided.
*
* @param txId the id of the transaction to update
* @param rawTransaction the raw transaction data
*/
fun setRawTransaction(txId: Long, rawTransaction: ByteArray)
/**
* Signal that there has been an error while attempting to create a transaction.
*
* @param txId the id of the transaction to update
* @param error information about the error that occurred
*/
fun setCreationError(txId: Long, error: TransactionError)
fun setSubmissionStarted(txId: Long)
fun setSubmissionComplete(txId: Long, isSuccess: Boolean, error: TransactionError? = null)
fun getAllPendingRawTransactions(): Map<Long, ByteArray>
}
interface TransactionError {
val message: String
}
}
data class PendingTransaction(
val id: Long = -1,
val isMined: Boolean = false,
val hasRaw: Boolean = false,
val submitCount: Int = 0,
val expiryHeight: Int = -1,
val expiryTime: Long = -1,
val errorMessage: String? = null
)
fun PendingTransaction.isFailure(): Boolean {
return errorMessage != null
}

View File

@ -1,5 +1,9 @@
package cash.z.android.wallet.data
import kotlinx.coroutines.CoroutineScope
interface TransactionSender {
fun sendToAddress(zatoshi: Long, toAddress: String, memo: String, fromAccountId: Int)
fun start(scope: CoroutineScope)
fun stop()
suspend fun sendToAddress(encoder: RawTransactionEncoder, zatoshi: Long, toAddress: String, memo: String = "", fromAccountId: Int = 0)
}

View File

@ -0,0 +1,16 @@
package cash.z.android.wallet.data
import cash.z.wallet.sdk.data.TransactionRepository
import cash.z.wallet.sdk.secure.Wallet
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
class WalletTransactionEncoder(
private val wallet: Wallet,
private val repository: TransactionRepository
) : RawTransactionEncoder {
override suspend fun create(zatoshi: Long, toAddress: String, memo: String): ByteArray = withContext(IO) {
val transactionId = wallet.createRawSendTransaction(zatoshi, toAddress, memo)
repository.findTransactionById(transactionId)?.raw!!
}
}

View File

@ -17,12 +17,13 @@ import androidx.navigation.Navigation
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import cash.z.android.wallet.*
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.data.StableSynchronizer
import cash.z.android.wallet.databinding.ActivityMainBinding
import cash.z.android.wallet.di.annotation.ActivityScope
import cash.z.android.wallet.extention.Toaster
import cash.z.android.wallet.extention.alert
import cash.z.android.wallet.extention.copyToClipboard
import cash.z.android.wallet.sample.WalletConfig
import cash.z.android.wallet.ui.fragment.ScanFragment
import cash.z.android.wallet.ui.presenter.BalancePresenter
import cash.z.android.wallet.ui.presenter.MainPresenter
@ -34,10 +35,11 @@ import cash.z.android.wallet.ui.util.Analytics.Tap.*
import cash.z.android.wallet.ui.util.Analytics.trackAction
import cash.z.android.wallet.ui.util.Analytics.trackFunnelStep
import cash.z.android.wallet.ui.util.Broom
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -46,14 +48,11 @@ import kotlin.random.Random
class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.BarcodeCallback, MainPresenter.MainView {
@Inject
lateinit var synchronizer: Synchronizer
lateinit var synchronizer: DataSyncronizer
@Inject
lateinit var mainPresenter: MainPresenter
@Inject
lateinit var walletConfig: WalletConfig
@Inject
lateinit var broom: Broom
@ -72,14 +71,13 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.lifecycle
chipBucket.restore()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.activity = this
initAppBar()
loadMessages = generateFunLoadMessages().shuffled()
synchronizer.start(this)
synchronizer.onSynchronizerErrorListener = ::onSynchronizerError
balancePresenter = BalancePresenter()
}
@ -92,7 +90,8 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
super.onResume()
chipBucket.restore()
launch {
balancePresenter.start(this, synchronizer)
synchronizer.start(this)
balancePresenter.start(this, synchronizer.balances())
mainPresenter.start()
}
}
@ -106,7 +105,6 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
override fun onDestroy() {
super.onDestroy()
synchronizer.stop()
Analytics.clear()
}
@ -421,7 +419,7 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
}
override fun orderUpdated(processing: MainPresenter.PurchaseResult.Processing) {
Toaster.short(processing.state.toString())
Toaster.short(processing.pendingTransaction.toString())
}
fun onSynchronizerError(error: Throwable?): Boolean {
@ -454,7 +452,9 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
fun copyAddress(view: View) {
trackAction(TAPPED_COPY_ADDRESS)
Toaster.short("Address copied!")
copyToClipboard(synchronizer.getAddress())
launch {
copyToClipboard(synchronizer.getAddress())
}
}

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import androidx.annotation.IdRes
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.ui.presenter.ProgressPresenter
import cash.z.wallet.sdk.data.Synchronizer
import kotlinx.coroutines.launch
@ -15,7 +16,7 @@ abstract class ProgressFragment(
ProgressPresenter.ProgressView {
@Inject
protected lateinit var synchronizer: Synchronizer
protected lateinit var synchronizer: DataSyncronizer
protected lateinit var progressPresenter: ProgressPresenter
private lateinit var progressBar: ProgressBar

View File

@ -10,12 +10,14 @@ import android.view.ViewGroup
import android.widget.TextView
import cash.z.android.qrecycler.QRecycler
import cash.z.android.wallet.R
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.util.AddressPartNumberSpan
import cash.z.wallet.sdk.data.Synchronizer
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.android.synthetic.main.fragment_receive.*
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
@ -27,7 +29,7 @@ class ReceiveFragment : BaseFragment() {
lateinit var qrecycler: QRecycler
@Inject
lateinit var synchronizer: Synchronizer
lateinit var synchronizer: DataSyncronizer
lateinit var addressParts: Array<TextView>
@ -61,7 +63,9 @@ class ReceiveFragment : BaseFragment() {
super.onResume()
// TODO: replace these with channels. For now just wire the logic together
onAddressLoaded(loadAddress())
launch {
onAddressLoaded(synchronizer.getAddress())
}
// converter.scanBlocks()
}
@ -85,12 +89,6 @@ class ReceiveFragment : BaseFragment() {
addressParts[index].text = textSpan
}
// TODO: replace with tiered load. First check memory reference (textview contents?) then check DB, then load from JNI and write to DB
private fun loadAddress(): String {
return synchronizer.getAddress()
}
}
@Module

View File

@ -66,7 +66,7 @@ class SyncFragment : ProgressFragment(R.id.progress_sync) {
(view?.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
synchronizer.onSynchronizerErrorListener = ::onSynchronizerError
// synchronizer.onSynchronizerErrorListener = ::onSynchronizerError
}
override fun onResume() {

View File

@ -19,6 +19,7 @@ import cash.z.android.wallet.ui.presenter.BalancePresenter
import cash.z.android.wallet.ui.presenter.TransactionPresenter
import cash.z.android.wallet.ui.presenter.TransactionPresenterModule
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.ext.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.secure.Wallet
@ -114,10 +115,6 @@ class Zcon1HomeFragment : BaseFragment(), BalancePresenter.BalanceView, Transact
override fun onResume() {
super.onResume()
chipBucket.setOnBucketChangedListener(this)
}
override fun onStart() {
super.onStart()
mainActivity?.balancePresenter?.addBalanceView(this)
chipBucket.setOnBucketChangedListener(this)
launch {
@ -125,8 +122,8 @@ class Zcon1HomeFragment : BaseFragment(), BalancePresenter.BalanceView, Transact
}
}
override fun onStop() {
super.onStop()
override fun onPause() {
super.onPause()
mainActivity?.balancePresenter?.removeBalanceView(this)
chipBucket.removeOnBucketChangedListener(this)
transactionPresenter.stop()

View File

@ -29,12 +29,12 @@ class BalancePresenter {
// LifeCycle
//
fun start(scope: CoroutineScope, synchronizer: Synchronizer) {
fun start(scope: CoroutineScope, balanceChannel: ReceiveChannel<Wallet.WalletBalance>) {
Twig.sprout("BalancePresenter")
twig("balancePresenter starting!")
balanceJob?.cancel()
balanceJob = Job()
balanceJob = scope.launchBalanceBinder(synchronizer.balances())
balanceJob = scope.launchBalanceBinder(balanceChannel)
}
fun stop() {

View File

@ -1,5 +1,6 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.fragment.HistoryFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
@ -19,7 +20,7 @@ import kotlin.coroutines.CoroutineContext
class HistoryPresenter @Inject constructor(
private val view: HistoryFragment,
private var synchronizer: Synchronizer
private var synchronizer: DataSyncronizer
) : Presenter {
private var job: Job? = null
@ -32,7 +33,7 @@ class HistoryPresenter @Inject constructor(
job?.cancel()
job = Job()
twig("historyPresenter starting!")
view.launchTransactionBinder(synchronizer.allTransactions())
// view.launchTransactionBinder(synchronizer.allTransactions())
}
override fun stop() {

View File

@ -1,5 +1,6 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.extention.alert
import cash.z.android.wallet.ui.fragment.HomeFragment
@ -18,7 +19,7 @@ import javax.inject.Singleton
class HomePresenter @Inject constructor(
private val view: HomeFragment,
private val synchronizer: Synchronizer
private val synchronizer: DataSyncronizer
) : Presenter {
private var job: Job? = null
@ -32,15 +33,15 @@ class HomePresenter @Inject constructor(
}
override suspend fun start() {
job?.cancel()
job = Job()
twig("homePresenter starting! from ${this.hashCode()}")
with(view) {
launchBalanceBinder(synchronizer.balances())
launchTransactionBinder(synchronizer.allTransactions())
launchActiveTransactionMonitor(synchronizer.activeTransactions())
}
synchronizer.onSynchronizerErrorListener = view::onSynchronizerError
// job?.cancel()
// job = Job()
// twig("homePresenter starting! from ${this.hashCode()}")
// with(view) {
// launchBalanceBinder(synchronizer.balances())
// launchTransactionBinder(synchronizer.allTransactions())
// launchActiveTransactionMonitor(synchronizer.activeTransactions())
// }
// synchronizer.onSynchronizerErrorListener = view::onSynchronizerError
}
override fun stop() {
@ -99,11 +100,11 @@ class HomePresenter @Inject constructor(
}
fun onCancelActiveTransaction(transaction: ActiveSendTransaction) {
twig("requesting to cancel send for transaction ${transaction.internalId}")
val isTooLate = !synchronizer.cancelSend(transaction)
if (isTooLate) {
view.onCancelledTooLate()
}
// twig("requesting to cancel send for transaction ${transaction.internalId}")
// val isTooLate = !synchronizer.cancelSend(transaction)
// if (isTooLate) {
// view.onCancelledTooLate()
// }
}
}

View File

@ -1,5 +1,8 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.data.PendingTransaction
import cash.z.android.wallet.data.isFailure
import cash.z.android.wallet.ui.activity.MainActivity
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.data.*
@ -13,7 +16,7 @@ import javax.inject.Inject
class MainPresenter @Inject constructor(
private val view: MainActivity,
private val synchronizer: Synchronizer
private val synchronizer: DataSyncronizer
) : Presenter {
interface MainView : PresenterView {
@ -34,7 +37,7 @@ class MainPresenter @Inject constructor(
purchaseJob?.cancel()
purchaseJob = Job()
purchaseJob = view.launchPurchaseBinder(synchronizer.activeTransactions())
purchaseJob = view.launchPurchaseBinder(synchronizer.pendingTransactions())
}
override fun stop() {
@ -43,7 +46,7 @@ class MainPresenter @Inject constructor(
purchaseJob?.cancel()?.also { purchaseJob = null }
}
fun CoroutineScope.launchPurchaseBinder(channel: ReceiveChannel<Map<ActiveTransaction, TransactionState>>) = launch {
private fun CoroutineScope.launchPurchaseBinder(channel: ReceiveChannel<List<PendingTransaction>>) = launch {
twig("main purchase binder starting!")
for (new in channel) {
twig("main polled a purchase info")
@ -57,18 +60,18 @@ class MainPresenter @Inject constructor(
// Events
//
private fun bind(activeTransactions: Map<ActiveTransaction, TransactionState>) {
val newestState = activeTransactions.entries.last().value
if (newestState is TransactionState.Failure) {
view.orderFailed(PurchaseResult.Failure(newestState.reason))
private fun bind(activeTransactions: List<PendingTransaction>) {
val newest = activeTransactions.last()
if (newest.isFailure()) {
view.orderFailed(PurchaseResult.Failure(newest.errorMessage))
} else {
view.orderUpdated(PurchaseResult.Processing(newestState))
view.orderUpdated(PurchaseResult.Processing(newest))
}
}
sealed class PurchaseResult {
data class Processing(val state: TransactionState = TransactionState.Creating) : PurchaseResult()
data class Failure(val reason: String = "") : PurchaseResult()
data class Processing(val pendingTransaction: PendingTransaction) : PurchaseResult()
data class Failure(val reason: String? = "") : PurchaseResult()
}
}

View File

@ -1,5 +1,6 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.Twig
@ -11,7 +12,7 @@ import kotlin.coroutines.CoroutineContext
class ProgressPresenter @Inject constructor(
private val view: ProgressView,
private var synchronizer: Synchronizer
private var synchronizer: DataSyncronizer
) : Presenter {
private var job: Job? = null
@ -29,7 +30,6 @@ class ProgressPresenter @Inject constructor(
job?.cancel()
job = Job()
Twig.sprout("ProgressPresenter")
twig("starting")
view.launchProgressMonitor(synchronizer.progress())
}
@ -40,7 +40,7 @@ class ProgressPresenter @Inject constructor(
}
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
twig("progress monitor starting on thread ${Thread.currentThread().name}!")
twig("Progress monitor starting")
for (i in channel) {
bind(i)
}

View File

@ -1,6 +1,7 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.R
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.extention.toAppString
import cash.z.android.wallet.sample.SampleProperties
@ -23,7 +24,7 @@ import javax.inject.Inject
class SendPresenter @Inject constructor(
private val view: SendFragment,
private val synchronizer: Synchronizer
private val synchronizer: DataSyncronizer
) : Presenter {
interface SendView : PresenterView {

View File

@ -1,5 +1,6 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.data.DataSyncronizer
import cash.z.android.wallet.ui.fragment.Zcon1HomeFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction
@ -14,7 +15,7 @@ import javax.inject.Inject
class TransactionPresenter @Inject constructor(
private val view: Zcon1HomeFragment,
private val synchronizer: Synchronizer
private val synchronizer: DataSyncronizer
) : Presenter {
interface TransactionView : PresenterView {
@ -35,7 +36,7 @@ class TransactionPresenter @Inject constructor(
transactionJob?.cancel()
transactionJob = Job()
// transactionJob = view.launchPurchaseBinder(synchronizer.activeTransactions())
transactionJob = view.launchTransactionBinder(synchronizer.allTransactions())
// transactionJob = view.launchTransactionBinder(synchronizer.allTransactions())
}
override fun stop() {

View File

@ -1,20 +1,16 @@
package cash.z.android.wallet.ui.util
import cash.z.android.wallet.PokerChip
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.data.StaticTransactionRepository
import cash.z.android.wallet.data.TransactionSender
import cash.z.android.wallet.data.WalletTransactionEncoder
import cash.z.android.wallet.extention.toDbPath
import cash.z.android.wallet.extention.tryIgnore
import cash.z.android.wallet.sample.SampleProperties
import cash.z.wallet.sdk.data.PollingTransactionRepository
import cash.z.wallet.sdk.data.TransactionRepository
import cash.z.wallet.sdk.data.Twig
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.ext.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.rpc.Service
import cash.z.wallet.sdk.secure.Wallet
import cash.z.wallet.sdk.service.LightWalletService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
@ -22,7 +18,7 @@ import kotlin.properties.Delegates
import kotlin.properties.ReadOnlyProperty
class Broom(
private val service: LightWalletService,
private val sender: TransactionSender,
private val rustBackend: RustBackendWelding,
private val cacheDbName: String,
private val appWallet: Wallet
@ -39,7 +35,7 @@ class Broom(
// cloneCachedBlocks() // optional?
try {
val wallet = initWallet(walletSeedProvider)
val encoder = initEncoder(walletSeedProvider)
// verify & scan
//TODO: for now, assume validation is happening elsewhere and just scan here
Twig.sprout("broom-scan")
@ -48,12 +44,7 @@ class Broom(
Twig.clip("broom-scan")
if (scanResult) {
twig("successfully scanned blocks! Ready to sweep!!!")
val memo = "swag shirt test"
val address = "ztestsapling1yu2zy9aanf8pjf2qvm4qmn4k6q57y2d9fcs3vz0guthxx3m2aq57qm6hkx0580m9u9635xh6ttr"
// val address = appWallet.getAddress()
val transactionId = wallet.createRawSendTransaction(amount, address).also { checkTx(it) }
val transactionRaw: ByteArray? = repository.findTransactionById(transactionId)?.raw.also { checkRawTx(it) }
service.submitTransaction(transactionRaw!!).also { checkResponse(it) }
sender.sendToAddress(encoder, amount, appWallet.getAddress())
} else {
twig("failed to scan!")
}
@ -67,34 +58,10 @@ class Broom(
}
}
private fun checkTx(transactionId: Long) {
if (transactionId < 0) {
throw SweepException.Creation
} else {
twig("successfully created transaction!")
}
}
private fun checkRawTx(transactionRaw: ByteArray?) {
if (transactionRaw == null) {
throw SweepException.Disappeared
} else {
twig("found raw transaction in the dataDb")
}
}
private fun checkResponse(response: Service.SendResponse) {
if (response.errorCode < 0) {
throw SweepException.IncompletePass(response)
} else {
twig("successfully submitted. error code: ${response.errorCode}")
}
}
private fun initWallet(seedProvider: ReadOnlyProperty<Any?, ByteArray>): Wallet {
private fun initEncoder(seedProvider: ReadOnlyProperty<Any?, ByteArray>): WalletTransactionEncoder {
// TODO: maybe let this one live and make a new one?
DATA_DB_PATH.absoluteFile.delete()
return Wallet(
val wallet = Wallet(
ZcashWalletApplication.instance,
rustBackend,
DATA_DB_PATH.absolutePath,
@ -107,6 +74,7 @@ class Broom(
it.initialize()
}
}
return WalletTransactionEncoder(wallet, repository)
}
companion object {
@ -114,10 +82,4 @@ class Broom(
private val DATA_DB_PATH: File = ZcashWalletApplication.instance.getDatabasePath(DATA_DB_NAME)
}
sealed class SweepException(val errorMessage: String) : RuntimeException(errorMessage) {
object Creation : SweepException("failed to create raw transaction")
object Disappeared : SweepException("unable to find a matching raw transaction. This means the rust backend said it created a TX but when we looked for it in the DB it was missing!")
class IncompletePass(response: Service.SendResponse) : SweepException("submit failed with error code: ${response.errorCode} and message ${response.errorMessage}")
}
}