[#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:
parent
48d86b23ae
commit
acd0012919
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue