diff --git a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt index 751749d..9677877 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt @@ -1,6 +1,7 @@ package cash.z.ecc.android.feedback import cash.z.ecc.android.ZcashWalletApp +import cash.z.ecc.android.sdk.model.BlockHeight object Report { @@ -56,7 +57,7 @@ object Report { object Error { object NonFatal { - class Reorg(errorBlockHeight: Int, rewindBlockHeight: Int) : Feedback.AppError( + class Reorg(errorBlockHeight: BlockHeight, rewindBlockHeight: BlockHeight) : Feedback.AppError( "reorg", "Chain error detected at height $errorBlockHeight, rewinding to $rewindBlockHeight", false, diff --git a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt index bb601ec..b0645fd 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt @@ -78,6 +78,7 @@ import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException import cash.z.ecc.android.sdk.ext.BatchMetrics import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.ui.history.HistoryViewModel import cash.z.ecc.android.ui.util.MemoUtil import cash.z.ecc.android.util.twig @@ -122,7 +123,7 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) { Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED - val latestHeight: Int? get() = if (isInitialized) { + val latestHeight: BlockHeight? get() = if (isInitialized) { synchronizerComponent.synchronizer().latestHeight } else { null @@ -278,7 +279,7 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) { if (isComplete) { if (batchMetrics.cumulativeItems > reportingThreshold) { val network = synchronizerComponent.synchronizer().network.networkName - reportAction(Report.Performance.ScanRate(network, batchMetrics.cumulativeItems, batchMetrics.cumulativeTime, batchMetrics.cumulativeIps)) + reportAction(Report.Performance.ScanRate(network, batchMetrics.cumulativeItems.toInt(), batchMetrics.cumulativeTime, batchMetrics.cumulativeIps)) } } } @@ -584,7 +585,7 @@ class MainActivity : AppCompatActivity(R.layout.main_activity) { return true } - private fun onChainError(errorHeight: Int, rewindHeight: Int) { + private fun onChainError(errorHeight: BlockHeight, rewindHeight: BlockHeight) { feedback.report(Reorg(errorHeight, rewindHeight)) } diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt index 8301ecd..591a4e4 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/history/HistoryViewModel.kt @@ -78,7 +78,7 @@ class HistoryViewModel @Inject constructor() : ViewModel() { tx != null && tx.toAddress.isNullOrEmpty() && tx.value > 0L && tx.minedHeight > 0 -> true else -> null } - isMined = tx?.minedHeight != null && tx.minedHeight > synchronizer.network.saplingActivationHeight + isMined = tx?.minedHeight != null && tx.minedHeight > synchronizer.network.saplingActivationHeight.value topValue = if (tx == null) "" else "\$${WalletZecFormmatter.toZecStringFull(tx.valueInZatoshi)}" minedHeight = String.format("%,d", tx?.minedHeight ?: 0) val flags = @@ -102,7 +102,7 @@ class HistoryViewModel @Inject constructor() : ViewModel() { tx?.let { val isMined = it.blockTimeInSeconds != 0L if (isMined) { - val hasLatestHeight = latestHeight != null && latestHeight > synchronizer.network.saplingActivationHeight + val hasLatestHeight = latestHeight != null && latestHeight > synchronizer.network.saplingActivationHeight.value if (it.minedHeight > 0 && hasLatestHeight) { val confirmations = latestHeight!! - it.minedHeight + 1 confirmation = if (confirmations >= 10) getString(R.string.transaction_status_confirmed) else "$confirmations ${getString( @@ -165,7 +165,7 @@ class HistoryViewModel @Inject constructor() : ViewModel() { private fun isSufficientlyOld(tx: ConfirmedTransaction): Boolean { val threshold = 75 * 1000 * 25 // approx 25 blocks val delta = System.currentTimeMillis() / 1000L - tx.blockTimeInSeconds - return tx.minedHeight > synchronizer.network.saplingActivationHeight && + return tx.minedHeight > synchronizer.network.saplingActivationHeight.value && delta < threshold } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt index 58dd0f4..c69899b 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/history/TransactionViewHolder.kt @@ -76,7 +76,7 @@ class TransactionViewHolder(itemView: View) : Recycler R.string.transaction_status_pending ) // TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?) - if (!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight ?: -1)) lineTwo = + if (!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight?.value ?: -1)) lineTwo = str(R.string.transaction_status_expired) amountDisplay = "- $amountZec" if (isMined) { diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt index b527625..52ea00a 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailFragment.kt @@ -17,6 +17,7 @@ import cash.z.ecc.android.ext.toAppColor import cash.z.ecc.android.ext.toSplitColorSpan import cash.z.ecc.android.feedback.Report.Tap.RECEIVE_BACK import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.home.BalanceDetailViewModel.StatusModel import kotlinx.coroutines.flow.launchIn @@ -26,7 +27,7 @@ import kotlinx.coroutines.launch class BalanceDetailFragment : BaseFragment() { private val viewModel: BalanceDetailViewModel by viewModel() - private var lastSignal = -1 + private var lastSignal: BlockHeight? = null override fun inflate(inflater: LayoutInflater): FragmentBalanceDetailBinding = FragmentBalanceDetailBinding.inflate(inflater) @@ -127,7 +128,7 @@ class BalanceDetailFragment : BaseFragment() { binding.textBlockHeight.text = String.format("%,d", status.info.lastScannedHeight) + " of " + String.format("%,d", status.info.networkBlockHeight) } else { status.info.lastScannedHeight.let { height -> - if (height < 1) { + if (height == null) { binding.textBlockHeightPrefix.text = "Processing..." binding.textBlockHeight.text = "" } else { @@ -145,9 +146,9 @@ class BalanceDetailFragment : BaseFragment() { } } - private fun sendNewBlockSignal(currentHeight: Int) { + private fun sendNewBlockSignal(currentHeight: BlockHeight?) { // prevent a flood of signals while scanning blocks - if (lastSignal != -1 && currentHeight > lastSignal) { + if (lastSignal != null && (currentHeight?.value ?: 0) > lastSignal!!.value) { mainActivity?.vibrate(0, 100, 100, 300) Toast.makeText(mainActivity, "New block!", Toast.LENGTH_SHORT).show() } @@ -199,7 +200,7 @@ class BalanceDetailFragment : BaseFragment() { if (status.contains("Awaiting")) status += " and " status += "$count outbound ${"transaction".plural(count)}" remainingConfirmations().firstOrNull()?.let { remaining -> - status += " with $remaining ${"confirmation".plural(remaining)} remaining" + status += " with $remaining ${"confirmation".plural(remaining.toInt())} remaining" } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt index 8038dc0..5d60aa1 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/home/BalanceDetailViewModel.kt @@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.db.entity.isMined import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import kotlinx.coroutines.flow.Flow @@ -116,15 +117,17 @@ class BalanceDetailViewModel @Inject constructor() : ViewModel() { val hasUnmined = pendingUnmined.isNotEmpty() val hasPendingShieldedBalance = (pendingShieldedBalance?.value ?: 0L) > 0L val hasPendingTransparentBalance = (pendingTransparentBalance?.value ?: 0L) > 0L - val missingBlocks = (info.networkBlockHeight - info.lastScannedHeight).coerceAtLeast(0) + val missingBlocks = ((info.networkBlockHeight?.value ?: 0) - (info.lastScannedHeight?.value ?: 0)).coerceAtLeast(0) - private fun PendingTransaction.isConfirmed(networkBlockHeight: Int): Boolean { - return isMined() && (networkBlockHeight - minedHeight + 1) > 10 // fix: plus 1 because the mined block counts as the FIRST confirmation + private fun PendingTransaction.isConfirmed(networkBlockHeight: BlockHeight?): Boolean { + return networkBlockHeight?.let { + isMined() && (it.value - minedHeight + 1) > 10 // fix: plus 1 because the mined block counts as the FIRST confirmation + } ?: false } fun remainingConfirmations(confirmationsRequired: Int = 10) = pendingUnconfirmed - .map { confirmationsRequired - (info.lastScannedHeight - it.minedHeight + 1) } // fix: plus 1 because the mined block counts as the FIRST confirmation + .map { confirmationsRequired - ((info.lastScannedHeight?.value ?: -1) - it.minedHeight + 1) } // fix: plus 1 because the mined block counts as the FIRST confirmation .filter { it > 0 } .sortedDescending() } diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt index 18cc06d..d0df62e 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/home/HomeViewModel.kt @@ -120,7 +120,7 @@ class HomeViewModel @Inject constructor() : ViewModel() { data class UiModel( val status: Synchronizer.Status = DISCONNECTED, - val processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(), + val processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(null, null, null, null, null), val orchardBalance: WalletBalance?, val saplingBalance: WalletBalance?, val transparentBalance: WalletBalance?, @@ -141,11 +141,11 @@ class HomeViewModel @Inject constructor() : ViewModel() { val isDisconnected = status == DISCONNECTED val downloadProgress: Int get() { return processorInfo.run { - if (lastDownloadRange.isEmpty()) { + if (lastDownloadRange?.isEmpty() == true) { 100 } else { val progress = - (((lastDownloadedHeight - lastDownloadRange.first + 1).coerceAtLeast(0).toFloat() / (lastDownloadRange.last - lastDownloadRange.first + 1)) * 100.0f).coerceAtMost( + ((((lastDownloadedHeight?.value ?: 0) - (lastDownloadRange?.start?.value ?: 0) + 1).coerceAtLeast(0).toFloat() / ((lastDownloadRange?.endInclusive?.value ?: 0) - (lastDownloadRange?.start?.value ?: 0) + 1)) * 100.0f).coerceAtMost( 100.0f ).roundToInt() progress @@ -154,10 +154,10 @@ class HomeViewModel @Inject constructor() : ViewModel() { } val scanProgress: Int get() { return processorInfo.run { - if (lastScanRange.isEmpty()) { + if (lastScanRange?.isEmpty() == true) { 100 } else { - val progress = (((lastScannedHeight - lastScanRange.first + 1).coerceAtLeast(0).toFloat() / (lastScanRange.last - lastScanRange.first + 1)) * 100.0f).coerceAtMost(100.0f).roundToInt() + val progress = ((((lastScannedHeight?.value ?: 0) - (lastScanRange?.start?.value ?: 0) + 1).coerceAtLeast(0).toFloat() / ((lastScanRange?.endInclusive?.value ?: 0) - (lastScanRange?.start?.value ?: 0) + 1)) * 100.0f).coerceAtMost(100.0f).roundToInt() progress } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt index db13c87..72c9580 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/profile/ProfileViewModel.kt @@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.Initializer import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.db.entity.PendingTransaction import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.util.twig @@ -48,8 +49,10 @@ class ProfileViewModel @Inject constructor() : ViewModel() { suspend fun fetchUtxos(): Int { val address = getTransparentAddress() - val height: Int = lockBox[Const.Backup.BIRTHDAY_HEIGHT] ?: synchronizer.network.saplingActivationHeight - return synchronizer.refreshUtxos(address, height) ?: 0 + val height: Long = lockBox[Const.Backup.BIRTHDAY_HEIGHT] + ?: synchronizer.network.saplingActivationHeight.value + return synchronizer.refreshUtxos(address, BlockHeight.new(synchronizer.network, height)) + ?: 0 } suspend fun getTransparentBalance(): WalletBalance { @@ -60,9 +63,19 @@ class ProfileViewModel @Inject constructor() : ViewModel() { fun shieldFunds(): Flow { return lockBox.getBytes(Const.Backup.SEED)?.let { val sk = runBlocking { DerivationTool.deriveSpendingKeys(it, synchronizer.network)[0] } - val tsk = runBlocking { DerivationTool.deriveTransparentSecretKey(it, synchronizer.network) } - val addr = runBlocking { DerivationTool.deriveTransparentAddressFromPrivateKey(tsk, synchronizer.network) } - synchronizer.shieldFunds(sk, tsk, "${ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX}\nAll UTXOs from $addr").onEach { + val tsk = + runBlocking { DerivationTool.deriveTransparentSecretKey(it, synchronizer.network) } + val addr = runBlocking { + DerivationTool.deriveTransparentAddressFromPrivateKey( + tsk, + synchronizer.network + ) + } + synchronizer.shieldFunds( + sk, + tsk, + "${ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX}\nAll UTXOs from $addr" + ).onEach { twig("Received shielding txUpdate: ${it?.toString()}") // updateMetrics(it) // reportFailures(it) @@ -84,7 +97,11 @@ class ProfileViewModel @Inject constructor() : ViewModel() { fun wipe() { synchronizer.stop() - Toast.makeText(ZcashWalletApp.instance, "SUCCESS! Wallet data cleared. Please relaunch to rescan!", Toast.LENGTH_LONG).show() + Toast.makeText( + ZcashWalletApp.instance, + "SUCCESS! Wallet data cleared. Please relaunch to rescan!", + Toast.LENGTH_LONG + ).show() runBlocking { Initializer.erase( ZcashWalletApp.instance, @@ -94,33 +111,57 @@ class ProfileViewModel @Inject constructor() : ViewModel() { } suspend fun fullRescan() { - rewindTo(synchronizer.latestBirthdayHeight) + synchronizer.latestBirthdayHeight?.let { + rewindTo(it) + } } suspend fun quickRescan() { - rewindTo(synchronizer.latestHeight - 8064) + synchronizer.latestHeight?.let { + val newHeightValue = + (it.value - 8064L).coerceAtLeast(synchronizer.network.saplingActivationHeight.value) + rewindTo(BlockHeight.new(synchronizer.network, newHeightValue)) + } } - private suspend fun rewindTo(targetHeight: Int) { + private suspend fun rewindTo(targetHeight: BlockHeight) { twig("TMP: rewinding to targetHeight $targetHeight") synchronizer.rewindToNearestHeight(targetHeight, true) } - fun fullScanDistance() = - (synchronizer.latestHeight - synchronizer.latestBirthdayHeight).coerceAtLeast(0) + fun fullScanDistance(): Long { + synchronizer.latestHeight?.let { latestHeight -> + synchronizer.latestBirthdayHeight?.let { latestBirthdayHeight -> + return (latestHeight.value - latestBirthdayHeight.value).coerceAtLeast(0) + } + } + return 0 + } fun quickScanDistance(): Int { val latest = synchronizer.latestHeight val oneWeek = 60 * 60 * 24 / 75 * 7 // a week's worth of blocks - var foo = 0 - runBlocking { - foo = synchronizer.getNearestRewindHeight(latest - oneWeek) + val height = BlockHeight.new( + synchronizer.network, + ((latest?.value ?: synchronizer.network.saplingActivationHeight.value) - oneWeek) + .coerceAtLeast(synchronizer.network.saplingActivationHeight.value) + ) + val foo = runBlocking { + synchronizer.getNearestRewindHeight(height) } - return latest - foo + return ((latest?.value ?: 0) - foo.value).toInt().coerceAtLeast(0) } + fun blocksToMinutesString(blocks: BlockHeight): String { + val duration = (blocks.value / bps.toDouble()).toDuration(DurationUnit.SECONDS) + return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") + } fun blocksToMinutesString(blocks: Int): String { val duration = (blocks / bps.toDouble()).toDuration(DurationUnit.SECONDS) return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") } + fun blocksToMinutesString(blocks: Long): String { + val duration = (blocks / bps.toDouble()).toDuration(DurationUnit.SECONDS) + return duration.toString(DurationUnit.MINUTES).replace("m", " minutes") + } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt index 7bf1fc9..31da5cc 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/AutoShieldViewModel.kt @@ -10,6 +10,7 @@ import cash.z.ecc.android.sdk.db.entity.isMined import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.tool.DerivationTool @@ -49,8 +50,10 @@ class AutoShieldViewModel @Inject constructor() : ViewModel() { emit(StatusModel(unmined, unconfirmed, pending, info.networkBlockHeight)) } - private fun PendingTransaction.isConfirmed(networkBlockHeight: Int): Boolean { - return isMined() && (networkBlockHeight - minedHeight + 1) > 10 + private fun PendingTransaction.isConfirmed(networkBlockHeight: BlockHeight?): Boolean { + return networkBlockHeight?.let { height -> + isMined() && (height.value - minedHeight + 1) > 10 + } ?: false } fun cancel(id: Long) { @@ -129,7 +132,7 @@ class AutoShieldViewModel @Inject constructor() : ViewModel() { val pendingUnconfirmed: List = listOf(), val pendingUnmined: List = listOf(), val pendingBalance: Long = 0L, - val latestHeight: Int = 0, + val latestHeight: BlockHeight? = null, ) { val hasUnconfirmed = pendingUnconfirmed.isNotEmpty() val hasUnmined = pendingUnmined.isNotEmpty() diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt index 2fb04f7..43c1a7d 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt @@ -21,7 +21,9 @@ import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE import cash.z.ecc.android.feedback.Report.Tap.BACKUP_VERIFY import cash.z.ecc.android.feedback.measure import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.android.sdk.db.entity.Block import cash.z.ecc.android.sdk.ext.ZcashSdk +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP import cash.z.ecc.android.ui.util.AddressPartNumberSpan @@ -91,23 +93,25 @@ class BackupFragment : BaseFragment() { } // TODO: move this into the SDK - private suspend fun calculateBirthday(): Int { - var storedBirthday = 0 - var oldestTransactionHeight = 0 - var activationHeight = 0 + private suspend fun calculateBirthday(): BlockHeight { + var storedBirthday: BlockHeight? = null + var oldestTransactionHeight: BlockHeight? = null + var activationHeight: BlockHeight? = null try { - activationHeight = mainActivity?.synchronizerComponent?.synchronizer()?.network?.saplingActivationHeight ?: 0 - storedBirthday = walletSetup.loadBirthdayHeight() ?: 0 - oldestTransactionHeight = mainActivity?.synchronizerComponent?.synchronizer()?.receivedTransactions?.first()?.last()?.minedHeight ?: 0 + activationHeight = mainActivity?.synchronizerComponent?.synchronizer()?.network?.saplingActivationHeight + storedBirthday = walletSetup.loadBirthdayHeight() + oldestTransactionHeight = mainActivity?.synchronizerComponent?.synchronizer()?.receivedTransactions?.first()?.last()?.minedHeight?.let { + BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it) + } // to be safe adjust for reorgs (and generally a little cushion is good for privacy) // so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away oldestTransactionHeight = ZcashSdk.MAX_REORG_SIZE.let { boundary -> - oldestTransactionHeight.let { it - it.rem(boundary) - boundary } + oldestTransactionHeight?.let { it.value - it.value.rem(boundary) - boundary }?.let { BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it) } } } catch (t: Throwable) { twig("failed to calculate birthday due to: $t") } - return maxOf(storedBirthday, oldestTransactionHeight, activationHeight) + return listOfNotNull(storedBirthday, oldestTransactionHeight, activationHeight).maxBy { it.value } } private fun onEnterWallet(showMessage: Boolean = !this.hasBackUp) { diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt index 8714977..03fb885 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt @@ -24,6 +24,7 @@ import cash.z.ecc.android.feedback.Report.Tap.LANDING_BACKUP_SKIPPED_2 import cash.z.ecc.android.feedback.Report.Tap.LANDING_BACKUP_SKIPPED_3 import cash.z.ecc.android.feedback.Report.Tap.LANDING_NEW import cash.z.ecc.android.feedback.Report.Tap.LANDING_RESTORE +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.type.ZcashNetwork import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP @@ -134,17 +135,17 @@ class LandingFragment : BaseFragment() { // AKA import wallet private fun onUseDevWallet() { val seedPhrase: String - val birthday: Int + val birthday: BlockHeight // new testnet dev wallet when (ZcashWalletApp.instance.defaultNetwork) { ZcashNetwork.Mainnet -> { seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" - birthday = 991645 // 663174 + birthday = BlockHeight.new(ZcashNetwork.Mainnet, 991645) // 663174 } ZcashNetwork.Testnet -> { seedPhrase = "quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten" - birthday = 1330190 + birthday = BlockHeight.new(ZcashNetwork.Testnet, 1330190) } else -> throw RuntimeException("No developer wallet exists for network ${ZcashWalletApp.instance.defaultNetwork}") } diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt index b268249..628bdef 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/RestoreFragment.kt @@ -27,6 +27,7 @@ import cash.z.ecc.android.feedback.Report.Tap.RESTORE_BACK import cash.z.ecc.android.feedback.Report.Tap.RESTORE_CLEAR import cash.z.ecc.android.feedback.Report.Tap.RESTORE_DONE import cash.z.ecc.android.feedback.Report.Tap.RESTORE_SUCCESS +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.ui.base.BaseFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.tylersuehr.chips.Chip @@ -133,18 +134,18 @@ class RestoreFragment : BaseFragment(), View.OnKeyListen } var birthday = binding.root.findViewById(R.id.input_birthdate).text.toString() .let { birthdateString -> - if (birthdateString.isNullOrEmpty()) activation else birthdateString.toInt() - }.coerceAtLeast(activation) + if (birthdateString.isNullOrEmpty()) activation.value else birthdateString.toLong() + }.coerceAtLeast(activation.value) try { walletSetup.validatePhrase(seedPhrase) - importWallet(seedPhrase, birthday) + importWallet(seedPhrase, BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, birthday)) } catch (t: Throwable) { mainActivity?.showInvalidSeedPhraseError(t) } } - private fun importWallet(seedPhrase: String, birthday: Int) { + private fun importWallet(seedPhrase: String, birthday: BlockHeight?) { mainActivity?.reportFunnel(Restore.ImportStarted) mainActivity?.hideKeyboard() mainActivity?.apply { diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt index 875212f..674dcfa 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt @@ -10,14 +10,11 @@ import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.sdk.Initializer import cash.z.ecc.android.sdk.exception.InitializerException +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.tool.DerivationTool -import cash.z.ecc.android.sdk.tool.WalletBirthdayTool import cash.z.ecc.android.sdk.type.UnifiedViewingKey -import cash.z.ecc.android.sdk.type.WalletBirthday import cash.z.ecc.android.sdk.type.ZcashNetwork -import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED -import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP -import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP +import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.* import cash.z.ecc.android.util.twig import cash.z.ecc.kotlin.mnemonic.Mnemonics import com.bugsnag.android.Bugsnag @@ -62,10 +59,13 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { mnemonics.validate(seedPhrase.toCharArray()) } - fun loadBirthdayHeight(): Int? { + fun loadBirthdayHeight(): BlockHeight? { val h: Int? = lockBox[Const.Backup.BIRTHDAY_HEIGHT] twig("Loaded birthday with key ${Const.Backup.BIRTHDAY_HEIGHT} and found $h") - return h + h?.let { + return BlockHeight.new(ZcashWalletApp.instance.defaultNetwork, it.toLong()) + } + return null } suspend fun newWallet(): Initializer { @@ -77,10 +77,10 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { return openStoredWallet() } - suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer { + suspend fun importWallet(seedPhrase: String, birthdayHeight: BlockHeight?): Initializer { val network = ZcashWalletApp.instance.defaultNetwork twig("Importing ${network.networkName} wallet. Requested birthday: $birthdayHeight") - storeWallet(seedPhrase.toCharArray(), network, loadNearestBirthday(network, birthdayHeight)) + storeWallet(seedPhrase.toCharArray(), network, birthdayHeight ?: loadNearestBirthday(network)) return openStoredWallet() } @@ -167,16 +167,15 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { } } - private suspend fun onMissingBirthday(network: ZcashNetwork): Int = failWith(InitializerException.MissingBirthdayException) { + private suspend fun onMissingBirthday(network: ZcashNetwork): BlockHeight = failWith(InitializerException.MissingBirthdayException) { twig("Recover Birthday: falling back to sapling birthday") - loadNearestBirthday(network, network.saplingActivationHeight).height + loadNearestBirthday(network) } - private suspend fun loadNearestBirthday(network: ZcashNetwork, birthdayHeight: Int? = null) = - WalletBirthdayTool.loadNearest( + private suspend fun loadNearestBirthday(network: ZcashNetwork) = + BlockHeight.ofLatestCheckpoint( ZcashWalletApp.instance, network, - birthdayHeight ) // @@ -192,7 +191,7 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { private suspend fun storeWallet( seedPhraseChars: CharArray, network: ZcashNetwork, - birthday: WalletBirthday + birthday: BlockHeight ) { check(!lockBox.getBoolean(Const.Backup.HAS_SEED)) { "Error! Cannot store a seed when one already exists! This would overwrite the" + @@ -210,9 +209,9 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() { } } - private suspend fun storeBirthday(birthday: WalletBirthday) = withContext(IO) { - twig("Storing birthday ${birthday.height} with and key ${Const.Backup.BIRTHDAY_HEIGHT}") - lockBox[Const.Backup.BIRTHDAY_HEIGHT] = birthday.height + private suspend fun storeBirthday(birthday: BlockHeight) = withContext(IO) { + twig("Storing birthday ${birthday.value} with and key ${Const.Backup.BIRTHDAY_HEIGHT}") + lockBox[Const.Backup.BIRTHDAY_HEIGHT] = birthday.value } private suspend fun storeSeedPhrase(seedPhrase: CharArray) = withContext(IO) { diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index f722134..0f31173 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -83,7 +83,7 @@ object Deps { object Zcash { const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0" const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.1" - const val SDK = "cash.z.ecc.android:zcash-android-sdk:1.7.0-beta01" + const val SDK = "cash.z.ecc.android:zcash-android-sdk:1.8.0-beta01-SNAPSHOT" } object Misc { const val LOTTIE = "com.airbnb.android:lottie:3.7.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 387c736..79f3b97 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,13 +18,13 @@ dependencyResolutionManagement { maven("https://jitpack.io") jcenter() // Uncomment to use a snapshot version of the SDK, e.g. when the SDK version ends in -SNAPSHOT - //maven("https://oss.sonatype.org/content/repositories/snapshots") { - // if (isRepoRestrictionEnabled) { - // content { - // includeGroup("cash.z.ecc.android") - // } - // } - //} + maven("https://oss.sonatype.org/content/repositories/snapshots") { + if (isRepoRestrictionEnabled) { + content { + includeGroup("cash.z.ecc.android") + } + } + } } }