From 7b7275b5bde8a019d900d9e0d376ff0cae1b45e8 Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 6 Sep 2023 15:24:27 +0200 Subject: [PATCH] Migrate to latest Rust revision follow-up changes --- .../z/ecc/android/sdk/internal/Backend.kt | 13 +++++++ .../z/ecc/android/sdk/internal/Derivation.kt | 1 + .../android/sdk/internal/jni/RustBackend.kt | 4 +-- .../sdk/internal/model/JniScanProgress.kt | 13 ++++++- .../client/model/TreeStateUnsafe.kt | 4 +-- .../cash/z/ecc/fixture/FakeRustBackend.kt | 1 + .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 16 ++++++--- .../cash/z/ecc/android/sdk/Synchronizer.kt | 4 +-- .../block/processor/CompactBlockProcessor.kt | 16 +++++---- .../android/sdk/internal/TypesafeBackend.kt | 1 - .../sdk/internal/TypesafeBackendImpl.kt | 2 -- .../sdk/internal/db/derived/DerivedDataDb.kt | 35 ++++++++++++------- .../android/sdk/internal/model/Checkpoint.kt | 7 ++-- .../android/sdk/internal/model/ScanRange.kt | 2 +- 14 files changed, 81 insertions(+), 38 deletions(-) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt index b9410d35..308f04d9 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt @@ -38,6 +38,10 @@ interface Backend { suspend fun initDataDb(seed: ByteArray?): Int + /** + * @throws RuntimeException as a common indicator of the operation failure + */ + @Throws(RuntimeException::class) suspend fun createAccount(seed: ByteArray, treeState: ByteArray, recoverUntil: Long?): JniUnifiedSpendingKey fun isValidShieldedAddr(addr: String): Boolean @@ -54,6 +58,10 @@ interface Backend { suspend fun listTransparentReceivers(account: Int): List + /** + * @throws RuntimeException as a common indicator of the operation failure + */ + @Throws(RuntimeException::class) suspend fun getBalance(account: Int): Long fun getBranchIdForHeight(height: Long): Long @@ -61,8 +69,13 @@ interface Backend { /** * @throws RuntimeException as a common indicator of the operation failure */ + @Throws(RuntimeException::class) suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? + /** + * @throws RuntimeException as a common indicator of the operation failure + */ + @Throws(RuntimeException::class) suspend fun getVerifiedBalance(account: Int): Long suspend fun getNearestRewindHeight(height: Long): Long diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt index 3f61d159..bd9ce2da 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Derivation.kt @@ -40,5 +40,6 @@ interface Derivation { companion object { const val DEFAULT_NUMBER_OF_ACCOUNTS = 1 + val DEFAULT_RECOVERY_UNTIL_HEIGHT = null } } diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt index 858a09cc..0395dfc0 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt @@ -239,7 +239,7 @@ class RustBackend private constructor( ) } - override suspend fun getScanProgress(): JniScanProgress = + override suspend fun getScanProgress(): JniScanProgress? = withContext(SdkDispatchers.DATABASE_IO) { getScanProgress( dataDbFile.absolutePath, @@ -506,7 +506,7 @@ class RustBackend private constructor( private external fun getScanProgress( dbDataPath: String, networkId: Int - ): JniScanProgress + ): JniScanProgress? @JvmStatic private external fun suggestScanRanges( diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniScanProgress.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniScanProgress.kt index 514ea216..305ccaea 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniScanProgress.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniScanProgress.kt @@ -6,11 +6,22 @@ import androidx.annotation.Keep * Serves as cross layer (Kotlin, Rust) communication class. * * @param numerator the numerator of the progress ratio - * @param endHeight the denominator of the progress ratio + * @param denominator the denominator of the progress ratio */ @Keep class JniScanProgress( val numerator: Long, val denominator: Long ) { + init { + require(numerator >= 0L) { + "Numerator $numerator is outside of allowed range [0, Long.MAX_VALUE]" + } + require(denominator >= 1L) { + "Denominator $denominator is outside of allowed range [1, Long.MAX_VALUE]" + } + require(numerator.toFloat().div(denominator) >= 0f) { + "Result of ${numerator.toFloat()}/$denominator is outside of allowed range" + } + } } diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/TreeStateUnsafe.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/TreeStateUnsafe.kt index b258238c..3f1740a1 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/TreeStateUnsafe.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/TreeStateUnsafe.kt @@ -22,7 +22,7 @@ class TreeStateUnsafe( .setTime(time) .setSaplingTree(tree) .build() - return TreeStateUnsafe.new(treeState) + return new(treeState) } } -} \ No newline at end of file +} diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt index 3333134c..829116fb 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt @@ -2,6 +2,7 @@ package cash.z.ecc.fixture import cash.z.ecc.android.sdk.internal.Backend import cash.z.ecc.android.sdk.internal.model.JniBlockMeta +import cash.z.ecc.android.sdk.internal.model.JniScanProgress import cash.z.ecc.android.sdk.internal.model.JniScanRange import cash.z.ecc.android.sdk.internal.model.JniSubtreeRoot import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index b40257ec..4d2e32ba 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -40,7 +40,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.PercentDecimal import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient -import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -521,8 +520,13 @@ class SdkSynchronizer private constructor( seed: ByteArray, checkpoint: Checkpoint, recoverUntil: BlockHeight? - ): UnifiedSpendingKey = - backend.createAccountAndGetSpendingKey(seed, checkpoint, recoverUntil) + ): UnifiedSpendingKey? { + return runCatching { + backend.createAccountAndGetSpendingKey(seed, checkpoint, recoverUntil) + }.onFailure { + Twig.error(it) { "Create account failed." } + }.getOrNull() + } /** * Returns the current Unified Address for this account. @@ -666,7 +670,8 @@ internal object DefaultSynchronizerFactory { zcashNetwork: ZcashNetwork, checkpoint: Checkpoint, seed: ByteArray?, - numberOfAccounts: Int + numberOfAccounts: Int, + recoverUntil: BlockHeight? ): DerivedDataRepository = DbDerivedDataRepository( DerivedDataDb.new( @@ -676,7 +681,8 @@ internal object DefaultSynchronizerFactory { zcashNetwork, checkpoint, seed, - numberOfAccounts + numberOfAccounts, + recoverUntil ) ) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index 4ce70472..d37e9067 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -16,7 +16,6 @@ import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.CheckpointTool -import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.type.AddressType import cash.z.ecc.android.sdk.type.ConsensusMatchType import co.electriccoin.lightwallet.client.model.LightWalletEndpoint @@ -483,7 +482,8 @@ interface Synchronizer { zcashNetwork, loadedCheckpoint, seed, - Derivation.DEFAULT_NUMBER_OF_ACCOUNTS + Derivation.DEFAULT_NUMBER_OF_ACCOUNTS, + Derivation.DEFAULT_RECOVERY_UNTIL_HEIGHT, ) val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt index 02fef611..185cc36c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt @@ -2058,18 +2058,20 @@ class CompactBlockProcessor internal constructor( * @param account the account to check for balance info. * * @return an instance of WalletBalance containing information about available and total funds. + * + * @throws RustLayerException.BalanceException if any error occurs while getting the balances via the Rust layer */ suspend fun getBalanceInfo(account: Account): WalletBalance { - @Suppress("TooGenericExceptionCaught") - return try { + return runCatching { val balanceTotal = backend.getBalance(account) - Twig.debug { "found total balance: $balanceTotal" } + Twig.info { "Found total balance: $balanceTotal" } val balanceAvailable = backend.getVerifiedBalance(account) - Twig.debug { "found available balance: $balanceAvailable" } + Twig.info { "Found available balance: $balanceAvailable" } WalletBalance(balanceTotal, balanceAvailable) - } catch (t: Throwable) { - Twig.debug { "failed to get balance due to $t" } - throw RustLayerException.BalanceException(t) + }.onFailure { + Twig.error(it) { "Failed to get balance due to ${it.localizedMessage}" } + }.getOrElse { + throw RustLayerException.BalanceException(it) } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt index 7bc9eee7..289f55c2 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt @@ -8,7 +8,6 @@ import cash.z.ecc.android.sdk.internal.model.SubtreeRoot import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.FirstClassByteArray -import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt index fb1989a3..2d5d248a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt @@ -9,12 +9,10 @@ import cash.z.ecc.android.sdk.internal.model.SubtreeRoot import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.FirstClassByteArray -import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork -import cash.z.ecc.android.sdk.tool.DerivationTool import kotlinx.coroutines.withContext @Suppress("TooManyFunctions") diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt index 907512c5..8f16167c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DerivedDataDb.kt @@ -6,9 +6,8 @@ import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.TypesafeBackend import cash.z.ecc.android.sdk.internal.db.ReadOnlySupportSqliteOpenHelper -import cash.z.ecc.android.sdk.internal.ext.tryWarn import cash.z.ecc.android.sdk.internal.model.Checkpoint -import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey +import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.ZcashNetwork import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -39,7 +38,7 @@ internal class DerivedDataDb private constructor( // SqliteOpenHelper is happy private const val DATABASE_VERSION = 8 - @Suppress("LongParameterList", "SpreadOperator") + @Suppress("LongParameterList") suspend fun new( context: Context, backend: TypesafeBackend, @@ -47,17 +46,11 @@ internal class DerivedDataDb private constructor( zcashNetwork: ZcashNetwork, checkpoint: Checkpoint, seed: ByteArray?, - numberOfAccounts: Int + numberOfAccounts: Int, + recoverUntil: BlockHeight? ): DerivedDataDb { backend.initDataDb(seed) - // If a seed is provided, fill in the accounts. - seed?.let { - for (i in 1..numberOfAccounts) { - backend.createAccountAndGetSpendingKey(it, checkpoint, null) - } - } - val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly( NoBackupContextWrapper( context, @@ -67,7 +60,25 @@ internal class DerivedDataDb private constructor( DATABASE_VERSION ) - return DerivedDataDb(zcashNetwork, database) + val dataDb = DerivedDataDb(zcashNetwork, database) + + // If a seed is provided, fill in the accounts. + seed?.let { checkedSeed -> + // toInt() should be safe because we expect very few accounts + val missingAccounts = numberOfAccounts - dataDb.accountTable.count().toInt() + require(missingAccounts >= 0) { + "Unexpected number of accounts: $missingAccounts" + } + repeat(missingAccounts) { + runCatching { + backend.createAccountAndGetSpendingKey(checkedSeed, checkpoint, recoverUntil) + }.onFailure { + Twig.error(it) { "Create account failed." } + } + } + } + + return dataDb } } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/Checkpoint.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/Checkpoint.kt index a4b3b177..6173c6c1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/Checkpoint.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/Checkpoint.kt @@ -1,7 +1,7 @@ package cash.z.ecc.android.sdk.internal.model +import cash.z.ecc.android.sdk.internal.ext.isInUIntRange import cash.z.ecc.android.sdk.model.BlockHeight -import cash.z.wallet.sdk.internal.rpc.Service.TreeState import co.electriccoin.lightwallet.client.model.TreeStateUnsafe /** @@ -21,8 +21,9 @@ internal data class Checkpoint( val tree: String ) { fun treeState(): TreeStateUnsafe { - // TODO: epochSeconds should be a Uint32, and for some reason the generated - // Protobuf type Service.TreeState uses Int for this. + require(epochSeconds.isInUIntRange()) { + "epochSeconds $epochSeconds is outside of allowed UInt range" + } return TreeStateUnsafe.fromParts(height.value, hash, epochSeconds.toInt(), tree) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanRange.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanRange.kt index 10693c61..adfc5b16 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanRange.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanRange.kt @@ -10,7 +10,7 @@ internal data class ScanRange( override fun toString() = "ScanRange(range=$range, priority=${getSuggestScanRangePriority()})" internal fun getSuggestScanRangePriority(): SuggestScanRangePriority { - return SuggestScanRangePriority.values() + return SuggestScanRangePriority.entries .firstOrNull { it.priority == priority } ?: SuggestScanRangePriority.Ignored }