[#536] Adopt BlockHeight API (#537)

Note that this change modifies the on-disk representation of the wallet.  Anyone who has an earlier build install will see a runtime crash.  This is expected, as we are not supporting data migrations at this stage of development.  The solution is to uninstall/reinstall the app.
This commit is contained in:
Carter Jernigan 2022-07-26 10:46:23 -04:00 committed by GitHub
parent 072d73e99c
commit f9afd2e5f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 63 additions and 155 deletions

View File

@ -132,7 +132,7 @@ PLAY_CORE_KTX_VERSION=1.8.1
ZCASH_ANDROID_WALLET_PLUGINS_VERSION=1.0.0
ZCASH_BIP39_VERSION=1.0.2
# TODO [#279]: Revert to stable SDK before app release
ZCASH_SDK_VERSION=1.7.0-beta01
ZCASH_SDK_VERSION=1.8.0-beta01-SNAPSHOT
ZXING_VERSION=3.5.0

View File

@ -1,57 +0,0 @@
package cash.z.ecc.sdk.model
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.sdk.fixture.WalletBirthdayFixture
import cash.z.ecc.sdk.test.count
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class WalletBirthdayTest {
@Test
@SmallTest
fun serialize() {
val walletBirthday = WalletBirthdayFixture.new()
val jsonObject = walletBirthday.toJson()
assertEquals(5, jsonObject.keys().count())
assertTrue(jsonObject.has(WalletBirthday.KEY_VERSION))
assertTrue(jsonObject.has(WalletBirthday.KEY_HEIGHT))
assertTrue(jsonObject.has(WalletBirthday.KEY_HASH))
assertTrue(jsonObject.has(WalletBirthday.KEY_EPOCH_SECONDS))
assertTrue(jsonObject.has(WalletBirthday.KEY_TREE))
assertEquals(1, jsonObject.getInt(WalletBirthday.KEY_VERSION))
assertEquals(WalletBirthdayFixture.HEIGHT, jsonObject.getInt(WalletBirthday.KEY_HEIGHT))
assertEquals(WalletBirthdayFixture.HASH, jsonObject.getString(WalletBirthday.KEY_HASH))
assertEquals(WalletBirthdayFixture.EPOCH_SECONDS, jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS))
assertEquals(WalletBirthdayFixture.TREE, jsonObject.getString(WalletBirthday.KEY_TREE))
}
@Test
@SmallTest
fun epoch_seconds_as_long_that_would_overflow_int() {
val walletBirthday = WalletBirthdayFixture.new(time = Long.MAX_VALUE)
val jsonObject = walletBirthday.toJson()
assertEquals(Long.MAX_VALUE, jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS))
WalletBirthday.from(jsonObject).also {
assertEquals(Long.MAX_VALUE, it.time)
}
}
@Test
@SmallTest
fun round_trip() {
val walletBirthday = WalletBirthdayFixture.new()
val deserialized = WalletBirthday.from(walletBirthday.toJson())
assertEquals(walletBirthday, deserialized)
assertFalse(walletBirthday === deserialized)
}
}

View File

@ -1,6 +1,6 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.sdk.model.PersistableWallet
import cash.z.ecc.sdk.model.SeedPhrase
@ -9,13 +9,15 @@ object PersistableWalletFixture {
val NETWORK = ZcashNetwork.Testnet
val BIRTHDAY = WalletBirthdayFixture.new()
// These came from the mainnet 1500000.json file
@Suppress("MagicNumber")
val BIRTHDAY = BlockHeight.new(ZcashNetwork.Mainnet, 1500000L)
val SEED_PHRASE = SeedPhraseFixture.new()
fun new(
network: ZcashNetwork = NETWORK,
birthday: WalletBirthday = BIRTHDAY,
birthday: BlockHeight = BIRTHDAY,
seedPhrase: SeedPhrase = SEED_PHRASE
) = PersistableWallet(network, birthday, seedPhrase)
}

View File

@ -1,20 +0,0 @@
package cash.z.ecc.sdk.fixture
import cash.z.ecc.android.sdk.type.WalletBirthday
object WalletBirthdayFixture {
const val HEIGHT = 1500000
const val HASH = "00047a34c61409682f44640af9352023ad92f69b827d0f2b288f152ebea50f46"
const val EPOCH_SECONDS = 1627076501L
@Suppress("MaxLineLength")
const val TREE = "01172b95f271c6af8f68388f08c8ef970db8ec8d8d61204ecb7b2bb2c38262b92d0010016284585a6c85dadfef27ff33f1403926b4bb391de92e8be797e4280cc4ca2971000001a1ff388639379c0120782b3929bd8871af797be4b651f694aa961bad65a9c12400000001d806c98bda9653d5ae22757eed750871e16e0fb657f52c3d771a4411668e84330001260f6e9fac0922f98d58afbcc3f391ac19d5d944081466929a33b99df19c0e6a0000013d2fd009bf8a22d68f720eac19c411c99014ed9c5f85d5942e15d1fc039e28680001f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"
fun new(
height: Int = HEIGHT,
hash: String = HASH,
time: Long = EPOCH_SECONDS,
tree: String = TREE
) = WalletBirthday(height = height, hash = hash, time = time, tree = tree)
}

View File

@ -2,28 +2,42 @@ package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
fun CompactBlockProcessor.ProcessorInfo.downloadProgress() = if (lastDownloadRange.isEmpty()) {
PercentDecimal.ONE_HUNDRED_PERCENT
} else {
val numerator = (lastDownloadedHeight - lastDownloadRange.first + 1)
.toFloat()
.coerceAtLeast(PercentDecimal.MIN)
val denominator = (lastDownloadRange.last - lastDownloadRange.first + 1).toFloat()
fun CompactBlockProcessor.ProcessorInfo.downloadProgress(): PercentDecimal {
val lastDownloadRangeSnapshot = lastDownloadRange
val lastDownloadedHeightSnapshot = lastDownloadedHeight
val progress = (numerator / denominator).coerceAtMost(PercentDecimal.MAX)
return if (lastDownloadRangeSnapshot?.isEmpty() != false || lastDownloadedHeightSnapshot == null) {
PercentDecimal.ONE_HUNDRED_PERCENT
} else {
val numerator = (lastDownloadedHeightSnapshot.value - lastDownloadRangeSnapshot.start.value + 1)
.toFloat()
.coerceAtLeast(PercentDecimal.MIN)
val denominator = (lastDownloadRangeSnapshot.endInclusive.value - lastDownloadRangeSnapshot.start.value + 1)
.toFloat()
PercentDecimal(progress)
val progress = (numerator / denominator).coerceAtMost(PercentDecimal.MAX)
PercentDecimal(progress)
}
}
fun CompactBlockProcessor.ProcessorInfo.scanProgress() = if (lastScanRange.isEmpty()) {
PercentDecimal.ONE_HUNDRED_PERCENT
} else {
val numerator = (lastScannedHeight - lastScanRange.first + 1).toFloat().coerceAtLeast(PercentDecimal.MIN)
val demonimator = (lastScanRange.last - lastScanRange.first + 1).toFloat()
fun CompactBlockProcessor.ProcessorInfo.scanProgress(): PercentDecimal {
val lastScanRangeSnapshot = lastScanRange
val lastScannedHeightSnapshot = lastScannedHeight
val progress = (numerator / demonimator).coerceAtMost(PercentDecimal.MAX)
return if (lastScanRangeSnapshot?.isEmpty() != false || lastScannedHeightSnapshot == null) {
PercentDecimal.ONE_HUNDRED_PERCENT
} else {
val numerator = (lastScannedHeightSnapshot.value - lastScanRangeSnapshot.start.value + 1)
.toFloat()
.coerceAtLeast(PercentDecimal.MIN)
val demonimator = (lastScanRangeSnapshot.endInclusive.value - lastScanRangeSnapshot.start.value + 1)
.toFloat()
PercentDecimal(progress)
val progress = (numerator / demonimator).coerceAtMost(PercentDecimal.MAX)
PercentDecimal(progress)
}
}
// These are estimates

View File

@ -3,8 +3,7 @@ package cash.z.ecc.sdk.model
import android.app.Application
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toEntropy
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.sdk.type.fromResources
import kotlinx.coroutines.Dispatchers
@ -16,7 +15,7 @@ import org.json.JSONObject
*/
data class PersistableWallet(
val network: ZcashNetwork,
val birthday: WalletBirthday?,
val birthday: BlockHeight?,
val seedPhrase: SeedPhrase
) {
@ -29,7 +28,7 @@ data class PersistableWallet(
put(KEY_VERSION, VERSION_1)
put(KEY_NETWORK_ID, network.id)
birthday?.let {
put(KEY_BIRTHDAY, it.toJson())
put(KEY_BIRTHDAY, it.value)
}
put(KEY_SEED_PHRASE, seedPhrase.joinToString())
}
@ -48,15 +47,19 @@ data class PersistableWallet(
fun from(jsonObject: JSONObject): PersistableWallet {
when (val version = jsonObject.getInt(KEY_VERSION)) {
VERSION_1 -> {
val networkId = jsonObject.getInt(KEY_NETWORK_ID)
val network = run {
val networkId = jsonObject.getInt(KEY_NETWORK_ID)
ZcashNetwork.from(networkId)
}
val birthday = if (jsonObject.has(KEY_BIRTHDAY)) {
WalletBirthday.from(jsonObject.getJSONObject(KEY_BIRTHDAY))
val birthdayBlockHeightLong = jsonObject.getLong(KEY_BIRTHDAY)
BlockHeight.new(network, birthdayBlockHeightLong)
} else {
null
}
val seedPhrase = jsonObject.getString(KEY_SEED_PHRASE)
return PersistableWallet(ZcashNetwork.from(networkId), birthday, SeedPhrase.new(seedPhrase))
return PersistableWallet(network, birthday, SeedPhrase.new(seedPhrase))
}
else -> {
throw IllegalArgumentException("Unsupported version $version")
@ -69,11 +72,11 @@ data class PersistableWallet(
*/
suspend fun new(application: Application): PersistableWallet {
val zcashNetwork = ZcashNetwork.fromResources(application)
val walletBirthday = WalletBirthdayTool.loadNearest(application, zcashNetwork)
val birthday = BlockHeight.ofLatestCheckpoint(application, zcashNetwork)
val seedPhrase = newSeedPhrase()
return PersistableWallet(zcashNetwork, walletBirthday, seedPhrase)
return PersistableWallet(zcashNetwork, birthday, seedPhrase)
}
}
}

View File

@ -1,43 +0,0 @@
package cash.z.ecc.sdk.model
import cash.z.ecc.android.sdk.type.WalletBirthday
import org.json.JSONObject
internal val WalletBirthday.Companion.VERSION_1
get() = 1
internal val WalletBirthday.Companion.KEY_VERSION
get() = "version"
internal val WalletBirthday.Companion.KEY_HEIGHT
get() = "height"
internal val WalletBirthday.Companion.KEY_HASH
get() = "hash"
internal val WalletBirthday.Companion.KEY_EPOCH_SECONDS
get() = "epoch_seconds"
internal val WalletBirthday.Companion.KEY_TREE
get() = "tree"
fun WalletBirthday.Companion.from(jsonString: String) = from(JSONObject(jsonString))
fun WalletBirthday.Companion.from(jsonObject: JSONObject): WalletBirthday {
when (val version = jsonObject.getInt(WalletBirthday.KEY_VERSION)) {
WalletBirthday.VERSION_1 -> {
val height = jsonObject.getInt(WalletBirthday.KEY_HEIGHT)
val hash = jsonObject.getString(WalletBirthday.KEY_HASH)
val epochSeconds = jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS)
val tree = jsonObject.getString(WalletBirthday.KEY_TREE)
return WalletBirthday(height, hash, epochSeconds, tree)
}
else -> {
throw IllegalArgumentException("Unsupported version $version")
}
}
}
fun WalletBirthday.toJson() = JSONObject().apply {
put(WalletBirthday.KEY_VERSION, WalletBirthday.VERSION_1)
put(WalletBirthday.KEY_HEIGHT, height)
put(WalletBirthday.KEY_HASH, hash)
put(WalletBirthday.KEY_EPOCH_SECONDS, time)
put(WalletBirthday.KEY_TREE, tree)
}

View File

@ -136,13 +136,15 @@ class WalletCoordinator(context: Context) {
* In order for a rescan to occur, the synchronizer must be loaded already
* which would happen if the UI is collecting it.
*
* @return True if the rewind was performed and false if the wipe was not performed.
* @return True if the rescan was performed and false if the rescan was not performed.
*/
suspend fun rescanBlockchain(): Boolean {
synchronizerMutex.withLock {
synchronizer.value?.let {
it.rewindToNearestHeight(it.latestBirthdayHeight, true)
return true
it.latestBirthdayHeight?.let { height ->
it.rewindToNearestHeight(height, true)
return true
}
}
}
@ -235,6 +237,6 @@ private suspend fun PersistableWallet.toConfig(): Initializer.Config {
val vk = deriveViewingKey()
return Initializer.Config {
it.importWallet(vk, birthday?.height, network, network.defaultHost, network.defaultPort)
it.importWallet(vk, birthday, network, network.defaultHost, network.defaultPort)
}
}

View File

@ -21,7 +21,13 @@ object WalletSnapshotFixture {
@Suppress("LongParameterList")
fun new(
status: Synchronizer.Status = STATUS,
processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(),
processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo(
null,
null,
null,
null,
null
),
orchardBalance: WalletBalance = ORCHARD_BALANCE,
saplingBalance: WalletBalance = SAPLING_BALANCE,
transparentBalance: WalletBalance = TRANSPARENT_BALANCE,

View File

@ -11,6 +11,7 @@ import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.db.entity.Transaction
import cash.z.ecc.android.sdk.db.entity.isMined
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
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
@ -282,7 +283,7 @@ sealed class SynchronizerError {
override fun getCauseMessage(): String? = error?.localizedMessage
}
class Chain(val x: Int, val y: Int) : SynchronizerError() {
class Chain(val x: BlockHeight, val y: BlockHeight) : SynchronizerError() {
override fun getCauseMessage(): String = "$x, $y"
}
}