[#325] Migrate to SDK with BlockHeight AP

SDK 1.8 is not released yet, so this consumes a snapshot of the SDK 1.8 release.
This commit is contained in:
Carter Jernigan 2022-07-27 13:56:08 -04:00 committed by GitHub
parent 48d86b23ae
commit acd0012919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 137 additions and 82 deletions

View File

@ -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,

View File

@ -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))
}

View File

@ -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
}
}

View File

@ -76,7 +76,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(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) {

View File

@ -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<FragmentBalanceDetailBinding>() {
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<FragmentBalanceDetailBinding>() {
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<FragmentBalanceDetailBinding>() {
}
}
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<FragmentBalanceDetailBinding>() {
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"
}
}

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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<PendingTransaction> {
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")
}
}

View File

@ -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<PendingTransaction> = listOf(),
val pendingUnmined: List<PendingTransaction> = listOf(),
val pendingBalance: Long = 0L,
val latestHeight: Int = 0,
val latestHeight: BlockHeight? = null,
) {
val hasUnconfirmed = pendingUnconfirmed.isNotEmpty()
val hasUnmined = pendingUnmined.isNotEmpty()

View File

@ -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<FragmentBackupBinding>() {
}
// 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) {

View File

@ -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<FragmentLandingBinding>() {
// 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}")
}

View File

@ -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<FragmentRestoreBinding>(), View.OnKeyListen
}
var birthday = binding.root.findViewById<TextView>(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 {

View File

@ -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) {

View File

@ -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"

View File

@ -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")
}
}
}
}
}